Heurísticas personalizadas

Cómo ampliar el servicio de heurísticas con tu propio algoritmo

Heurísticas personalizadas

Usamos el término heurísticas para referirnos a algoritmos simples y robustos empleados para detectar problemas como un alto uso de CPU o la rabia al hacer clic (click rage). Aquí usaremos un ejemplo para entender cómo funcionan las heurísticas y cómo crear tu propio detector y ver sus resultados en las DevTools.

Una plantilla para las heurísticas se encuentra en la ruta 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
}

Cuando se crea el método heurístico, puede añadirse modificando el archivo main.go en la ruta openreplay/backend/cmd/heuristics importando el módulo personalizado y añadiendo la función personalizada al handler del procesador de mensajes.

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
                }
        }
        ...
}

Para este ejemplo, crearemos la heurística Quick Return, que enviará una señal cada vez que regresemos a la misma url en menos de cinco segundos. El método implementado para detectar dicho evento aprovechará los eventos SetPageLocation y MouseClick. Cada vez que se produce un clic del ratón actualizamos la marca de tiempo actual y, si se recibe un mensaje SetPageEvent en los cinco segundos siguientes apuntando a la página web actual, entonces devolvemos un evento QuickReturn.

Para empezar, creemos el archivo quickreturn.go en la ruta openreplay/backend/pkg/handlers/custom con el código que se muestra a continuación

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
}

Ahora que la heurística está creada, solo falta habilitarla. Para ello, tenemos que añadir la heurística al archivo main.go en la ruta openreplay/backend/cmd/heuristics de la siguiente manera:

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{},
                }
        }
        ...
}

La heurística ya está disponible y los mensajes se envían como un Custom Event. Ten en cuenta que necesitarás volver a desplegar tu back-end antes de poder aprovechar la nueva heurística. Para ello, sigue los pasos de la sección build and deploy de la página deploy from source.

Cómo aprovechar el evento personalizado

Section titled Cómo aprovechar el evento personalizado

Ahora que tienes tu heurística personalizada enviando un nuevo evento personalizado, lo siguiente que debes hacer es darle algún uso al evento. Con el ejemplo anterior, tienes dos opciones:

  1. Puedes filtrar las sesiones por evento personalizado, asegurándote de listar solo las que tienen ese “problema” (en nuestro caso, el problema de la vuelta rápida).
  2. Puedes encontrar el evento dentro de la pestaña Events en la sección DevTools del reproductor. De esa manera puedes identificar el momento exacto en que se dispara el evento.

Filtrar sesiones por evento personalizado

Section titled Filtrar sesiones por evento personalizado

Para filtrar sesiones por tu nuevo evento personalizado, simplemente usa la función Omnisearch, haciendo clic en la barra de búsqueda y seleccionando “Custom Events” como tipo de filtro. Una vez hecho esto, podrás introducir el nombre del evento; debería autocompletarse el nombre en cuanto empieces a escribir, como puedes ver en la imagen siguiente:

Custom event filter

Ten en cuenta que si el autocompletado no funciona o no devuelve el nombre del evento que estás buscando, entonces no existe ningún evento con ese nombre en la base de datos. En ese caso, deberías revisar tu lógica para asegurarte de que estás disparando correctamente el envío del evento personalizado.

Encontrar el evento personalizado en las DevTools

Section titled Encontrar el evento personalizado en las DevTools

Si quieres entender exactamente cuándo se dispara el evento personalizado, tendrás que reproducir una sesión y hacer clic en la pestaña “Events” de las DevTools. La encontrarás situada en la esquina inferior derecha de la pantalla. Una vez hagas clic, se abrirá y verás la lista de eventos, incluyendo el tuyo personalizado, como se ve en la imagen siguiente:

Events listed in devTools

En este sencillo proceso hemos creado un nuevo detector. Por supuesto, tu algoritmo puede ser más sofisticado e incluir una combinación de diferentes tipos de eventos.

Si encuentras algún problema, conéctate a nuestro Slack o consulta nuestro Foro y obtén ayuda de nuestra comunidad.