main 5041353

startup_trace.go RawHistoryFolder

1package cef
2
3import (
4	"sync"
5	"time"
6
7	"github.com/rs/zerolog"
8)
9
10// startupTrace records the accelerated CEF/DMABUF/GTK path from process entry
11// to the first GTK presentation. It deliberately rejects unknown, duplicate,
12// and out-of-order milestones so collected cold-start measurements remain
13// truthful.
14type startupTrace struct {
15	mu               sync.Mutex
16	now              func() time.Time
17	t0               time.Time
18	milestones       []startupMilestone
19	logger           *zerolog.Logger
20	buffered         []startupMilestone
21	backend          string
22	incompleteReason string
23	summaryEmitted   bool
24}
25
26type startupMilestone struct {
27	Name    string
28	Elapsed time.Duration
29	Delta   time.Duration
30}
31
32var startupMilestoneOrder = []string{
33	"process_entry",
34	"config_complete",
35	"cef_library_load_begin",
36	"cef_initialized",
37	"browser_create_requested",
38	"first_accelerated_paint_received",
39	"first_dmabuf_texture_swap",
40	"first_gtk_presentation",
41}
42
43var processStartupTrace struct {
44	sync.Mutex
45	trace *startupTrace
46}
47
48func newStartupTrace(now func() time.Time, processEntry time.Time) *startupTrace {
49	return &startupTrace{
50		now:        now,
51		t0:         processEntry,
52		milestones: make([]startupMilestone, 0, len(startupMilestoneOrder)),
53		buffered:   make([]startupMilestone, 0, len(startupMilestoneOrder)),
54	}
55}
56
57// ActivateStartupTrace starts the CEF-only first-presentation trace after GUI
58// engine selection. The supplied timestamps are captured by the application at
59// process entry and configuration completion, before CEF initialization.
60func ActivateStartupTrace(processEntry, configComplete time.Time, logger *zerolog.Logger) {
61	if processEntry.IsZero() || configComplete.IsZero() {
62		return
63	}
64
65	processStartupTrace.Lock()
66	defer processStartupTrace.Unlock()
67	if processStartupTrace.trace != nil {
68		return
69	}
70
71	trace := newStartupTrace(time.Now, processEntry)
72	if !trace.markAt("process_entry", processEntry) || !trace.markAt("config_complete", configComplete) {
73		return
74	}
75	trace.SetLogger(logger)
76	processStartupTrace.trace = trace
77}
78
79func activeStartupTrace() *startupTrace {
80	processStartupTrace.Lock()
81	defer processStartupTrace.Unlock()
82	return processStartupTrace.trace
83}
84
85func (st *startupTrace) SetLogger(logger *zerolog.Logger) {
86	if st == nil || logger == nil {
87		return
88	}
89	st.mu.Lock()
90	defer st.mu.Unlock()
91	st.logger = logger
92	for _, milestone := range st.buffered {
93		st.emitMilestone(milestone)
94	}
95	st.buffered = nil
96	st.emitSummaryLocked()
97}
98
99func (st *startupTrace) SetBackend(backend string) {
100	if st == nil {
101		return
102	}
103	st.mu.Lock()
104	defer st.mu.Unlock()
105	st.backend = backend
106}
107
108func (st *startupTrace) SetIncompleteReason(reason string) {
109	if st == nil {
110		return
111	}
112	st.mu.Lock()
113	defer st.mu.Unlock()
114	if st.incompleteReason == "" {
115		st.incompleteReason = reason
116	}
117}
118
119// Mark accepts exactly the next non-presentation startup milestone.
120func (st *startupTrace) Mark(name string) bool {
121	if name == "first_gtk_presentation" {
122		return false
123	}
124	return st.markAt(name, st.currentTime())
125}
126
127// MarkGTKAfterPaint records the final milestone from the CEF-to-GTK bridge's
128// first frame-clock after-paint callback.
129func (st *startupTrace) MarkGTKAfterPaint() bool {
130	return st.markAt("first_gtk_presentation", st.currentTime())
131}
132
133func (st *startupTrace) currentTime() time.Time {
134	if st == nil || st.now == nil {
135		return time.Time{}
136	}
137	return st.now()
138}
139
140func (st *startupTrace) markAt(name string, at time.Time) bool {
141	if st == nil || st.now == nil || at.IsZero() {
142		return false
143	}
144	st.mu.Lock()
145	defer st.mu.Unlock()
146	if at.Before(st.t0) || len(st.milestones) >= len(startupMilestoneOrder) || name != startupMilestoneOrder[len(st.milestones)] {
147		return false
148	}
149
150	milestone := startupMilestone{Name: name, Elapsed: at.Sub(st.t0)}
151	previousMilliseconds := int64(0)
152	if len(st.milestones) > 0 {
153		previousMilliseconds = st.milestones[len(st.milestones)-1].Elapsed.Milliseconds()
154	}
155	milestone.Delta = time.Duration(milestone.Elapsed.Milliseconds()-previousMilliseconds) * time.Millisecond
156	st.milestones = append(st.milestones, milestone)
157	if st.logger == nil {
158		st.buffered = append(st.buffered, milestone)
159	} else {
160		st.emitMilestone(milestone)
161	}
162	st.emitSummaryLocked()
163	return true
164}
165
166func (st *startupTrace) emitMilestone(m startupMilestone) {
167	if st.logger == nil {
168		return
169	}
170	st.logger.Debug().
171		Str("milestone", m.Name).
172		Int64("t_ms", m.Elapsed.Milliseconds()).
173		Int64("delta_ms", m.Delta.Milliseconds()).
174		Msg("startup_trace: milestone")
175}
176
177func (st *startupTrace) emitSummaryLocked() {
178	if st.summaryEmitted || st.logger == nil || len(st.milestones) != len(startupMilestoneOrder) {
179		return
180	}
181	st.summaryEmitted = true
182	st.logger.Info().
183		Str("backend", st.backend).
184		Str("incomplete_reason", st.incompleteReason).
185		Int64("total_ms", st.milestones[len(st.milestones)-1].Elapsed.Milliseconds()).
186		Interface("milestones", st.milestones).
187		Msg("startup_trace: first presentation")
188}
189
190func (st *startupTrace) Enabled() bool { return st != nil && st.now != nil }
191
192func (st *startupTrace) TotalElapsed() time.Duration {
193	if st == nil || st.now == nil {
194		return 0
195	}
196	st.mu.Lock()
197	defer st.mu.Unlock()
198	return st.now().Sub(st.t0)
199}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close