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

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

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

Термином эвристики мы называем простые и надёжные алгоритмы, используемые для обнаружения таких проблем, как высокая загрузка 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. Обратите внимание, что вам потребуется повторно развернуть ваш back-end, прежде чем вы сможете воспользоваться новой эвристикой. Для этого выполните шаги из раздела build and deploy на странице deploy from source.

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

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

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

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

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

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

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

Custom event filter

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

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

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

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

Events listed in devTools

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

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