Пользовательские эвристики

Как расширить сервис эвристик собственным алгоритмом

Пользовательские эвристики

Термином эвристики мы называем простые и надёжные алгоритмы, используемые для обнаружения таких проблем, как высокая загрузка CPU или яростные клики (click rage). Здесь мы рассмотрим один пример, чтобы понять, как работают эвристики, как создать собственный детектор и увидеть его результаты в DevTools.

Шаблон для эвристик расположен по пути openreplay/backend/pkg/handlers/custom.

package custom

import . "openreplay/backend/pkg/messages"

type CustomHandler struct {
	lastTimestamp uint64
}

func (h *CustomHandler) Handle(message Message, messageID uint64, timestamp uint64) Message {
	h.lastTimestamp = timestamp
	return nil
}

func (h *CustomHandler) Build() Message {
	return nil
}

После того как эвристический метод создан, его можно добавить, изменив файл main.go по пути openreplay/backend/cmd/heuristics: импортировав пользовательский модуль и добавив пользовательскую функцию в обработчик процессора сообщений.

package main

import (
        "log"
        "openreplay/backend/internal/config/heuristics"
        "openreplay/backend/pkg/handlers"
        web2 "openreplay/backend/pkg/handlers/web"
        "openreplay/backend/pkg/intervals"
        logger "openreplay/backend/pkg/log"
        "openreplay/backend/pkg/messages"
        "openreplay/backend/pkg/queue"
        "openreplay/backend/pkg/queue/types"
        "openreplay/backend/pkg/sessions"
        "os"
        "os/signal"
        "syscall"
        "time"
        // Import custom modules here
)

func main() {
        log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile)

        // Load service configuration
        cfg := heuristics.New()

        // HandlersFabric returns the list of message handlers we want to be applied to each incoming message.
        handlersFabric := func() []handlers.MessageProcessor {
                return []handlers.MessageProcessor{
                        // web handlers
                        &web2.ClickRageDetector{},
                        &web2.CpuIssueDetector{},
                        &web2.DeadClickDetector{},
                        &web2.MemoryIssueDetector{},
                        &web2.NetworkIssueDetector{},
                        &web2.PerformanceAggregator{},
                        // Add custom handlers here
                }
        }
        ...
}

Для этого примера мы создадим эвристику Quick Return, которая будет посылать сигнал каждый раз, когда мы возвращаемся к тому же url менее чем за пять секунд. Метод, реализованный для обнаружения такого события, будет использовать события SetPageLocation и MouseClick. Каждый раз при клике мышью мы обновляем текущую метку времени, и если в течение следующих пяти секунд получено сообщение SetPageEvent, указывающее на текущую веб-страницу, то мы возвращаем событие QuickReturn.

Для начала давайте создадим файл quickreturn.go по пути openreplay/backend/pkg/handlers/custom, содержащий код, показанный ниже

package custom
import (
    "log"
    "encoding/json"
    . "openreplay/backend/pkg/messages"
)
type QuickReturnDetector struct {
    timestamp   uint64
    currentPage string
    lastPage    string
}

type CustomPayload struct {
        Timestamp   uint64 `json:"timestamp"`
        CurrentPage string `json:"current_page"`
}

// If received SetPageLocation on same 
func (h *QuickReturnDetector) HandleSetPageLocation(msg *SetPageLocation, messageID uint64, timestamp uint64) Message {
    if (h.timestamp + 5000 >= msg.NavigationStart && h.lastPage == msg.URL) {
        h.timestamp = msg.NavigationStart
        return h.Build()
    }
    h.lastPage = h.currentPage
    h.currentPage = msg.URL
    h.timestamp = msg.NavigationStart
    return nil
}
// detect when a button is clicked (selector must have string 'button' in it)
func (h *QuickReturnDetector) HandleMouseClick(msg *MouseClick, messageID uint64, timestamp uint64) {
    h.timestamp = timestamp
}
func (h *QuickReturnDetector) Handle(message Message, messageID uint64, timestamp uint64) Message {
    switch msg := message.(type) {
    case *SetPageLocation:
        if msg.NavigationStart != 0 {
            return h.HandleSetPageLocation(msg, messageID, timestamp)
        }
    case *MouseClick:
        h.HandleMouseClick(msg, messageID, timestamp)
    }
    return nil
}
func (h *QuickReturnDetector) Build() Message {
    payload := &CustomPayload{
        CurrentPage: h.currentPage,
        Timestamp: h.timestamp,
    }
    payloadData, err := json.Marshal(payload)
    if err != nil {
            log.Println("JSON encoding error", err)
            return nil
    }
    payloadString := string(payloadData)
    event := &CustomEvent {
        MessageID: 122,
        Timestamp: h.timestamp,
        Name:      "quickreturn",
        Payload:   payloadString,
    }
    return event
}

Теперь эвристика создана, остаётся только включить её. Для этого мы должны добавить эвристику в файл main.go по пути openreplay/backend/cmd/heuristics следующим образом:

package main

import (
        "log"
        "openreplay/backend/internal/config/heuristics"
        "openreplay/backend/pkg/handlers"
        web2 "openreplay/backend/pkg/handlers/web"
        "openreplay/backend/pkg/intervals"
        logger "openreplay/backend/pkg/log"
        "openreplay/backend/pkg/messages"
        "openreplay/backend/pkg/queue"
        "openreplay/backend/pkg/queue/types"
        "openreplay/backend/pkg/sessions"
        "os"
        "os/signal"
        "syscall"
        "time"
        // Add custom module
        custom "openreplay/backend/pkg/custom"
)

func main() {
        log.SetFlags(log.LstdFlags | log.LUTC | log.Llongfile)

        // Load service configuration
        cfg := heuristics.New()

        // HandlersFabric returns the list of message handlers we want to be applied to each incoming message.
        handlersFabric := func() []handlers.MessageProcessor {
                return []handlers.MessageProcessor{
                        // web handlers
                        &web2.ClickRageDetector{},
                        &web2.CpuIssueDetector{},
                        &web2.DeadClickDetector{},
                        &web2.MemoryIssueDetector{},
                        &web2.NetworkIssueDetector{},

                        // The new handler
                        &custom.QuickReturnDetector{},
                }
        }
        ...
}

Теперь эвристика доступна, и сообщения отправляются как Custom Event. Обратите внимание, что вам потребуется заново развернуть бэкенд, прежде чем вы сможете воспользоваться новой эвристикой. Для этого выполните шаги из раздела build and deploy на странице deploy from source.

Использование пользовательского события

Section titled Использование пользовательского события

Теперь, когда ваша пользовательская эвристика отправляет новое пользовательское событие, следующее, что вам нужно сделать, — это каким-то образом использовать это событие. В приведённом ранее примере у вас есть два варианта:

  1. Вы можете фильтровать сессии по пользовательскому событию, обеспечивая вывод только тех, у которых есть эта «проблема» (в нашем случае — проблема быстрого возврата).
  2. Вы можете найти событие на вкладке Events в разделе DevTools плеера. Таким образом вы можете точно определить момент, когда событие срабатывает.

Фильтрация сессий по пользовательскому событию

Section titled Фильтрация сессий по пользовательскому событию

Чтобы отфильтровать сессии по вашему новому пользовательскому событию, просто воспользуйтесь функцией Omnisearch: нажмите на строку поиска и выберите «Custom Events» в качестве типа фильтра. После этого вы сможете ввести имя события; оно должно автоматически дополняться, как только вы начнёте печатать, как видно на изображении ниже:

Custom event filter

Обратите внимание, что если автодополнение не работает или не возвращает имя искомого события, значит, в базе данных нет события с таким именем. В этом случае вам следует ещё раз проверить вашу логику, чтобы убедиться, что вы правильно инициируете отправку пользовательского события.

Поиск пользовательского события в DevTools

Section titled Поиск пользовательского события в DevTools

Если вы хотите точно понять, когда срабатывает пользовательское событие, вам придётся воспроизвести сессию и нажать на вкладку «Events» в DevTools. Вы найдёте её в правом нижнем углу экрана. После нажатия она откроется, и вы увидите список событий, включая ваше пользовательское, как показано на изображении ниже:

Events listed in devTools

В ходе этого простого процесса мы создали новый детектор. Разумеется, ваш алгоритм может быть более сложным и включать комбинацию событий разных типов.

Остались вопросы?

Section titled Остались вопросы?

Если вы столкнётесь с какими-либо проблемами, присоединяйтесь к нашему Slack или загляните на наш Форум и получите помощь от нашего сообщества.