diff --git a/backend/internal/handlers/ios/appNotResponding.go b/backend/internal/handlers/ios/appNotResponding.go index 1241648db..097361c00 100644 --- a/backend/internal/handlers/ios/appNotResponding.go +++ b/backend/internal/handlers/ios/appNotResponding.go @@ -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 } diff --git a/backend/internal/handlers/ios/clickRage.go b/backend/internal/handlers/ios/clickRage.go index 2707c04d5..6562e05c1 100644 --- a/backend/internal/handlers/ios/clickRage.go +++ b/backend/internal/handlers/ios/clickRage.go @@ -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 } diff --git a/backend/internal/handlers/ios/performanceAggregator.go b/backend/internal/handlers/ios/performanceAggregator.go index 1525127b8..b4bc812c7 100644 --- a/backend/internal/handlers/ios/performanceAggregator.go +++ b/backend/internal/handlers/ios/performanceAggregator.go @@ -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 } diff --git a/backend/internal/handlers/web/clickRage.go b/backend/internal/handlers/web/clickRage.go index db22a9667..e22eb6454 100644 --- a/backend/internal/handlers/web/clickRage.go +++ b/backend/internal/handlers/web/clickRage.go @@ -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 } diff --git a/backend/internal/handlers/web/cpuIssue.go b/backend/internal/handlers/web/cpuIssue.go index 5cc12be68..56f483e8b 100644 --- a/backend/internal/handlers/web/cpuIssue.go +++ b/backend/internal/handlers/web/cpuIssue.go @@ -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, diff --git a/backend/internal/handlers/web/deadClick.go b/backend/internal/handlers/web/deadClick.go index a04da9be9..6377b074e 100644 --- a/backend/internal/handlers/web/deadClick.go +++ b/backend/internal/handlers/web/deadClick.go @@ -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 } diff --git a/backend/internal/handlers/web/domDrop.go b/backend/internal/handlers/web/domDrop.go index c89fab2c4..4a3ec2065 100644 --- a/backend/internal/handlers/web/domDrop.go +++ b/backend/internal/handlers/web/domDrop.go @@ -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 } diff --git a/backend/internal/handlers/web/memoryIssue.go b/backend/internal/handlers/web/memoryIssue.go index ac8ca8a14..487c396a9 100644 --- a/backend/internal/handlers/web/memoryIssue.go +++ b/backend/internal/handlers/web/memoryIssue.go @@ -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 { diff --git a/backend/internal/handlers/web/performanceAggregator.go b/backend/internal/handlers/web/performanceAggregator.go index a7bf79f9f..928cedeb9 100644 --- a/backend/internal/handlers/web/performanceAggregator.go +++ b/backend/internal/handlers/web/performanceAggregator.go @@ -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 { diff --git a/backend/pkg/messages/messages.go b/backend/pkg/messages/messages.go index e9aec5788..418c47342 100644 --- a/backend/pkg/messages/messages.go +++ b/backend/pkg/messages/messages.go @@ -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 {