feat(backend/handlers): refactored web and ios message handlers

This commit is contained in:
Alexander Zavorotynskiy 2022-05-10 15:40:55 +02:00
parent 47007eb9d7
commit 26e23d594f
10 changed files with 148 additions and 83 deletions

View file

@ -3,9 +3,17 @@ package ios
import (
"openreplay/backend/internal/handlers"
. "openreplay/backend/pkg/messages"
"time"
)
// app is not responding detector
/*
Handler name: AppNotResponding
Input events: IOSClickEvent,
IOSInputEvent,
IOSPerformanceEvent,
IOSSessionEnd
Output event: IOSIssueEvent
*/
const MIN_TIME_AFTER_LAST_HEARTBEAT = 60 * 1000
@ -17,46 +25,44 @@ type AppNotResponding struct {
}
func (h *AppNotResponding) Handle(message Message, messageID uint64, timestamp uint64) Message {
var event Message = nil
switch m := message.(type) {
case *IOSClickEvent:
h.buildIf(m.Timestamp)
event = h.build(m.Timestamp)
h.lastLabel = m.Label
h.lastHeartbeatTimestamp = m.Timestamp
h.lastHeartbeatIndex = m.Index
case *IOSInputEvent:
h.buildIf(m.Timestamp)
event = h.build(m.Timestamp)
h.lastLabel = m.Label
h.lastHeartbeatTimestamp = m.Timestamp
h.lastHeartbeatIndex = m.Index
case *IOSPerformanceEvent:
h.buildIf(m.Timestamp)
event = h.build(m.Timestamp)
h.lastHeartbeatTimestamp = m.Timestamp
h.lastHeartbeatIndex = m.Index
case *IOSSessionEnd:
h.buildIf(m.Timestamp)
event = h.build(m.Timestamp)
}
return nil
return event
}
func (h *AppNotResponding) Build() Message {
//TODO implement me
panic("implement me")
return h.build(uint64(time.Now().Unix()))
}
func (h *AppNotResponding) buildIf(timestamp uint64) {
func (h *AppNotResponding) build(timestamp uint64) Message {
if h.lastHeartbeatTimestamp != 0 && h.lastHeartbeatTimestamp+MIN_TIME_AFTER_LAST_HEARTBEAT <= timestamp {
m := &IOSIssueEvent{
event := &IOSIssueEvent{
Type: "anr",
ContextString: h.lastLabel,
Timestamp: h.lastHeartbeatTimestamp,
}
m.Timestamp = h.lastHeartbeatTimestamp
m.Index = h.lastHeartbeatIndex // Associated Index/ MessageID ?
h.Append(m)
event.Index = h.lastHeartbeatIndex // Associated Index/ MessageID ?
// Reset
h.lastHeartbeatTimestamp = 0
h.lastHeartbeatIndex = 0
return event
}
}
func (h *AppNotResponding) HandleMessage(msg Message) {
// TODO: delete it
return nil
}

View file

@ -6,9 +6,14 @@ import (
. "openreplay/backend/pkg/messages"
)
const CLICK_TIME_DIFF = 200
/*
Handler name: ClickRage
Input events: IOSClickEvent,
IOSSessionEnd
Output event: IOSIssueEvent
*/
//const MIN_CLICKS_IN_A_ROW = 3
const CLICK_TIME_DIFF = 200
type ClickRageDetector struct {
handlers.ReadyMessageStore
@ -20,6 +25,7 @@ type ClickRageDetector struct {
}
func (h *ClickRageDetector) Handle(message Message, messageID uint64, timestamp uint64) Message {
var event Message = nil
switch m := message.(type) {
case *IOSClickEvent:
if h.lastTimestamp+CLICK_TIME_DIFF < m.Timestamp && h.lastLabel == m.Label {
@ -27,7 +33,7 @@ func (h *ClickRageDetector) Handle(message Message, messageID uint64, timestamp
h.countsInARow += 1
return nil
}
h.build()
event = h.Build()
if m.Label != "" {
h.lastTimestamp = m.Timestamp
h.lastLabel = m.Label
@ -36,33 +42,25 @@ func (h *ClickRageDetector) Handle(message Message, messageID uint64, timestamp
h.countsInARow = 1
}
case *IOSSessionEnd:
h.build()
event = h.Build()
}
return nil
return event
}
func (h *ClickRageDetector) Build() Message {
//TODO implement me
panic("implement me")
}
func (h *ClickRageDetector) build() {
if h.countsInARow >= web.MIN_CLICKS_IN_A_ROW {
m := &IOSIssueEvent{
event := &IOSIssueEvent{
Type: "click_rage",
ContextString: h.lastLabel,
}
m.Timestamp = h.firstInARawTimestamp
m.Index = h.firstInARawSeqIndex // Associated Index/ MessageID ?
h.Append(m)
event.Timestamp = h.firstInARawTimestamp
event.Index = h.firstInARawSeqIndex // Associated Index/ MessageID ?
return event
}
h.lastTimestamp = 0
h.lastLabel = ""
h.firstInARawTimestamp = 0
h.firstInARawSeqIndex = 0
h.countsInARow = 0
}
func (h *ClickRageDetector) HandleMessage(msg Message) {
// TODO: delete it
return nil
}

View file

@ -3,8 +3,16 @@ package ios
import (
"openreplay/backend/internal/handlers"
. "openreplay/backend/pkg/messages"
"time"
)
/*
Handler name: PerformanceAggregator
Input events: IOSPerformanceEvent,
IOSSessionEnd
Output event: IssueEvent
*/
const AGGR_TIME = 15 * 60 * 1000
type valueAggregator struct {
@ -32,13 +40,14 @@ func (h *PerformanceAggregator) Handle(message Message, messageID uint64, timest
if h.pa == nil {
h.pa = &IOSPerformanceAggregated{} // TODO: struct type in messages
}
switch m := message.(type) { // TODO: All Timestampe messages
var event Message = nil
switch m := message.(type) { // TODO: All Timestamp messages
case *IOSPerformanceEvent:
if h.pa.TimestampStart == 0 {
h.pa.TimestampStart = m.Timestamp
}
if h.pa.TimestampStart+AGGR_TIME <= m.Timestamp {
h.build(m.Timestamp)
event = h.build(m.Timestamp)
}
switch m.Name {
case "fps":
@ -79,35 +88,32 @@ func (h *PerformanceAggregator) Handle(message Message, messageID uint64, timest
}
}
case *IOSSessionEnd:
h.build(m.Timestamp)
event = h.build(m.Timestamp)
}
return nil
return event
}
func (h *PerformanceAggregator) Build() Message {
//TODO implement me
panic("implement me")
return h.build(uint64(time.Now().Unix()))
}
func (h *PerformanceAggregator) build(timestamp uint64) {
func (h *PerformanceAggregator) build(timestamp uint64) Message {
if h.pa == nil {
return
return nil
}
h.pa.TimestampEnd = timestamp
h.pa.AvgFPS = h.fps.aggregate()
h.pa.AvgCPU = h.cpu.aggregate()
h.pa.AvgMemory = h.memory.aggregate()
h.pa.AvgBattery = h.battery.aggregate()
h.Append(h.pa)
event := h.pa
h.pa = &IOSPerformanceAggregated{}
for _, agg := range []valueAggregator{h.fps, h.cpu, h.memory, h.battery} {
agg.sum = 0
agg.count = 0
}
}
func (h *PerformanceAggregator) HandleMessage(msg Message) {
// TODO: delete it
return event
}

View file

@ -2,11 +2,16 @@ package web
import (
"encoding/json"
"log"
. "openreplay/backend/pkg/messages"
)
// TODO: Description of click rage detector
/*
Handler name: ClickRage
Input event: MouseClick
Output event: IssueEvent
*/
const MAX_TIME_DIFF = 300
const MIN_CLICKS_IN_A_ROW = 3
@ -28,26 +33,28 @@ func (crd *ClickRageDetector) reset() {
}
func (crd *ClickRageDetector) Build() Message {
defer crd.reset()
if crd.countsInARow >= MIN_CLICKS_IN_A_ROW {
payload, _ := json.Marshal(struct{ Count int }{crd.countsInARow})
i := &IssueEvent{
payload, err := json.Marshal(struct{ Count int }{crd.countsInARow})
if err != nil {
log.Printf("can't marshal ClickRage payload to json: %s", err)
}
event := &IssueEvent{
Type: "click_rage",
ContextString: crd.lastLabel,
Payload: string(payload), // TODO: json message field type
Payload: string(payload),
Timestamp: crd.firstInARawTimestamp,
MessageID: crd.firstInARawMessageId,
}
crd.reset()
return i
return event
}
crd.reset()
return nil
}
func (crd *ClickRageDetector) Handle(message Message, messageID uint64, timestamp uint64) Message {
switch msg := message.(type) {
case *MouseClick:
// TODO: check if we it is ok to capture clickrages without the connected CleckEvent in db.
// TODO: check if we it is ok to capture clickRage event without the connected ClickEvent in db.
if msg.Label == "" {
return crd.Build()
}
@ -56,13 +63,13 @@ func (crd *ClickRageDetector) Handle(message Message, messageID uint64, timestam
crd.countsInARow += 1
return nil
}
i := crd.Build()
event := crd.Build()
crd.lastTimestamp = timestamp
crd.lastLabel = msg.Label
crd.firstInARawTimestamp = timestamp
crd.firstInARawMessageId = messageID
crd.countsInARow = 1
return i
return event
}
return nil
}

View file

@ -2,12 +2,18 @@ package web
import (
"encoding/json"
"log"
. "openreplay/backend/pkg/messages"
"openreplay/backend/pkg/messages/performance"
)
// TODO: Description of cpu issue detector
/*
Handler name: CpuIssue
Input events: PerformanceTrack,
SetPageLocation
Output event: IssueEvent
*/
const CPU_THRESHOLD = 70 // % out of 100
const CPU_MIN_DURATION_TRIGGER = 6 * 1000
@ -36,10 +42,14 @@ func (f *CpuIssueDetector) Build() Message {
return nil
}
payload, _ := json.Marshal(struct {
payload, err := json.Marshal(struct {
Duration uint64
Rate uint64
}{duration, maxRate})
if err != nil {
log.Printf("can't marshal CpuIssue payload to json: %s", err)
}
return &IssueEvent{
Type: "cpu",
Timestamp: timestamp,

View file

@ -4,7 +4,22 @@ import (
. "openreplay/backend/pkg/messages"
)
// TODO: Description of dead click detector
/*
Handler name: DeadClick
Input events: SetInputTarget,
CreateDocument,
MouseClick,
SetNodeAttribute,
RemoveNodeAttribute,
CreateElementNode,
CreateTextNode,
MoveNode,
RemoveNode,
SetCSSData,
CSSInsertRule,
CSSDeleteRule
Output event: IssueEvent
*/
const CLICK_RELATION_TIME = 1400
@ -23,23 +38,22 @@ func (d *DeadClickDetector) reset() {
d.lastMessageID = 0
}
func (d *DeadClickDetector) handleReaction(timestamp uint64) Message {
if d.lastMouseClick == nil || d.lastClickTimestamp+CLICK_RELATION_TIME > timestamp { // riaction is instant
d.reset()
func (d *DeadClickDetector) build(timestamp uint64) Message {
defer d.reset()
if d.lastMouseClick == nil || d.lastClickTimestamp+CLICK_RELATION_TIME > timestamp { // reaction is instant
return nil
}
i := &IssueEvent{
event := &IssueEvent{
Type: "dead_click",
ContextString: d.lastMouseClick.Label,
Timestamp: d.lastClickTimestamp,
MessageID: d.lastMessageID,
}
d.reset()
return i
return event
}
func (d *DeadClickDetector) Build() Message {
return d.handleReaction(d.lastTimestamp)
return d.build(d.lastTimestamp)
}
func (d *DeadClickDetector) Handle(message Message, messageID uint64, timestamp uint64) Message {
@ -56,14 +70,14 @@ func (d *DeadClickDetector) Handle(message Message, messageID uint64, timestamp
if msg.Label == "" {
return nil
}
i := d.handleReaction(timestamp)
event := d.build(timestamp)
if d.inputIDSet[msg.ID] { // ignore if input
return i
return event
}
d.lastMouseClick = msg
d.lastClickTimestamp = timestamp
d.lastMessageID = messageID
return i
return event
case *SetNodeAttribute,
*RemoveNodeAttribute,
*CreateElementNode,
@ -73,7 +87,7 @@ func (d *DeadClickDetector) Handle(message Message, messageID uint64, timestamp
*SetCSSData,
*CSSInsertRule,
*CSSDeleteRule:
return d.handleReaction(timestamp)
return d.build(timestamp)
}
return nil
}

View file

@ -4,7 +4,13 @@ import (
. "openreplay/backend/pkg/messages"
)
// TODO: Description of dom drop detector
/*
Handler name: DomDrop
Input events: CreateElementNode,
CreateTextNode,
RemoveNode
Output event: DOMDrop
*/
const DROP_WINDOW = 200 //ms
const CRITICAL_COUNT = 1 // Our login page contains 20. But on crush it removes only roots (1-3 nodes).
@ -38,13 +44,12 @@ func (dd *domDropDetector) Handle(message Message, _ uint64, timestamp uint64) M
}
func (dd *domDropDetector) Build() Message {
defer dd.reset()
if dd.removedCount >= CRITICAL_COUNT {
domDrop := &DOMDrop{
Timestamp: dd.lastDropTimestamp,
}
dd.reset()
return domDrop
}
dd.reset()
return nil
}

View file

@ -2,12 +2,18 @@ package web
import (
"encoding/json"
"log"
"math"
. "openreplay/backend/pkg/messages"
)
// TODO: Description of memory issue detector
/*
Handler name: MemoryIssue
Input events: PerformanceTrack,
SetPageLocation
Output event: IssueEvent
*/
const MIN_COUNT = 3
const MEM_RATE_THRESHOLD = 300 // % to average
@ -21,22 +27,29 @@ type MemoryIssueDetector struct {
contextString string
}
func (f *MemoryIssueDetector) reset() {
f.startTimestamp = 0
f.startMessageID = 0
f.rate = 0
}
func (f *MemoryIssueDetector) Build() Message {
if f.startTimestamp == 0 {
return nil
}
payload, _ := json.Marshal(struct{ Rate int }{f.rate - 100})
i := &IssueEvent{
payload, err := json.Marshal(struct{ Rate int }{f.rate - 100})
if err != nil {
log.Printf("can't marshal MemoryIssue payload to json: %s", err)
}
event := &IssueEvent{
Type: "memory",
Timestamp: f.startTimestamp,
MessageID: f.startMessageID,
ContextString: f.contextString,
Payload: string(payload),
}
f.startTimestamp = 0
f.startMessageID = 0
f.rate = 0
return i
f.reset()
return event
}
func (f *MemoryIssueDetector) Handle(message Message, messageID uint64, timestamp uint64) Message {

View file

@ -7,6 +7,12 @@ import (
"openreplay/backend/pkg/messages/performance"
)
/*
Handler name: PerformanceAggregator
Input event: PerformanceTrack
Output event: PerformanceTrackAggr
*/
const AGGREGATION_WINDOW = 2 * 60 * 1000
type PerformanceAggregator struct {

View file

@ -1168,7 +1168,7 @@ type IssueEvent struct {
Type string
ContextString string
Context string
Payload string
Payload string // TODO: check, maybe it's better to use empty interface here
}
func (msg *IssueEvent) Encode() []byte {