Skip to content

Initial check-in for supporting telemetry in vscode #6123

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 35 commits into from
Mar 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b131f23
init check-in for telemetry
RodgeFu Feb 16, 2025
d7a67d3
Merge remote-tracking branch 'upstream/main' into vscode-telemetry
RodgeFu Feb 16, 2025
8dcb494
update telemetry after discussion
RodgeFu Feb 20, 2025
ef973de
add pipeline change
RodgeFu Feb 21, 2025
f495b74
update telemetry
RodgeFu Feb 24, 2025
43b59ab
some update
RodgeFu Feb 24, 2025
70f3804
Merge remote-tracking branch 'upstream/main' into vscode-telemetry
RodgeFu Feb 24, 2025
138efa8
update script
RodgeFu Feb 24, 2025
f9399c4
add changelog
RodgeFu Feb 24, 2025
76a47df
refine the operation detail part
RodgeFu Feb 25, 2025
0e3ab03
update telemetry key var name
RodgeFu Feb 26, 2025
9927ec6
comment some pipeline for verifying the change (will revert after test)
RodgeFu Feb 27, 2025
94764cc
comment more for testing
RodgeFu Feb 27, 2025
c9c1454
update script to have more log
RodgeFu Feb 27, 2025
507d81b
add more log to verify the script
RodgeFu Feb 27, 2025
fa8281e
update update-vscode-telemetry-key script
RodgeFu Feb 27, 2025
6ccec9b
fix bug in script
RodgeFu Feb 27, 2025
d302d59
refine handling of telemetry key
RodgeFu Feb 27, 2025
b056e29
add vsix to artifact
RodgeFu Feb 27, 2025
4a56505
revert unnecessary change in pipeline
RodgeFu Feb 27, 2025
36a9060
add more log
RodgeFu Feb 27, 2025
77cb489
some refine
RodgeFu Feb 27, 2025
fab9abc
rename env var
RodgeFu Feb 27, 2025
79ad735
add comment in launch.json
RodgeFu Feb 27, 2025
016c9d8
update emitterPackage to emitterName
RodgeFu Feb 28, 2025
eec692e
merge from upstream/main
RodgeFu Mar 11, 2025
3c93e16
Merge remote-tracking branch 'upstream/main' into vscode-telemetry
RodgeFu Mar 12, 2025
d890012
revert unnecessary change from merge
RodgeFu Mar 12, 2025
fa52faf
add basic telemetry for emit code
RodgeFu Mar 12, 2025
c020741
Merge remote-tracking branch 'upstream/main' into vscode-telemetry
RodgeFu Mar 13, 2025
c6738f5
add telemetry for preview and emit
RodgeFu Mar 13, 2025
e3d2e6e
add more telemetry and support delay
RodgeFu Mar 13, 2025
7f4ccc4
Merge remote-tracking branch 'upstream/main' into vscode-telemetry
RodgeFu Mar 13, 2025
e3e3e74
remove flush which doesnt work
RodgeFu Mar 13, 2025
967f05a
some update
RodgeFu Mar 13, 2025
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
7 changes: 7 additions & 0 deletions .chronus/changes/vscode-telemetry-2025-1-24-22-51-43.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- typespec-vscode
---

Support telemetry
3 changes: 3 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@
// Use empty node options and don't debug while profiling to get the most accurate timing
//"TYPESPEC_SERVER_NODE_OPTIONS": "",

// Set the telemetry key environment variable to use if you dont want to set it in package.json
//"TYPESPEC_VSCODE_TELEMETRY_KEY": "{The instrumentation key of your Application Insights}",

"TYPESPEC_SERVER_NODE_OPTIONS": "--nolazy --inspect-brk=4242",
"TYPESPEC_DEVELOPMENT_MODE": "true"
},
Expand Down
4 changes: 4 additions & 0 deletions eng/tsp-core/pipelines/jobs/build-for-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ jobs:
parameters:
useDotNet: true

- script: pnpm run update-telemetry-key $(vscode.telemetryKey)
workingDirectory: $(Build.SourcesDirectory)/packages/typespec-vscode
displayName: Update vscode telemetry key

- template: /eng/tsp-core/pipelines/templates/build.yml

- script: pnpm run test:ci
Expand Down
3 changes: 3 additions & 0 deletions packages/typespec-vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"workspaceContains:**/tspconfig.yaml"
],
"icon": "./icons/logo.png",
"telemetryKey": "00000000-0000-0000-0000-000000000000",
"contributes": {
"viewsWelcome": [
{
Expand Down Expand Up @@ -248,6 +249,7 @@
"copy-tmlanguage": "node scripts/copy-tmlanguage.js",
"generate-language-configuration": "node scripts/generate-language-configuration.js",
"generate-third-party-notices": "typespec-build-tool generate-third-party-notices",
"update-telemetry-key": "node scripts/update-telemetry-key.js",
"package-vsix": "vsce package",
"deploy": "vsce publish",
"open-in-browser": "vscode-test-web --extensionDevelopmentPath=. .",
Expand All @@ -268,6 +270,7 @@
"@typespec/internal-build-utils": "workspace:^",
"@vitest/coverage-v8": "^3.0.7",
"@vitest/ui": "^3.0.7",
"@vscode/extension-telemetry": "^0.6.2",
"@vscode/test-web": "^0.0.67",
"@vscode/vsce": "~3.2.2",
"c8": "^10.1.3",
Expand Down
37 changes: 37 additions & 0 deletions packages/typespec-vscode/scripts/update-telemetry-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";

const newKey = process.argv[2];
if (!newKey) {
console.log("One argument for telemetry-key to use is expected. Exit without updating anything");
process.exit(1);
} else {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const targetPackageJsonFile = path.resolve(__dirname, "../package.json");
console.log(`Updating package.json at ${targetPackageJsonFile}`);
const packageJson = JSON.parse(fs.readFileSync(targetPackageJsonFile, "utf8"));
const oldKey = packageJson.telemetryKey ?? "";
const keyToString = (key) =>
`'${key.substring(0, 8)}...${key.substring(Math.max(0, key.length - 17))}'(length: ${key.length})`;

console.log(`Updating telemetry key from ${keyToString(oldKey)} to ${keyToString(newKey)}`);
packageJson.telemetryKey = newKey;

fs.writeFileSync(targetPackageJsonFile, JSON.stringify(packageJson, null, 2));

// double verify the updated result
const newPackageJson = JSON.parse(fs.readFileSync(targetPackageJsonFile, "utf8"));
const updatedKey = newPackageJson.telemetryKey ?? "";
if (updatedKey !== newKey) {
console.error(
`Failed to update telemetry key in package.json. Actual = ${keyToString(updatedKey)}; Expected = ${keyToString(newKey)}`,
);
process.exit(2);
} else {
console.log(
`telemetryKey in package.json updated to '${keyToString(updatedKey)}' successfully`,
);
}
}
1 change: 1 addition & 0 deletions packages/typespec-vscode/src/const.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const StartFileName = "main.tsp";
export const TspConfigFileName = "tspconfig.yaml";
export const EmptyGuid = "00000000-0000-0000-0000-000000000000";
32 changes: 32 additions & 0 deletions packages/typespec-vscode/src/extension-state-manager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LogLevel } from "rollup";
import vscode from "vscode";
import { normalizePath } from "./path-utils.js";
import { RawTelemetryEvent } from "./telemetry/telemetry-event.js";

export interface StartUpMessage {
/**
Expand All @@ -17,6 +18,11 @@ export interface StartUpMessage {
level: LogLevel;
}

interface DelayedTelemetryEvent {
raw: RawTelemetryEvent;
isError: boolean;
}

/** manage data stored in vscode extension's state (ExtensionContext.globalState/workspaceState) */
export class ExtensionStateManager {
constructor(private vscodeContext: vscode.ExtensionContext) {}
Expand Down Expand Up @@ -58,4 +64,30 @@ export class ExtensionStateManager {
const key = this.getStartUpMessageKey(workspaceFolder);
this.setValue(key, undefined, true);
}

private TELEMETRY_DELAYED_EVENT_KEY: string = "telemetry-delayed-event";
pushDelayedTelemetryEvent(raw: RawTelemetryEvent, isError: boolean) {
const existing = this.getValue<DelayedTelemetryEvent[]>(
this.TELEMETRY_DELAYED_EVENT_KEY,
[],
true,
);
this.setValue<DelayedTelemetryEvent[]>(
this.TELEMETRY_DELAYED_EVENT_KEY,
[
...existing,
{
raw,
isError,
},
],
true,
);
}
loadDelayedTelemetryEvents(): DelayedTelemetryEvent[] {
return this.getValue<DelayedTelemetryEvent[]>(this.TELEMETRY_DELAYED_EVENT_KEY, [], true);
}
cleanUpDelayedTelemetryEvents() {
this.setValue(this.TELEMETRY_DELAYED_EVENT_KEY, [], true);
}
}
93 changes: 71 additions & 22 deletions packages/typespec-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ import { ExtensionLogListener, getPopupAction } from "./log/extension-log-listen
import logger from "./log/logger.js";
import { TypeSpecLogOutputChannel } from "./log/typespec-log-output-channel.js";
import { createTaskProvider } from "./task-provider.js";
import telemetryClient from "./telemetry/telemetry-client.js";
import { OperationTelemetryEvent, TelemetryEventName } from "./telemetry/telemetry-event.js";
import { TspLanguageClient } from "./tsp-language-client.js";
import {
CommandName,
InstallGlobalCliCommandArgs,
RestartServerCommandArgs,
RestartServerCommandResult,
Result,
ResultCode,
SettingName,
} from "./types.js";
import { isWhitespaceStringOrUndefined } from "./utils.js";
Expand All @@ -34,6 +39,8 @@ logger.registerLogListener("extension-log", new ExtensionLogListener(outputChann

export async function activate(context: ExtensionContext) {
const stateManager = new ExtensionStateManager(context);
telemetryClient.Initialize(stateManager);
context.subscriptions.push(telemetryClient);

context.subscriptions.push(createTaskProvider());

Expand Down Expand Up @@ -64,34 +71,50 @@ export async function activate(context: ExtensionContext) {
title: "Emit from TypeSpec...",
cancellable: false,
},
async () => await emitCode(context, uri),
async () => {
await telemetryClient.doOperationWithTelemetry<ResultCode>(
TelemetryEventName.EmitCode,
async (tel): Promise<ResultCode> => {
return await emitCode(context, uri, tel);
},
);
},
);
}),
);

context.subscriptions.push(
commands.registerCommand(
CommandName.RestartServer,
async (args: RestartServerCommandArgs | undefined): Promise<TspLanguageClient> => {
async (args: RestartServerCommandArgs | undefined): Promise<RestartServerCommandResult> => {
return vscode.window.withProgress(
{
title: args?.notificationMessage ?? "Restarting TypeSpec language service...",
location: vscode.ProgressLocation.Notification,
},
async () => {
if (args?.forceRecreate === true) {
logger.info("Forcing to recreate TypeSpec LSP server...");
return await recreateLSPClient(context);
}
if (client && client.state === State.Running) {
await client.restart();
return client;
} else {
logger.info(
"TypeSpec LSP server is not running which is not expected, try to recreate and start...",
);
return recreateLSPClient(context);
}
return await telemetryClient.doOperationWithTelemetry(
TelemetryEventName.RestartServer,
async (tel) => {
if (args?.forceRecreate === true) {
logger.info("Forcing to recreate TypeSpec LSP server...");
tel.lastStep = "Recreate LSP client in force";
return await recreateLSPClient(context, tel.activityId);
}
if (client && client.state === State.Running) {
tel.lastStep = "Restart LSP client";
await client.restart();
return { code: ResultCode.Success, value: client };
} else {
logger.info(
"TypeSpec LSP server is not running which is not expected, try to recreate and start...",
);
tel.lastStep = "Recreate LSP client";
return await recreateLSPClient(context, tel.activityId);
}
},
args?.activityId,
);
},
);
},
Expand Down Expand Up @@ -121,15 +144,25 @@ export async function activate(context: ExtensionContext) {

context.subscriptions.push(
commands.registerCommand(CommandName.ShowOpenApi3, async (uri: vscode.Uri) => {
await showOpenApi3(uri, context, client!);
await telemetryClient.doOperationWithTelemetry(
TelemetryEventName.PreviewOpenApi3,
async (tel): Promise<ResultCode> => {
return await showOpenApi3(uri, context, client!, tel);
},
);
}),
);

context.subscriptions.push(
vscode.workspace.onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => {
if (e.affectsConfiguration(SettingName.TspServerPath)) {
logger.info("TypeSpec server path changed, restarting server...");
await recreateLSPClient(context);
await telemetryClient.doOperationWithTelemetry(
TelemetryEventName.ServerPathSettingChanged,
async (tel) => {
return await recreateLSPClient(context, tel.activityId);
},
);
}
}),
);
Expand Down Expand Up @@ -171,27 +204,43 @@ export async function activate(context: ExtensionContext) {
location: vscode.ProgressLocation.Notification,
},
async () => {
await recreateLSPClient(context);
await telemetryClient.doOperationWithTelemetry(
TelemetryEventName.StartExtension,
async (tel: OperationTelemetryEvent) => {
return await recreateLSPClient(context, tel.activityId);
},
);
},
);
} else {
logger.info("No workspace opened, Skip starting TypeSpec language service.");
}
showStartUpMessages(stateManager);
telemetryClient.sendDelayedTelemetryEvents();
}

export async function deactivate() {
await client?.stop();
await clearOpenApi3PreviewTempFolders();
}

async function recreateLSPClient(context: ExtensionContext) {
async function recreateLSPClient(
context: ExtensionContext,
activityId: string,
): Promise<Result<TspLanguageClient>> {
logger.info("Recreating TypeSpec LSP server...");
const oldClient = client;
client = await TspLanguageClient.create(context, outputChannel);
client = await TspLanguageClient.create(activityId, context, outputChannel);
await oldClient?.stop();
await client.start();
return client;
await client.start(activityId);
if (client.state === State.Running) {
telemetryClient.logOperationDetailTelemetry(activityId, {
compilerVersion: client.initializeResult?.serverInfo?.version ?? "< 0.64.0",
});
return { code: ResultCode.Success, value: client };
} else {
return { code: ResultCode.Fail, details: "TspLanguageClient is not running." };
}
}

function showStartUpMessages(stateManager: ExtensionStateManager) {
Expand Down
Loading
Loading