v0.33.1 9577fee

profile.go RawHistoryFolder

1package cef
2
3import (
4	"encoding/json"
5	"errors"
6	"fmt"
7	"os"
8	"path/filepath"
9	"strconv"
10	"strings"
11	"sync"
12	"time"
13
14	cef2gtk "github.com/bnema/purego-cef2gtk"
15
16	"github.com/bnema/dumber/internal/logging"
17)
18
19const (
20	cef2gtkProfileEnv         = "DUMBER_CEF2GTK_PROFILE"
21	cef2gtkProfileIntervalEnv = "DUMBER_CEF2GTK_PROFILE_INTERVAL"
22	cef2gtkProfileOutputEnv   = "DUMBER_CEF2GTK_PROFILE_OUTPUT"
23)
24
25var errProfileWriterClosed = errors.New("cef2gtk profile writer is closed")
26
27type lockedProfileWriter struct {
28	mu   sync.Mutex
29	file *os.File
30}
31
32func (w *lockedProfileWriter) Write(p []byte) (int, error) {
33	if w == nil {
34		return 0, errProfileWriterClosed
35	}
36	w.mu.Lock()
37	defer w.mu.Unlock()
38	if w.file == nil {
39		return 0, errProfileWriterClosed
40	}
41	return w.file.Write(p)
42}
43
44func (w *lockedProfileWriter) Close() error {
45	if w == nil {
46		return nil
47	}
48	w.mu.Lock()
49	defer w.mu.Unlock()
50	if w.file == nil {
51		return nil
52	}
53	err := w.file.Close()
54	w.file = nil
55	return err
56}
57
58func cef2gtkProfileEnabled() bool {
59	v := strings.TrimSpace(strings.ToLower(os.Getenv(cef2gtkProfileEnv)))
60	if v == "" {
61		return false
62	}
63	if b, err := strconv.ParseBool(v); err == nil {
64		return b
65	}
66	switch v {
67	case "1", "yes", "on", "enabled":
68		return true
69	default:
70		return false
71	}
72}
73
74func cef2gtkProfileInterval() time.Duration {
75	v := strings.TrimSpace(os.Getenv(cef2gtkProfileIntervalEnv))
76	if v == "" {
77		return time.Second
78	}
79	d, err := time.ParseDuration(v)
80	if err != nil || d <= 0 {
81		return time.Second
82	}
83	return d
84}
85
86type cef2gtkProfileRecord struct {
87	WebViewID uint64                  `json:"webview_id"`
88	Snapshot  cef2gtk.ProfileSnapshot `json:"snapshot"`
89}
90
91const (
92	profileLogDirPerm  = 0o755
93	profileLogFilePerm = 0o644
94)
95
96func (e *Engine) cef2gtkProfileOptions(wv *WebView) cef2gtk.ProfileOptions {
97	if e == nil || wv == nil || !cef2gtkProfileEnabled() {
98		return cef2gtk.ProfileOptions{}
99	}
100	path := e.cef2gtkProfilePath()
101	if path == "" {
102		return cef2gtk.ProfileOptions{}
103	}
104	if err := os.MkdirAll(filepath.Dir(path), profileLogDirPerm); err != nil {
105		logging.FromContext(e.ctx).Warn().Err(err).Str("path", path).Msg("cef2gtk: failed to create profile log directory")
106		return cef2gtk.ProfileOptions{}
107	}
108	file, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, profileLogFilePerm)
109	if err != nil {
110		logging.FromContext(e.ctx).Warn().Err(err).Str("path", path).Msg("cef2gtk: failed to open profile log")
111		return cef2gtk.ProfileOptions{}
112	}
113	writer := &lockedProfileWriter{file: file}
114	wv.profileCleanup = func() { _ = writer.Close() }
115	logging.FromContext(e.ctx).Info().Str("profile_log", path).Uint64("webview_id", uint64(wv.id)).Msg("cef2gtk profiling enabled")
116	return cef2gtk.ProfileOptions{
117		Enabled:  true,
118		Interval: cef2gtkProfileInterval(),
119		OnSnapshot: func(s cef2gtk.ProfileSnapshot) {
120			wv.recordCEF2GTKProfileSnapshot(s)
121			record := cef2gtkProfileRecord{WebViewID: uint64(wv.id), Snapshot: s}
122			if b, err := json.Marshal(record); err == nil {
123				_, _ = writer.Write(append(b, '\n'))
124			}
125			logging.FromContext(e.ctx).Debug().
126				Uint64("webview_id", uint64(wv.id)).
127				Uint64("frames_received", s.FramesReceived).
128				Uint64("frames_rendered", s.FramesRendered).
129				Uint32("gc_delta", s.GC.NumGCDelta).
130				Msg("cef2gtk profile snapshot")
131		},
132	}
133}
134
135func (wv *WebView) recordCEF2GTKProfileSnapshot(snapshot cef2gtk.ProfileSnapshot) {
136	if wv == nil {
137		return
138	}
139	wv.profileMu.Lock()
140	wv.previousProfileSnapshot = wv.latestProfileSnapshot
141	wv.latestProfileSnapshot = snapshot
142	wv.latestProfileSnapshotAt = snapshot.Time
143	wv.profileMu.Unlock()
144}
145
146func (wv *WebView) latestCEF2GTKProfileSnapshot(now time.Time) (cef2gtk.ProfileSnapshot, time.Duration, bool) {
147	if wv == nil {
148		return cef2gtk.ProfileSnapshot{}, 0, false
149	}
150	wv.profileMu.Lock()
151	snapshot := wv.latestProfileSnapshot
152	snapshotAt := wv.latestProfileSnapshotAt
153	wv.profileMu.Unlock()
154	if snapshotAt.IsZero() {
155		return cef2gtk.ProfileSnapshot{}, 0, false
156	}
157	age := max(now.Sub(snapshotAt), 0)
158	return snapshot, age, true
159}
160
161func (e *Engine) cef2gtkProfilePath() string {
162	if explicit := strings.TrimSpace(os.Getenv(cef2gtkProfileOutputEnv)); explicit != "" {
163		return explicit
164	}
165	metadata, ok := logging.SessionMetadataFromContext(e.ctx)
166	if !ok || metadata.ID == "" {
167		if e.profileLogDir == "" {
168			return ""
169		}
170		return filepath.Join(e.profileLogDir, "cef2gtk_profile.jsonl")
171	}
172	logDir := e.profileLogDir
173	if metadata.LogPath != "" {
174		logDir = filepath.Dir(metadata.LogPath)
175	}
176	if logDir == "" {
177		return ""
178	}
179	return filepath.Join(logDir, fmt.Sprintf("session_%s_cef2gtk_profile.jsonl", metadata.ID))
180}

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