Skip to content

Commit 3ec7b80

Browse files
perf(Sky): Batch performance marks in PostHogBridge to prevent rate limiting
Refactor PostHogBridge.ts to collect performance marks into a buffer and flush as a single event every 2 seconds, instead of sending individual events for each mark. This prevents PostHog rate limiting during editor boot sequences with many telemetry marks. Changes: - Add MarkBuffer array and FlushMarks function to batch marks - Send "land:boot_marks" event with mark array instead of individual captures - Add visibilitychange listener to flush remaining marks on page hide - Errors continue to be sent immediately (no batching) - Add "blob:" to connect-src CSP in index.astro for blob URL support in Electron webview
1 parent 41a6fe6 commit 3ec7b80

2 files changed

Lines changed: 48 additions & 9 deletions

File tree

Source/Workbench/Electron/PostHogBridge.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,33 @@ const Initialize = async (): Promise<void> => {
6666
const PH = await LoadPostHog();
6767
if (!PH) return;
6868

69-
// Capture performance marks as PostHog events
69+
// Batch performance marks to avoid rate limiting.
70+
// Collects marks for 2 seconds, then flushes as a single event.
71+
let MarkBuffer: Array<{
72+
Name: string;
73+
Category: string;
74+
Action: string;
75+
TimestampMs: number;
76+
DurationMs: number;
77+
Detail: unknown;
78+
}> = [];
79+
let FlushTimer: ReturnType<typeof setTimeout> | null = null;
80+
81+
const FlushMarks = () => {
82+
FlushTimer = null;
83+
if (MarkBuffer.length === 0) return;
84+
85+
const Marks = MarkBuffer;
86+
MarkBuffer = [];
87+
88+
PH.capture("land:boot_marks", {
89+
marks: Marks,
90+
mark_count: Marks.length,
91+
first_mark_ms: Marks[0]?.TimestampMs,
92+
last_mark_ms: Marks[Marks.length - 1]?.TimestampMs,
93+
});
94+
};
95+
7096
const Observer = new PerformanceObserver((List) => {
7197
for (const Entry of List.getEntries()) {
7298
if (!Entry.name.startsWith("land:")) continue;
@@ -77,32 +103,44 @@ const Initialize = async (): Promise<void> => {
77103
const Action = Parts.slice(2).join(":");
78104

79105
if (IsError) {
80-
// Error tracking — capture as exception
106+
// Errors always sent immediately
81107
PH.captureException(new Error(Entry.name), {
82108
$exception_type: `land:${Category}`,
83109
$exception_message: Action,
84110
timestamp_ms: performance.timeOrigin + Entry.startTime,
85111
detail: (Entry as any).detail,
86112
});
87113
} else {
88-
// Regular event
89-
PH.capture(`land:${Category}`, {
90-
action: Action,
91-
mark_name: Entry.name,
92-
timestamp_ms: performance.timeOrigin + Entry.startTime,
93-
duration_ms:
114+
// Buffer regular marks
115+
MarkBuffer.push({
116+
Name: Entry.name,
117+
Category,
118+
Action,
119+
TimestampMs: performance.timeOrigin + Entry.startTime,
120+
DurationMs:
94121
Entry.entryType === "measure"
95122
? (Entry as PerformanceMeasure).duration
96123
: 0,
97-
detail: (Entry as any).detail,
124+
Detail: (Entry as any).detail,
98125
});
126+
127+
if (!FlushTimer) {
128+
FlushTimer = setTimeout(FlushMarks, 2000);
129+
}
99130
}
100131
}
101132
});
102133

103134
Observer.observe({ type: "mark", buffered: true });
104135
Observer.observe({ type: "measure", buffered: true });
105136

137+
// Flush remaining marks on page hide
138+
addEventListener("visibilitychange", () => {
139+
if (document.visibilityState === "hidden" && MarkBuffer.length > 0) {
140+
FlushMarks();
141+
}
142+
});
143+
106144
// Capture unhandled errors
107145
window.addEventListener("error", (Event) => {
108146
if (!Event.message || Event.message === "Script error.") return;

Source/pages/index.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const Worker = `/Worker.js?BASE_REMOTE=${encodeURIComponent(Astro.url.origin)}`;
107107
],
108108
"connect-src": [
109109
"'self'",
110+
"blob:",
110111
"ipc:",
111112
"vscode-file:",
112113
"vscode-file://vscode-app",

0 commit comments

Comments
 (0)