Skip to content

[gp-code] measure all sessions vs errored sessions #428

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 101 additions & 3 deletions src/vs/gitpod/browser/workbench/workbench-dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,104 @@
<!DOCTYPE html>
<html>
<head>
<script>
function getMetricsUrl() {
const gitpodHost = "{{GITPOD_HOST}}";
if (!gitpodHost) {
return '';
}
return `https://ide.${gitpodHost}/metrics-api`;
}
const workspaceId = 'fake-workspace-id'
const ideMetricsUrl = getMetricsUrl()
async function gitpodMetricsAddCounter(metricsName, labels, value) {
try {
if (!ideMetricsUrl) {
return false;
}
const url = `${ideMetricsUrl}/metrics/counter/add/${metricsName}`;
const params = { value, labels };
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(params),
credentials: 'omit',
});
if (!response.ok) {
const data = await response.json(); // { code: number; message: string; }
console.error(`Cannot report metrics with addCounter: ${response.status} ${response.statusText}`, data);
return false;
}
return true;
} catch (err) {
console.error('Cannot report metrics with addCounter, error:', err);
return false;
}
}

async function gitpodMetricsReportError(error, properties) {
try {
if (!ideMetricsUrl) {
return false;
}
const p = Object.assign({}, properties)
p.error_name = error.name
p.error_message = error.message
const url = `${ideMetricsUrl}/reportError`;
// TODO: Add quality as a params
const params = {
errorStack: error.stack ?? String(error),
component: "vscode-workbench",
version: "{{VERSION}}",
workspaceId: workspaceId,
properties: p,
};
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(params),
credentials: 'omit',
});
if (!response.ok) {
const data = await response.json();
console.error(`Cannot report error: ${response.status} ${response.statusText}`, data);
return false;
}
return true;
} catch (err) {
console.error("Cannot report error, error:", err);
return false;
}
}

// sum(rate(gitpod_vscode_web_load_total{status='failed'}[2m]))/sum(rate(gitpod_vscode_web_load_total{status='loading'}[2m]))
const gitpodVSCodeWebLoadTotal = 'gitpod_vscode_web_load_total';
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'loading' });
let hasVscodeWebLoadFailed = false;
const onVsCodeWorkbenchError = (event) => {
if (!hasVscodeWebLoadFailed) {
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'failed' });
hasVscodeWebLoadFailed = true;
}

if (typeof event?.target?.getAttribute !== 'function') {
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total');
return;
}
const labels = {};

// We take a look at what is the resource that was attempted to load;
const resourceSource = event.target.getAttribute('src') || event.target.getAttribute('href');

// If the event has a `target`, it means that it wasn't a script error
if (resourceSource) {
labels['resource'] = 'vscode-web-workbench';
labels['error'] = 'LoadError';
gitpodMetricsReportError(new Error("LoadError"), { resource: labels['resource'], url: resourceSource })
} else {
labels['error'] = 'Unknown';
}
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total', labels);
};
</script>
<script>
performance.mark('code/didStartRenderer')
</script>
Expand Down Expand Up @@ -34,8 +132,8 @@
</body>

<!-- Startup (do not modify order of script tags!) -->
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/loader.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
<script src="{{WORKBENCH_WEB_BASE_URL}}/out/vs/webPackagePaths.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
<script>
const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString();
Object.keys(self.webPackagePaths).map(function (key, index) {
Expand Down Expand Up @@ -65,6 +163,6 @@
performance.mark('code/willLoadWorkbenchMain');
</script>
<script>
require(['vs/gitpod/browser/workbench/workbench'], function() {});
require(['vs/gitpod/browser/workbench/workbench'], () => {});
</script>
</html>
111 changes: 81 additions & 30 deletions src/vs/gitpod/browser/workbench/workbench.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,37 @@
<html>
<head>
<script>
async function gitpodMetricsAddCounter(metricsName, labels, value) {
function getMetricsUrl() {
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}))';
// this pattern matches URL prefixes of workspaces
const workspaceUrlPrefixRegex = RegExp(`^([0-9]{4,6}-)?${baseWorkspaceIDRegex}\\.`);
const url = new URL(window.location.href);
url.search = '';
url.hash = '';
url.pathname = '';
if (url.host.match(workspaceUrlPrefixRegex)) {
url.host = url.host.split('.').splice(2).join('.');
} else {
return;
}
const hostSegments = url.host.split('.');
if (hostSegments[0] !== 'ide') {
url.host = 'ide.' + url.host;
}
url.pathname = '/metrics-api';
return url.toString();
let workspaceId = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mustard-mh we should be careful with creating global variables, we had gitpodMetricsAddCounter on purpose to avoid polluting global scope

let ideMetricsUrl = null;
(() => {
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}))';
// this pattern matches URL prefixes of workspaces
const workspaceUrlPrefixRegex = RegExp(`^([0-9]{4,6}-)?${baseWorkspaceIDRegex}\\.`);
const url = new URL(window.location.href);
url.search = '';
url.hash = '';
url.pathname = '';
const result = url.host.match(workspaceUrlPrefixRegex);
if (result) {
workspaceId = result[4]
url.host = url.host.split('.').splice(2).join('.');
} else {
return;
}
const hostSegments = url.host.split('.');
if (hostSegments[0] !== 'ide') {
url.host = 'ide.' + url.host;
}
url.pathname = '/metrics-api';
ideMetricsUrl = url.toString()
})();

async function gitpodMetricsAddCounter(metricsName, labels, value) {
try {
const metricsUrl = getMetricsUrl();
if (!metricsUrl) {
if (!ideMetricsUrl) {
return false;
}
const url = `${metricsUrl}/metrics/counter/add/${metricsName}`;
const url = `${ideMetricsUrl}/metrics/counter/add/${metricsName}`;
const params = { value, labels };
const response = await fetch(url, {
method: 'POST',
Expand All @@ -48,7 +52,50 @@
}
}

async function gitpodMetricsReportError(error, properties) {
try {
if (!ideMetricsUrl) {
return false;
}
const p = Object.assign({}, properties)
p.error_name = error.name
p.error_message = error.message
const url = `${ideMetricsUrl}/reportError`;
// TODO: Add quality as a params
const params = {
errorStack: error.stack ?? String(error),
component: "vscode-workbench",
version: "{{VERSION}}",
workspaceId: workspaceId,
properties: p,
};
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(params),
credentials: 'omit',
});
if (!response.ok) {
const data = await response.json();
console.error(`Cannot report error: ${response.status} ${response.statusText}`, data);
return false;
}
return true;
} catch (err) {
console.error("Cannot report error, error:", err);
return false;
}
}

// sum(rate(gitpod_vscode_web_load_total{status='failed'}[2m]))/sum(rate(gitpod_vscode_web_load_total{status='loading'}[2m]))
const gitpodVSCodeWebLoadTotal = 'gitpod_vscode_web_load_total';
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'loading' });
let hasVscodeWebLoadFailed = false;
const onVsCodeWorkbenchError = (event) => {
if (!hasVscodeWebLoadFailed) {
gitpodMetricsAddCounter(gitpodVSCodeWebLoadTotal, { status: 'failed' });
hasVscodeWebLoadFailed = true;
}

if (typeof event?.target?.getAttribute !== 'function') {
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total');
return;
Expand All @@ -62,12 +109,16 @@
if (resourceSource) {
labels['resource'] = 'vscode-web-workbench';
labels['error'] = 'LoadError';
gitpodMetricsReportError(new Error("LoadError"), { resource: labels['resource'], url: resourceSource })
} else {
labels['error'] = 'Unknown';
}
gitpodMetricsAddCounter('gitpod_supervisor_frontend_error_total', labels);
};

// TODO collect errors, we can capture resourceSource, error if possible with stack, message, name, and window URL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mustard-mh it is done?

Copy link

@mustard-mh mustard-mh Sep 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll clean some comments up by push commit to gp-code/main, and improve this #428 (comment)

};
</script>
<script>
performance.mark('code/didStartRenderer');
</script>
<meta charset="utf-8" />
Expand Down Expand Up @@ -101,9 +152,9 @@
</body>

<!-- Startup (do not modify order of script tags!) -->
<script type="text/javascript" src="/_supervisor/frontend/main.js" onerror="onVsCodeWorkbenchError(event)" charset="utf-8"></script>
<script src="./static/out/vs/loader.js" onerror="onVsCodeWorkbenchError(event)" ></script>
<script src="./static/out/vs/webPackagePaths.js" onerror="onVsCodeWorkbenchError(event)" ></script>
<script type="text/javascript" src="/_supervisor/frontend/main.js" onerror="onVsCodeWorkbenchError(event)" charset="utf-8" crossorigin="anonymous"></script>
Copy link
Member

@akosyakov akosyakov Sep 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mustard-mh crossorigin="anonymous" it does not seem you are making use of it, i.e. idea was to add it if we want to collect information about script errors, onVsCodeWorkbenchError should report them too then

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add this, supervisor frontend can use it too

<script src="./static/out/vs/loader.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
<script src="./static/out/vs/webPackagePaths.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
<script>
Object.keys(self.webPackagePaths).map(function (key, index) {
self.webPackagePaths[key] = `${window.location.origin}/static/node_modules/${key}/${self.webPackagePaths[key]}`;
Expand All @@ -127,7 +178,7 @@
<script>
performance.mark('code/willLoadWorkbenchMain');
</script>
<script src="./static/out/vs/workbench/workbench.web.main.nls.js" onerror="onVsCodeWorkbenchError(event)"></script>
<script src="./static/out/vs/workbench/workbench.web.main.js" onerror="onVsCodeWorkbenchError(event)"></script>
<script src="./static/out/vs/gitpod/browser/workbench/workbench.js" onerror="onVsCodeWorkbenchError(event)"></script>
<script src="./static/out/vs/workbench/workbench.web.main.nls.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
<script src="./static/out/vs/workbench/workbench.web.main.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
<script src="./static/out/vs/gitpod/browser/workbench/workbench.js" onerror="onVsCodeWorkbenchError(event)" crossorigin="anonymous"></script>
</html>
2 changes: 2 additions & 0 deletions src/vs/gitpod/common/insightsHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export enum SenderKind {
Node = 2
}

// TODO map 'UnhandledError' to our error and report it both only for window and remote-server

export function mapMetrics(source: 'window' | 'remote-server', eventName: string, data: any): IDEMetric[] | undefined {
const maybeMetrics = doMapMetrics(source, eventName, data);
return maybeMetrics instanceof Array ? maybeMetrics : typeof maybeMetrics === 'object' ? [maybeMetrics] : undefined;
Expand Down
6 changes: 5 additions & 1 deletion src/vs/server/node/webClientServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ export class WebClientServer {

const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl;
const values: { [key: string]: string } = {
VERSION: this._productService.version,
GITPOD_HOST: this._productService.gitpodPreview?.host || '',
WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration),
WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '',
WORKBENCH_WEB_BASE_URL: this._staticRoute,
Expand Down Expand Up @@ -353,9 +355,11 @@ export class WebClientServer {
'manifest-src \'self\';'
].join(' ');

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';`;

const headers: http.OutgoingHttpHeaders = {
'Content-Type': 'text/html',
'Content-Security-Policy': cspDirectives
'Content-Security-Policy': this._environmentService.isBuilt ? cspDirectives : allowAllCSP
Copy link

@mustard-mh mustard-mh Sep 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this to make workbench.html onerror works in dev mode. allowAllCSP value comes from stackoverflow

};
if (this._connectionToken.type !== ServerConnectionTokenType.None) {
// At this point we know the client has a valid cookie
Expand Down