Add the CLI version to telemetry events

This adds the CLI version to telemetry command-usage events.

Note that the CLI server is created after the telemetry listener is
created. The first few telemetry events may have a "not-set" value for
the CLI version.
This commit is contained in:
Andrew Eisenberg
2023-04-12 16:34:14 -07:00
parent eabcd00e75
commit d6b3e51f15
4 changed files with 53 additions and 9 deletions

View File

@@ -75,7 +75,7 @@ export function registerCommandWithErrorHandling(
return undefined; return undefined;
} finally { } finally {
const executionTime = Date.now() - startTime; const executionTime = Date.now() - startTime;
telemetryListener?.sendCommandUsage(commandId, executionTime, error); void telemetryListener?.sendCommandUsage(commandId, executionTime, error);
} }
}); });
} }

View File

@@ -306,7 +306,7 @@ export async function activate(
const distributionConfigListener = new DistributionConfigListener(); const distributionConfigListener = new DistributionConfigListener();
await initializeLogging(ctx); await initializeLogging(ctx);
await initializeTelemetry(extension, ctx); const telemetryListener = await initializeTelemetry(extension, ctx);
addUnhandledRejectionListener(); addUnhandledRejectionListener();
install(); install();
@@ -395,6 +395,7 @@ export async function activate(
variantAnalysisViewSerializer.onExtensionLoaded( variantAnalysisViewSerializer.onExtensionLoaded(
codeQlExtension.variantAnalysisManager, codeQlExtension.variantAnalysisManager,
); );
telemetryListener.cli = codeQlExtension.cliServer;
} }
return codeQlExtension; return codeQlExtension;

View File

@@ -19,6 +19,7 @@ import { extLogger } from "./common";
import { UserCancellationException } from "./progress"; import { UserCancellationException } from "./progress";
import { showBinaryChoiceWithUrlDialog } from "./helpers"; import { showBinaryChoiceWithUrlDialog } from "./helpers";
import { RedactableError } from "./pure/errors"; import { RedactableError } from "./pure/errors";
import { CodeQLCliServer } from "./cli";
// Key is injected at build time through the APP_INSIGHTS_KEY environment variable. // Key is injected at build time through the APP_INSIGHTS_KEY environment variable.
const key = "REPLACE-APP-INSIGHTS-KEY"; const key = "REPLACE-APP-INSIGHTS-KEY";
@@ -56,6 +57,8 @@ export class TelemetryListener extends ConfigListener {
private reporter?: TelemetryReporter; private reporter?: TelemetryReporter;
private _cli?: CodeQLCliServer;
constructor( constructor(
private readonly id: string, private readonly id: string,
private readonly version: string, private readonly version: string,
@@ -147,7 +150,7 @@ export class TelemetryListener extends ConfigListener {
void this.reporter?.dispose(); void this.reporter?.dispose();
} }
sendCommandUsage(name: string, executionTime: number, error?: Error) { async sendCommandUsage(name: string, executionTime: number, error?: Error) {
if (!this.reporter) { if (!this.reporter) {
return; return;
} }
@@ -157,12 +160,18 @@ export class TelemetryListener extends ConfigListener {
? CommandCompletion.Cancelled ? CommandCompletion.Cancelled
: CommandCompletion.Failed; : CommandCompletion.Failed;
const cliVersion = this._cli
? (await this._cli.getVersion()).toString()
: // telemetry events that are sent before the cli is initialized will not have a version number
"not-set";
this.reporter.sendTelemetryEvent( this.reporter.sendTelemetryEvent(
"command-usage", "command-usage",
{ {
name, name,
status, status,
isCanary: isCanary().toString(), isCanary: isCanary().toString(),
cliVersion,
}, },
{ executionTime }, { executionTime },
); );
@@ -241,6 +250,10 @@ export class TelemetryListener extends ConfigListener {
return this.reporter; return this.reporter;
} }
set cli(cli: CodeQLCliServer) {
this._cli = cli;
}
private disposeReporter() { private disposeReporter() {
if (this.reporter) { if (this.reporter) {
void this.reporter.dispose(); void this.reporter.dispose();
@@ -265,7 +278,7 @@ export let telemetryListener: TelemetryListener | undefined;
export async function initializeTelemetry( export async function initializeTelemetry(
extension: Extension<any>, extension: Extension<any>,
ctx: ExtensionContext, ctx: ExtensionContext,
): Promise<void> { ): Promise<TelemetryListener> {
if (telemetryListener !== undefined) { if (telemetryListener !== undefined) {
throw new Error("Telemetry is already initialized"); throw new Error("Telemetry is already initialized");
} }
@@ -279,4 +292,5 @@ export async function initializeTelemetry(
// this is a particular problem during integration tests, which will hang if a modal popup is displayed. // this is a particular problem during integration tests, which will hang if a modal popup is displayed.
void telemetryListener.initialize(); void telemetryListener.initialize();
ctx.subscriptions.push(telemetryListener); ctx.subscriptions.push(telemetryListener);
return telemetryListener;
} }

View File

@@ -185,7 +185,7 @@ describe("telemetry reporting", () => {
it("should send an event", async () => { it("should send an event", async () => {
await telemetryListener.initialize(); await telemetryListener.initialize();
telemetryListener.sendCommandUsage("command-id", 1234, undefined); await telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith( expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage", "command-usage",
@@ -193,6 +193,7 @@ describe("telemetry reporting", () => {
name: "command-id", name: "command-id",
status: "Success", status: "Success",
isCanary, isCanary,
cliVersion: "not-set",
}, },
{ executionTime: 1234 }, { executionTime: 1234 },
); );
@@ -203,7 +204,7 @@ describe("telemetry reporting", () => {
it("should send a command usage event with an error", async () => { it("should send a command usage event with an error", async () => {
await telemetryListener.initialize(); await telemetryListener.initialize();
telemetryListener.sendCommandUsage( await telemetryListener.sendCommandUsage(
"command-id", "command-id",
1234, 1234,
new UserCancellationException(), new UserCancellationException(),
@@ -215,6 +216,33 @@ describe("telemetry reporting", () => {
name: "command-id", name: "command-id",
status: "Cancelled", status: "Cancelled",
isCanary, isCanary,
cliVersion: "not-set",
},
{ executionTime: 1234 },
);
expect(sendTelemetryExceptionSpy).not.toBeCalled();
});
it("should send a command usage event with a cli version", async () => {
await telemetryListener.initialize();
telemetryListener.cli = {
getVersion: () => Promise.resolve("1.2.3"),
} as any;
await telemetryListener.sendCommandUsage(
"command-id",
1234,
new UserCancellationException(),
);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage",
{
name: "command-id",
status: "Cancelled",
isCanary,
cliVersion: "1.2.3",
}, },
{ executionTime: 1234 }, { executionTime: 1234 },
); );
@@ -226,8 +254,8 @@ describe("telemetry reporting", () => {
await telemetryListener.initialize(); await telemetryListener.initialize();
await enableTelemetry("codeQL.telemetry", false); await enableTelemetry("codeQL.telemetry", false);
telemetryListener.sendCommandUsage("command-id", 1234, undefined); await telemetryListener.sendCommandUsage("command-id", 1234, undefined);
telemetryListener.sendCommandUsage("command-id", 1234, new Error()); await telemetryListener.sendCommandUsage("command-id", 1234, new Error());
expect(sendTelemetryEventSpy).not.toBeCalled(); expect(sendTelemetryEventSpy).not.toBeCalled();
expect(sendTelemetryExceptionSpy).not.toBeCalled(); expect(sendTelemetryExceptionSpy).not.toBeCalled();
@@ -238,7 +266,7 @@ describe("telemetry reporting", () => {
await enableTelemetry("codeQL.telemetry", false); await enableTelemetry("codeQL.telemetry", false);
await enableTelemetry("codeQL.telemetry", true); await enableTelemetry("codeQL.telemetry", true);
telemetryListener.sendCommandUsage("command-id", 1234, undefined); await telemetryListener.sendCommandUsage("command-id", 1234, undefined);
expect(sendTelemetryEventSpy).toHaveBeenCalledWith( expect(sendTelemetryEventSpy).toHaveBeenCalledWith(
"command-usage", "command-usage",
@@ -246,6 +274,7 @@ describe("telemetry reporting", () => {
name: "command-id", name: "command-id",
status: "Success", status: "Success",
isCanary, isCanary,
cliVersion: "not-set",
}, },
{ executionTime: 1234 }, { executionTime: 1234 },
); );