Skip to content

Commit 8ccf7ff

Browse files
akosyakovmustard-mh
authored andcommitted
track loading and failed windows
1 parent 8a04b4e commit 8ccf7ff

File tree

4 files changed

+189
-34
lines changed

4 files changed

+189
-34
lines changed

src/vs/gitpod/browser/workbench/workbench-dev.html

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,104 @@
22
<!DOCTYPE html>
33
<html>
44
<head>
5+
<script>
6+
function getMetricsUrl() {
7+
const gitpodHost = "{{GITPOD_HOST}}";
8+
if (!gitpodHost) {
9+
return '';
10+
}
11+
return `https://ide.${gitpodHost}/metrics-api`;
12+
}
13+
const workspaceId = 'fake-workspace-id'
14+
const ideMetricsUrl = getMetricsUrl()
15+
async function gitpodMetricsAddCounter(metricsName, labels, value) {
16+
try {
17+
if (!ideMetricsUrl) {
18+
return false;
19+
}
20+
const url = `${ideMetricsUrl}/metrics/counter/add/${metricsName}`;
21+
const params = { value, labels };
22+
const response = await fetch(url, {
23+
method: 'POST',
24+
body: JSON.stringify(params),
25+
credentials: 'omit',
26+
});
27+
if (!response.ok) {
28+
const data = await response.json(); // { code: number; message: string; }
29+
console.error(`Cannot report metrics with addCounter: ${response.status} ${response.statusText}`, data);
30+
return false;
31+
}
32+
return true;
33+
} catch (err) {
34+
console.error('Cannot report metrics with addCounter, error:', err);
35+
return false;
36+
}
37+
}
38+
39+
async function gitpodMetricsReportError(error, properties) {
40+
try {
41+
if (!ideMetricsUrl) {
42+
return false;
43+
}
44+
const p = Object.assign({}, properties)
45+
p.error_name = error.name
46+
p.error_message = error.message
47+
const url = `${ideMetricsUrl}/reportError`;
48+
// TODO: Add quality as a params
49+
const params = {
50+
errorStack: error.stack ?? String(error),
51+
component: "vscode-workbench",
52+
version: "{{VERSION}}",
53+
workspaceId: workspaceId,
54+
properties: p,
55+
};
56+
const response = await fetch(url, {
57+
method: 'POST',
58+
body: JSON.stringify(params),
59+
credentials: 'omit',
60+
});
61+
if (!response.ok) {
62+
const data = await response.json();
63+
console.error(`Cannot report error: ${response.status} ${response.statusText}`, data);
64+
return false;
65+
}
66+
return true;
67+
} catch (err) {
68+
console.error("Cannot report error, error:", err);
69+
return false;
70+
}
71+
}
72+
73+
// sum(rate(gitpod_vscode_web_load_total{status='failed'}[2m]))/sum(rate(gitpod_vscode_web_load_total{status='loading'}[2m]))
74+
const gitpodVSCodeWebLoadTotal = 'gitpod_vscode_web_load_total';
75+
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'loading' });
76+
let hasVscodeWebLoadFailed = false;
77+
const onVsCodeWorkbenchError = (event) => {
78+
if (!hasVscodeWebLoadFailed) {
79+
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'failed' });
80+
hasVscodeWebLoadFailed = true;
81+
}
82+
83+
if (typeof event?.target?.getAttribute !== 'function') {
84+
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total');
85+
return;
86+
}
87+
const labels = {};
88+
89+
// We take a look at what is the resource that was attempted to load;
90+
const resourceSource = event.target.getAttribute('src') || event.target.getAttribute('href');
91+
92+
// If the event has a `target`, it means that it wasn't a script error
93+
if (resourceSource) {
94+
labels['resource'] = 'vscode-web-workbench';
95+
labels['error'] = 'LoadError';
96+
gitpodMetricsReportError(new Error("LoadError"), { resource: labels['resource'], url: resourceSource })
97+
} else {
98+
labels['error'] = 'Unknown';
99+
}
100+
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total', labels);
101+
};
102+
</script>
5103
<script>
6104
performance.mark('code/didStartRenderer')
7105
</script>
@@ -34,8 +132,8 @@
34132
</body>
35133

36134
<!-- Startup (do not modify order of script tags!) -->
37-
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
38-
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js"></script>
135+
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
136+
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
39137
<script>
40138
const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString();
41139
Object.keys(self.webPackagePaths).map(function (key, index) {
@@ -65,6 +163,6 @@
65163
performance.mark('code/willLoadWorkbenchMain');
66164
</script>
67165
<script>
68-
require(['vs/gitpod/browser/workbench/workbench'], function() {});
166+
require(['vs/gitpod/browser/workbench/workbench'], () => {});
69167
</script>
70168
</html>

src/vs/gitpod/browser/workbench/workbench.html

Lines changed: 81 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,33 +3,37 @@
33
<html>
44
<head>
55
<script>
6-
async function gitpodMetricsAddCounter(metricsName, labels, value) {
7-
function getMetricsUrl() {
8-
const baseWorkspaceIDRegex = '(([a-f][0-9a-f]{7}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|([0-9a-z]{2,16}-[0-9a-z]{2,16}-[0-9a-z]{8,11}))';
9-
// this pattern matches URL prefixes of workspaces
10-
const workspaceUrlPrefixRegex = RegExp(`^([0-9]{4,6}-)?${baseWorkspaceIDRegex}\\.`);
11-
const url = new URL(window.location.href);
12-
url.search = '';
13-
url.hash = '';
14-
url.pathname = '';
15-
if (url.host.match(workspaceUrlPrefixRegex)) {
16-
url.host = url.host.split('.').splice(2).join('.');
17-
} else {
18-
return;
19-
}
20-
const hostSegments = url.host.split('.');
21-
if (hostSegments[0] !== 'ide') {
22-
url.host = 'ide.' + url.host;
23-
}
24-
url.pathname = '/metrics-api';
25-
return url.toString();
6+
let workspaceId = null;
7+
let ideMetricsUrl = null;
8+
(() => {
9+
const baseWorkspaceIDRegex = '(([a-f][0-9a-f]{7}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|([0-9a-z]{2,16}-[0-9a-z]{2,16}-[0-9a-z]{8,11}))';
10+
// this pattern matches URL prefixes of workspaces
11+
const workspaceUrlPrefixRegex = RegExp(`^([0-9]{4,6}-)?${baseWorkspaceIDRegex}\\.`);
12+
const url = new URL(window.location.href);
13+
url.search = '';
14+
url.hash = '';
15+
url.pathname = '';
16+
const result = url.host.match(workspaceUrlPrefixRegex);
17+
if (result) {
18+
workspaceId = result[4]
19+
url.host = url.host.split('.').splice(2).join('.');
20+
} else {
21+
return;
22+
}
23+
const hostSegments = url.host.split('.');
24+
if (hostSegments[0] !== 'ide') {
25+
url.host = 'ide.' + url.host;
2626
}
27+
url.pathname = '/metrics-api';
28+
ideMetricsUrl = url.toString()
29+
})();
30+
31+
async function gitpodMetricsAddCounter(metricsName, labels, value) {
2732
try {
28-
const metricsUrl = getMetricsUrl();
29-
if (!metricsUrl) {
33+
if (!ideMetricsUrl) {
3034
return false;
3135
}
32-
const url = `${metricsUrl}/metrics/counter/add/${metricsName}`;
36+
const url = `${ideMetricsUrl}/metrics/counter/add/${metricsName}`;
3337
const params = { value, labels };
3438
const response = await fetch(url, {
3539
method: 'POST',
@@ -48,7 +52,50 @@
4852
}
4953
}
5054

55+
async function gitpodMetricsReportError(error, properties) {
56+
try {
57+
if (!ideMetricsUrl) {
58+
return false;
59+
}
60+
const p = Object.assign({}, properties)
61+
p.error_name = error.name
62+
p.error_message = error.message
63+
const url = `${ideMetricsUrl}/reportError`;
64+
// TODO: Add quality as a params
65+
const params = {
66+
errorStack: error.stack ?? String(error),
67+
component: "vscode-workbench",
68+
version: "{{VERSION}}",
69+
workspaceId: workspaceId,
70+
properties: p,
71+
};
72+
const response = await fetch(url, {
73+
method: 'POST',
74+
body: JSON.stringify(params),
75+
credentials: 'omit',
76+
});
77+
if (!response.ok) {
78+
const data = await response.json();
79+
console.error(`Cannot report error: ${response.status} ${response.statusText}`, data);
80+
return false;
81+
}
82+
return true;
83+
} catch (err) {
84+
console.error("Cannot report error, error:", err);
85+
return false;
86+
}
87+
}
88+
89+
// sum(rate(gitpod_vscode_web_load_total{status='failed'}[2m]))/sum(rate(gitpod_vscode_web_load_total{status='loading'}[2m]))
90+
const gitpodVSCodeWebLoadTotal = 'gitpod_vscode_web_load_total';
91+
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'loading' });
92+
let hasVscodeWebLoadFailed = false;
5193
const onVsCodeWorkbenchError = (event) => {
94+
if (!hasVscodeWebLoadFailed) {
95+
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'failed' });
96+
hasVscodeWebLoadFailed = true;
97+
}
98+
5299
if (typeof event?.target?.getAttribute !== 'function') {
53100
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total');
54101
return;
@@ -62,12 +109,16 @@
62109
if (resourceSource) {
63110
labels['resource'] = 'vscode-web-workbench';
64111
labels['error'] = 'LoadError';
112+
gitpodMetricsReportError(new Error("LoadError"), { resource: labels['resource'], url: resourceSource })
65113
} else {
66114
labels['error'] = 'Unknown';
67115
}
68116
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total', labels);
69-
};
70117

118+
// TODO collect errors, we can capture resourceSource, error if possible with stack, message, name, and window URL
119+
};
120+
</script>
121+
<script>
71122
performance.mark('code/didStartRenderer');
72123
</script>
73124
<meta charset="utf-8" />
@@ -101,9 +152,9 @@
101152
</body>
102153

103154
<!-- Startup (do not modify order of script tags!) -->
104-
<script type="text/javascript" src="/_supervisor/frontend/main.js" onerror="onVsCodeWorkbenchError(event)" charset="utf-8"></script>
105-
<script src="./static/out/vs/loader.js" onerror="onVsCodeWorkbenchError(event)" ></script>
106-
<script src="./static/out/vs/webPackagePaths.js" onerror="onVsCodeWorkbenchError(event)" ></script>
155+
<script type="text/javascript" src="/_supervisor/frontend/main.js" onerror="onVsCodeWorkbenchError(event)" charset="utf-8" crossorigin="anonymous"></script>
156+
<script src="./static/out/vs/loader.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
157+
<script src="./static/out/vs/webPackagePaths.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
107158
<script>
108159
Object.keys(self.webPackagePaths).map(function (key, index) {
109160
self.webPackagePaths[key] = `${window.location.origin}/static/node_modules/${key}/${self.webPackagePaths[key]}`;
@@ -127,7 +178,7 @@
127178
<script>
128179
performance.mark('code/willLoadWorkbenchMain');
129180
</script>
130-
<script src="./static/out/vs/workbench/workbench.web.main.nls.js" onerror="onVsCodeWorkbenchError(event)"></script>
131-
<script src="./static/out/vs/workbench/workbench.web.main.js" onerror="onVsCodeWorkbenchError(event)"></script>
132-
<script src="./static/out/vs/gitpod/browser/workbench/workbench.js" onerror="onVsCodeWorkbenchError(event)"></script>
181+
<script src="./static/out/vs/workbench/workbench.web.main.nls.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
182+
<script src="./static/out/vs/workbench/workbench.web.main.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
183+
<script src="./static/out/vs/gitpod/browser/workbench/workbench.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
133184
</html>

src/vs/gitpod/common/insightsHelper.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export enum SenderKind {
2828
Node = 2
2929
}
3030

31+
// TODO map 'UnhandledError' to our error and report it both only for window and remote-server
32+
3133
export function mapMetrics(source: 'window' | 'remote-server', eventName: string, data: any): IDEMetric[] | undefined {
3234
const maybeMetrics = doMapMetrics(source, eventName, data);
3335
return maybeMetrics instanceof Array ? maybeMetrics : typeof maybeMetrics === 'object' ? [maybeMetrics] : undefined;

src/vs/server/node/webClientServer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ export class WebClientServer {
323323

324324
const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
325325
const values: { [key: string]: string } = {
326+
VERSION: this._productService.version,
327+
GITPOD_HOST: this._productService.gitpodPreview?.host || '',
326328
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
327329
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
328330
WORKBENCH_WEB_BASE_URL: this._staticRoute,
@@ -353,9 +355,11 @@ export class WebClientServer {
353355
'manifest-src \'self\';'
354356
].join(' ');
355357

358+
const allowAllCSP = `default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';`;
359+
356360
const headers: http.OutgoingHttpHeaders = {
357361
'Content-Type': 'text/html',
358-
'Content-Security-Policy': cspDirectives
362+
'Content-Security-Policy': this._environmentService.isBuilt ? cspDirectives : allowAllCSP
359363
};
360364
if (this._connectionToken.type !== ServerConnectionTokenType.None) {
361365
// At this point we know the client has a valid cookie

0 commit comments

Comments
 (0)