返回 DeepSeek-Reasonix
startup_diagnostics.go
根目录 / desktop / startup_diagnostics.go
1 package main
2
3 import (
4 "fmt"
5 "runtime"
6 "time"
7
8 "reasonix/internal/repair"
9 )
10
11 func (a *App) recordPreviousRunDiagnostics() {
12 previous := a.lifecycle.previousRun
13 if previous.Abnormal {
14 report := previousRunReport(previous)
15 _ = writePendingReport(report, true)
16 if m := a.metrics.Load(); m != nil {
17 m.inc("desktop_legacy_exit", "abnormal")
18 m.inc("desktop_legacy_exit_phase", metricBucket(previous.Phase))
19 m.persist()
20 }
21 }
22 for _, lifecycle := range a.lifecycle.previousRuns {
23 persisted := writePendingReport(desktopLifecycleReport(lifecycle), true)
24 a.lifecycle.tracker.finalizeObservation(lifecycle, persisted)
25 if !persisted {
26 continue
27 }
28 if m := a.metrics.Load(); m != nil {
29 m.inc("desktop_exit", "abnormal")
30 m.inc("desktop_exit_phase", metricBucket(lifecycle.Phase))
31 m.persist()
32 }
33 }
34 }
35
36 func previousRunReport(previous repair.PreviousRunObservation) crashReport {
37 phase := metricBucket(previous.Phase)
38 uptime := metricBucket(previous.UptimeBucket)
39 profile := metricBucket(previous.InstallProfile)
40 update := "none"
41 if previous.UpdateTo != "" {
42 update = sanitizeCrashField(previous.UpdateFrom+" -> "+previous.UpdateTo, 128)
43 }
44 message := fmt.Sprintf(`[desktop.legacy_abnormal_exit]
45
46 Reasonix consumed a legacy v1.18-v1.19 startup record whose owner was no longer running.
47
48 --- lifecycle context ---
49 phase: %s
50 uptime bucket: %s
51 install profile: %s
52 previous version: %s
53 update transition: %s`,
54 phase,
55 uptime,
56 profile,
57 sanitizeCrashField(previous.Version, 64),
58 update,
59 )
60 report := baseCrashReport("crash")
61 report.SchemaVersion = 4
62 report.Source = "native.lifecycle.legacy"
63 report.Label = "desktop.legacy_abnormal_exit"
64 report.ErrorType = "LegacyAbnormalDesktopExit"
65 report.ErrorMessage = "A legacy startup record was consumed once after its owner stopped."
66 report.TopFrame = "desktop.lifecycle.legacy." + phase
67 report.FingerprintHint = "desktop.legacy_abnormal_exit." + runtime.GOOS + "." + phase
68 report.Diagnostics = &crashDiagnostics{
69 SubjectVersion: previous.Version, ObserverVersion: version, ObserverBuildCommit: buildCommit(),
70 ProcessRole: "service", LastPhase: phase, ObservedAt: time.Now().UTC().Format(time.RFC3339Nano),
71 TerminationReason: "unknown", CleanupOutcome: "unknown", Evidence: "observed",
72 Category: "historical_record", LegacyParsed: true,
73 }
74 report.Message = sanitizeCrashText(message, maxCrashDetailBytes)
75 return report
76 }
77
78 func desktopLifecycleReport(previous desktopLifecycleObservation) crashReport {
79 phase := metricBucket(previous.Phase)
80 category := "unclean_exit"
81 processRole := previous.ProcessRole
82 if processRole == "" {
83 processRole = "service"
84 }
85 if previous.TerminationReason == shutdownReasonStartupFailure {
86 category = "startup_failure"
87 }
88 message := fmt.Sprintf(`[desktop.abnormal_exit.v2]
89
90 Reasonix found a per-process lifecycle record whose desktop process was no longer running.
91
92 --- lifecycle context ---
93 phase: %s
94 previous version: %s
95 previous channel: %s
96 started at: %s
97 last phase update: %s`,
98 phase,
99 sanitizeCrashField(previous.Version, 64),
100 sanitizeCrashField(previous.Channel, 32),
101 sanitizeCrashField(previous.StartedAt, 64),
102 sanitizeCrashField(previous.UpdatedAt, 64),
103 )
104 report := baseCrashReport("crash")
105 report.SchemaVersion = 4
106 report.Source = "native.lifecycle"
107 report.Label = "desktop.abnormal_exit.v2"
108 report.ErrorType = "AbnormalDesktopExit"
109 report.ErrorMessage = "A per-process lifecycle record remained after its desktop process stopped."
110 report.TopFrame = "desktop.lifecycle.v2." + phase
111 report.FingerprintHint = "desktop.abnormal_exit.v2." + runtime.GOOS + "." + phase
112 report.Diagnostics = &crashDiagnostics{
113 SubjectVersion: previous.Version, SubjectBuildCommit: previous.BuildCommit, SubjectChannel: previous.Channel,
114 ObserverVersion: version, ObserverBuildCommit: buildCommit(), RunID: previous.RunID, IncidentID: previous.IncidentID,
115 ProcessRole: processRole, LastPhase: phase, LastPhaseAt: previous.UpdatedAt,
116 ObservedAt: time.Now().UTC().Format(time.RFC3339Nano), TerminationReason: previous.TerminationReason,
117 CleanupOutcome: previous.CleanupOutcome, Evidence: "observed", Category: category,
118 }
119 report.Message = sanitizeCrashText(message, maxCrashDetailBytes)
120 return report
121 }
122
122 lines GO