Skip to content

Commit f3fac9c

Browse files
akosyakovjeanp413
authored andcommitted
extension installation metrics
1 parent 19344f4 commit f3fac9c

File tree

14 files changed

+452
-151
lines changed

14 files changed

+452
-151
lines changed

.vscode/launch.json

+8-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,14 @@
287287
"presentation": {
288288
"group": "0_vscode",
289289
"order": 2
290-
}
290+
},
291+
"env": {
292+
"VSCODE_DEV": "1",
293+
"NODE_ENV": "development"
294+
},
295+
"args": [
296+
"--without-connection-token"
297+
]
291298
},
292299
{
293300
"type": "node",

build/.webignore

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ xterm-addon-webgl/out/**
4545

4646
@gitpod/**
4747
!@gitpod/local-app-api-grpcweb/lib/localapp.js
48+
!@gitpod/ide-metrics-api-grpcweb/lib/index.js
4849

4950
browser-headers/**
5051
google-protobuf/**

doc/DEV.md

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## Observability
2+
3+
### Metrics defintion
4+
- Declare new metrics or update existing in https://github.com/gitpod-io/gitpod/blob/ad355c4d9abd858a44daf15f9bd6747976142911/install/installer/pkg/components/ide-metrics/configmap.go
5+
- Create a new branch and push, wait for https://werft.gitpod-dev.com/ to create a preview env.
6+
7+
### Collecting metrics
8+
- Convert VS Code telemetry to metrics in https://github.com/gitpod-io/openvscode-server/blob/63796b8c6eca9bcaf36b90ae1e96dae32638bab6/src/vs/gitpod/common/insightsHelper.ts#L35.
9+
10+
### Testing from sources
11+
- Add to product.json (don't commit!):
12+
```jsonc
13+
"gitpodPreview": {
14+
"host": "<host of preview env>",
15+
// optionally to log to stdout or browser console
16+
"log": {
17+
"metrics": true,
18+
"analytics": false,
19+
}
20+
}
21+
```
22+
- Restart VS Code Server and open VS Code preview page to trigger telemetry events.
23+
- In dev workspace for gitpod-io/gitpod run `./dev/preview/portforward-monitoring-satellite.sh -c harvester`
24+
- Navigate to a printed Grafana link, open Explorer view, select prometheus as a data source and query for metrics.
25+
26+
### Integration testing
27+
28+
- Commit changes in this repo.
29+
- Update codeCommit in WORKSPACE.yaml in gitpod-io/gitpod and push.
30+
- Wait for https://werft.gitpod-dev.com/ to update preview envs.
31+
- Test the complete integration.

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@
6262
},
6363
"dependencies": {
6464
"@gitpod/gitpod-protocol": "main",
65+
"@gitpod/ide-metrics-api-grpcweb": "ak-ext-metrics",
6566
"@gitpod/local-app-api-grpcweb": "main",
6667
"@gitpod/supervisor-api-grpc": "main",
68+
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
6769
"@microsoft/1ds-core-js": "^3.2.2",
6870
"@microsoft/1ds-post-js": "^3.2.2",
6971
"@parcel/watcher": "2.0.5",

remote/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
"private": true,
55
"dependencies": {
66
"@gitpod/gitpod-protocol": "main",
7+
"@gitpod/ide-metrics-api-grpcweb": "ak-ext-metrics",
78
"@gitpod/supervisor-api-grpc": "main",
9+
"@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
810
"@microsoft/1ds-core-js": "^3.2.2",
911
"@microsoft/1ds-post-js": "^3.2.2",
1012
"@parcel/watcher": "2.0.5",

remote/web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"dependencies": {
66
"@gitpod/local-app-api-grpcweb": "main",
7+
"@gitpod/ide-metrics-api-grpcweb": "ak-ext-metrics",
78
"@microsoft/1ds-core-js": "^3.2.2",
89
"@microsoft/1ds-post-js": "^3.2.2",
910
"@vscode/iconv-lite-umd": "0.7.0",

remote/web/yarn.lock

+13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
# yarn lockfile v1
33

44

5+
"@gitpod/ide-metrics-api-grpcweb@ak-ext-metrics":
6+
version "0.0.1-ak-ext-metrics.4"
7+
resolved "https://registry.yarnpkg.com/@gitpod/ide-metrics-api-grpcweb/-/ide-metrics-api-grpcweb-0.0.1-ak-ext-metrics.4.tgz#9dacee7f13181e132fba9e4a5b97cb7f4b1739d2"
8+
integrity sha512-s1C4W5Q7nlgyaQGCKqG8gI1ZdzwsaFZW2k8VX5hJKIcpD5S60I7AJbP1wVxYhRqR93YW3++DHydSpbjBUVnQFA==
9+
dependencies:
10+
"@improbable-eng/grpc-web" "^0.14.0"
11+
google-protobuf "^3.19.1"
12+
513
"@gitpod/local-app-api-grpcweb@main":
614
version "0.1.5-main.1701"
715
resolved "https://registry.yarnpkg.com/@gitpod/local-app-api-grpcweb/-/local-app-api-grpcweb-0.1.5-main.1701.tgz#3f4f4203c4532b098d697c65799095c3d4add9d4"
@@ -93,6 +101,11 @@ google-protobuf@^3.17.0:
93101
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.19.0.tgz#97f474323c92f19fd6737af1bb792e396991e0b8"
94102
integrity sha512-qXGAiv3OOlaJXJNeKOBKxbBAwjsxzhx+12ZdKOkZTsqsRkyiQRmr/nBkAkqnuQ8cmA9X5NVXvObQTpHVnXE2DQ==
95103

104+
google-protobuf@^3.19.1:
105+
version "3.21.0"
106+
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.21.0.tgz#8dfa3fca16218618d373d414d3c1139e28034d6e"
107+
integrity sha512-byR7MBTK4tZ5PZEb+u5ZTzpt4SfrTxv5682MjPlHN16XeqgZE2/8HOIWeiXe8JKnT9OVbtBGhbq8mtvkK8cd5g==
108+
96109
97110
version "3.0.0"
98111
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882"

remote/yarn.lock

+25
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@
3636
vscode-ws-jsonrpc "^0.2.0"
3737
ws "^7.4.6"
3838

39+
"@gitpod/ide-metrics-api-grpcweb@ak-ext-metrics":
40+
version "0.0.1-ak-ext-metrics.4"
41+
resolved "https://registry.yarnpkg.com/@gitpod/ide-metrics-api-grpcweb/-/ide-metrics-api-grpcweb-0.0.1-ak-ext-metrics.4.tgz#9dacee7f13181e132fba9e4a5b97cb7f4b1739d2"
42+
integrity sha512-s1C4W5Q7nlgyaQGCKqG8gI1ZdzwsaFZW2k8VX5hJKIcpD5S60I7AJbP1wVxYhRqR93YW3++DHydSpbjBUVnQFA==
43+
dependencies:
44+
"@improbable-eng/grpc-web" "^0.14.0"
45+
google-protobuf "^3.19.1"
46+
3947
"@gitpod/supervisor-api-grpc@main":
4048
version "0.1.5-main.2046"
4149
resolved "https://registry.yarnpkg.com/@gitpod/supervisor-api-grpc/-/supervisor-api-grpc-0.1.5-main.2046.tgz#47b450cda80b3b655a76e2829a8eca66d70084bc"
@@ -63,6 +71,18 @@
6371
protobufjs "^6.10.0"
6472
yargs "^16.1.1"
6573

74+
"@improbable-eng/grpc-web-node-http-transport@^0.15.0":
75+
version "0.15.0"
76+
resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.15.0.tgz#5a064472ef43489cbd075a91fb831c2abeb09d68"
77+
integrity sha512-HLgJfVolGGpjc9DWPhmMmXJx8YGzkek7jcCFO1YYkSOoO81MWRZentPOd/JiKiZuU08wtc4BG+WNuGzsQB5jZA==
78+
79+
"@improbable-eng/grpc-web@^0.14.0":
80+
version "0.14.1"
81+
resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz#f4662f64dc89c0f956a94bb8a3b576556c74589c"
82+
integrity sha512-XaIYuunepPxoiGVLLHmlnVminUGzBTnXr8Wv7khzmLWbNw4TCwJKX09GSMJlKhu/TRk6gms0ySFxewaETSBqgw==
83+
dependencies:
84+
browser-headers "^0.4.1"
85+
6686
"@microsoft/[email protected]", "@microsoft/1ds-core-js@^3.2.2":
6787
version "3.2.3"
6888
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7"
@@ -358,6 +378,11 @@ bluebird@^3.3.3:
358378
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
359379
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
360380

381+
browser-headers@^0.4.1:
382+
version "0.4.1"
383+
resolved "https://registry.yarnpkg.com/browser-headers/-/browser-headers-0.4.1.tgz#4308a7ad3b240f4203dbb45acedb38dc2d65dd02"
384+
integrity sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==
385+
361386
buffer-crc32@~0.2.3:
362387
version "0.2.13"
363388
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"

src/vs/base/common/product.ts

+10
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,16 @@ export interface IProductConfiguration {
156156
readonly 'editSessions.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>;
157157

158158
readonly darwinUniversalAssetId?: string;
159+
160+
readonly gitpodPreview?: IGitpodPreviewConfiguration;
161+
}
162+
163+
export interface IGitpodPreviewConfiguration {
164+
host: string;
165+
log?: {
166+
analytics?: boolean;
167+
metrics?: boolean;
168+
};
159169
}
160170

161171
export type ImportantExtensionTip = { name: string; languages?: string[]; pattern?: string; isExtensionPack?: boolean; whenNotInstalled?: string[] };

src/vs/gitpod/browser/gitpodInsightsAppender.ts

+86-11
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66

77
import { IProductService } from 'vs/platform/product/common/productService';
88
import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils';
9-
import { mapTelemetryData, SenderKind } from 'vs/gitpod/common/insightsHelper';
9+
import { mapMetrics, mapTelemetryData } from 'vs/gitpod/common/insightsHelper';
10+
import type { IDEMetric } from '@gitpod/ide-metrics-api-grpcweb';
11+
12+
type SendMetrics = (metrics: IDEMetric[]) => Promise<void>;
1013

1114
export class GitpodInsightsAppender implements ITelemetryAppender {
12-
private _baseProperties: { appName: string; uiKind: 'web'; version: string };
15+
private readonly _baseProperties: { appName: string; uiKind: 'web'; version: string };
16+
private readonly devMode = this.productService.nameShort.endsWith(' Dev');
1317
constructor(
1418
@IProductService private readonly productService: IProductService
1519
) {
16-
1720
this._baseProperties = {
1821
appName: this.productService.nameShort,
1922
uiKind: 'web',
@@ -22,15 +25,87 @@ export class GitpodInsightsAppender implements ITelemetryAppender {
2225
}
2326

2427
public log(eventName: string, data: any): void {
25-
const trackMessage = mapTelemetryData(SenderKind.Browser, eventName, data);
26-
if (!trackMessage) {
27-
return;
28+
this.sendAnalytics(eventName, data);
29+
this.sendMetrics(eventName, data);
30+
}
31+
32+
private sendAnalytics(eventName: string, data: any): void {
33+
try {
34+
const trackMessage = mapTelemetryData('window', eventName, data);
35+
if (!trackMessage) {
36+
return;
37+
}
38+
trackMessage.properties = {
39+
...trackMessage.properties,
40+
...this._baseProperties,
41+
};
42+
if (this.devMode) {
43+
if (this.productService.gitpodPreview?.log?.analytics) {
44+
console.log('Gitpod Analytics: ', JSON.stringify(trackMessage, undefined, 2));
45+
}
46+
} else {
47+
// TODO(ak) get rid of it
48+
// it is bad usage of window.postMessage
49+
// we should use Segment directly here and publish to production/staging untrusted
50+
// use server api to resolve a user
51+
window.postMessage({ type: 'vscode_telemetry', event: trackMessage.event, properties: trackMessage.properties }, '*');
52+
}
53+
} catch (e) {
54+
console.error('failed to send IDE analytic:', e);
2855
}
29-
trackMessage.properties = {
30-
...trackMessage.properties,
31-
...this._baseProperties,
32-
};
33-
window.postMessage({ type: 'vscode_telemetry', event: trackMessage.event, properties: trackMessage.properties }, '*');
56+
}
57+
58+
private async sendMetrics(eventName: string, data: any): Promise<void> {
59+
try {
60+
const metrics = mapMetrics('window', eventName, data);
61+
if (!metrics || !metrics.length) {
62+
return;
63+
}
64+
if (this.devMode) {
65+
if (this.productService.gitpodPreview?.log?.metrics) {
66+
console.log('Gitpod Metrics: ', JSON.stringify(metrics, undefined, 2));
67+
}
68+
}
69+
const doSendMetrics = await this.getSendMetrics();
70+
if (doSendMetrics) {
71+
await doSendMetrics(metrics);
72+
}
73+
} catch (e) {
74+
console.error('failed to send IDE metric:', e);
75+
}
76+
}
77+
78+
private _sendMetrics: Promise<SendMetrics | undefined> | undefined;
79+
private getSendMetrics(): Promise<SendMetrics | undefined> {
80+
if (this._sendMetrics) {
81+
return this._sendMetrics;
82+
}
83+
return this._sendMetrics = (async () => {
84+
let gitpodHost: string | undefined;
85+
if (!this.devMode) {
86+
const infoResponse = await fetch(window.location.protocol + '//' + window.location.host + '/_supervisor/v1/info/workspace', {
87+
credentials: 'include'
88+
});
89+
if (!infoResponse.ok) {
90+
throw new Error(`Getting workspace info failed: ${infoResponse.statusText}`);
91+
}
92+
const info: { gitpodHost: string } = await infoResponse.json();
93+
gitpodHost = new URL(info.gitpodHost).host;
94+
} else if (this.productService.gitpodPreview) {
95+
gitpodHost = this.productService.gitpodPreview.host;
96+
}
97+
if (!gitpodHost) {
98+
return undefined;
99+
}
100+
// load grpc-web before see https://github.com/gitpod-io/gitpod/issues/4448
101+
await import('@improbable-eng/grpc-web');
102+
const { MetricsServiceClient, sendMetrics } = await import('@gitpod/ide-metrics-api-grpcweb');
103+
const ideMetricsEndpoint = 'https://ide.' + gitpodHost + '/metrics-api';
104+
const client = new MetricsServiceClient(ideMetricsEndpoint);
105+
return async (metrics: IDEMetric[]) => {
106+
await sendMetrics(client, metrics);
107+
};
108+
})();
34109
}
35110

36111
public flush(): Promise<any> {

0 commit comments

Comments
 (0)