feat(backend/handlers): refactored web and ios message handlers
This commit is contained in:
parent
47007eb9d7
commit
26e23d594f
10 changed files with 148 additions and 83 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue