diff --git a/backend/cmd/heuristics/main.go b/backend/cmd/heuristics/main.go index 9feec3823..c40a2fe2a 100644 --- a/backend/cmd/heuristics/main.go +++ b/backend/cmd/heuristics/main.go @@ -34,6 +34,7 @@ func main() { &web.MemoryIssueDetector{}, &web.NetworkIssueDetector{}, &web.PerformanceAggregator{}, + web.NewAppCrashDetector(), } } diff --git a/backend/internal/db/datasaver/saver.go b/backend/internal/db/datasaver/saver.go index a1247c424..12bca3b83 100644 --- a/backend/internal/db/datasaver/saver.go +++ b/backend/internal/db/datasaver/saver.go @@ -66,7 +66,8 @@ func (s *saverImpl) handleMessage(msg Message) error { case *Metadata: return s.sessions.UpdateMetadata(m.SessionID(), m.Key, m.Value) case *IssueEvent: - if err = s.pg.InsertIssueEvent(session, m); err != nil { + err = s.pg.InsertIssueEvent(session, m) + if err != nil { return err } return s.sessions.UpdateIssuesStats(session.SessionID, 0, postgres.GetIssueScore(m)) diff --git a/backend/pkg/db/postgres/events.go b/backend/pkg/db/postgres/events.go index 30364d1f9..f38e12d7c 100644 --- a/backend/pkg/db/postgres/events.go +++ b/backend/pkg/db/postgres/events.go @@ -77,6 +77,10 @@ func (conn *Conn) InsertIssueEvent(sess *sessions.Session, e *messages.IssueEven payload = nil } + if e.Type == "app_crash" { + log.Printf("app crash event: %+v", e) + } + if err := conn.bulks.Get("webIssues").Append(sess.ProjectID, issueID, e.Type, e.ContextString); err != nil { log.Printf("insert web issue err: %s", err) } diff --git a/backend/pkg/handlers/web/appCrash.go b/backend/pkg/handlers/web/appCrash.go new file mode 100644 index 000000000..1b6e95072 --- /dev/null +++ b/backend/pkg/handlers/web/appCrash.go @@ -0,0 +1,81 @@ +package web + +import ( + "log" + "openreplay/backend/pkg/messages" +) + +const CrashWindow = 2 * 1000 +const CrashThreshold = 70 + +type AppCrashDetector struct { + dropTimestamp uint64 + dropMessageID uint64 + lastIssueTimestamp uint64 +} + +func NewAppCrashDetector() *AppCrashDetector { + return &AppCrashDetector{} +} + +func (h *AppCrashDetector) reset() { + h.dropTimestamp = 0 + h.lastIssueTimestamp = 0 +} + +func (h *AppCrashDetector) updateLastIssueTimestamp(msgTimestamp uint64) { + if msgTimestamp > h.lastIssueTimestamp { + h.lastIssueTimestamp = msgTimestamp + } +} + +func (h *AppCrashDetector) build() messages.Message { + if h.dropTimestamp == 0 || h.lastIssueTimestamp == 0 { + // Nothing to build + return nil + } + + // Calculate timestamp difference + var diff uint64 + if h.lastIssueTimestamp > h.dropTimestamp { + diff = h.lastIssueTimestamp - h.dropTimestamp + } else { + diff = h.dropTimestamp - h.lastIssueTimestamp + } + + // Check possible app crash + if diff < CrashWindow { + msg := &messages.IssueEvent{ + MessageID: h.dropMessageID, + Timestamp: h.dropTimestamp, + Type: "app_crash", + } + log.Printf("created app crash event: %+v", msg) + h.reset() + return msg + } + return nil +} + +func (h *AppCrashDetector) Handle(message messages.Message, timestamp uint64) messages.Message { + switch msg := message.(type) { + case *messages.UnbindNodes: + if msg.TotalRemovedPercent < CrashThreshold { + // Not enough nodes removed + return nil + } + h.dropTimestamp = timestamp + h.dropMessageID = msg.MsgID() + case *messages.JSException: + h.updateLastIssueTimestamp(msg.Timestamp) + case *messages.NetworkRequest: + if msg.Status >= 400 { + h.updateLastIssueTimestamp(msg.Timestamp) + } + } + return h.build() +} + +func (h *AppCrashDetector) Build() messages.Message { + return h.build() +}