Skip to content

Commit

Permalink
Telemetry (turn off with DO_NOT_TRACK env var) (#1674)
Browse files Browse the repository at this point in the history
* Set do not track ENV if telemetry is off

* Use env props

* Fix tests

* Tests for env props

* Server is too ambiguous
  • Loading branch information
sourishkrout committed Sep 19, 2024
1 parent 4173df4 commit ae812b7
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"preLaunchTask": "npm: watch"
},
{
"name": "Run Extension (no server)",
"name": "Run Extension (no webpack)",
"type": "extensionHost",
"request": "launch",
"args": [
Expand Down
4 changes: 3 additions & 1 deletion __mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ export const env = {
clipboard: {
writeText: vi.fn()
},
openExternal: vi.fn()
openExternal: vi.fn(),
onDidChangeTelemetryEnabled: vi.fn(),
isTelemetryEnabled: false,
}

export const NotebookData = vi.fn()
Expand Down
1 change: 1 addition & 0 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export class RunmeExtension {

const server = new KernelServer(
context.extensionUri,
kernel.envProps,
{
retryOnFailure: true,
maxNumberOfIntents: 10,
Expand Down
9 changes: 9 additions & 0 deletions src/extension/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import {
handleNotebookAutosaveSettings,
getWorkspaceFolder,
getRunnerSessionEnvs,
getEnvProps,
} from './utils'
import { getEventReporter } from './ai/events'
import { getSystemShellPath, isShellLanguage } from './executors/utils'
Expand Down Expand Up @@ -226,6 +227,14 @@ export class Kernel implements Disposable {
}
}

get envProps() {
const ext = {
id: this.context!.extension.id,
version: this.context!.extension.packageJSON.version,
}
return getEnvProps(ext)
}

isFeatureOn(featureName: FeatureName): boolean {
if (!this.featuresState$) {
return false
Expand Down
45 changes: 35 additions & 10 deletions src/extension/server/kernelServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import crypto from 'node:crypto'

import { ChannelCredentials } from '@grpc/grpc-js'
import { GrpcTransport } from '@protobuf-ts/grpc-transport'
import { Disposable, Uri, EventEmitter, Event } from 'vscode'
import { Disposable, Uri, EventEmitter, Event, env } from 'vscode'

import getLogger from '../logger'
import { HealthCheckRequest, HealthCheckResponse_ServingStatus } from '../grpc/healthTypes'
Expand All @@ -20,7 +20,7 @@ import {
getTLSDir,
getTLSEnabled,
} from '../../utils/configuration'
import { isPortAvailable } from '../utils'
import { EnvProps, isPortAvailable } from '../utils'
import { HealthClient } from '../grpc/client'

import KernelServerError from './kernelServerError'
Expand Down Expand Up @@ -78,6 +78,7 @@ class KernelServer implements IServer {

constructor(
protected readonly extBasePath: Uri,
protected envProps: EnvProps,
options: IServerConfig,
externalServer: boolean,
protected readonly enableRunner = false,
Expand All @@ -94,6 +95,10 @@ class KernelServer implements IServer {
this.#acceptsIntents = options.acceptsConnection?.intents || 50
this.#acceptsInterval = options.acceptsConnection?.interval || 200
this.#forceExternalServer = externalServer

env.onDidChangeTelemetryEnabled(() => {
this.kill()
})
}

dispose() {
Expand Down Expand Up @@ -132,6 +137,10 @@ class KernelServer implements IServer {
return false
}

protected kill(): boolean {
return this.#process?.kill() ?? false
}

address(): string {
const customAddress = getCustomServerAddress()
if (customAddress) {
Expand Down Expand Up @@ -238,28 +247,29 @@ class KernelServer implements IServer {
args.push('--insecure')
}

const process = spawn(binaryLocation, args)
const env = this.getConfiguredEnv()
const child = spawn(binaryLocation, args, { env })

process.on('close', (code) => {
child.on('close', (code) => {
if (this.#loggingEnabled) {
log.info(`Server process #${this.#process?.pid} closed with code ${code}`)
}
this.#onClose.fire({ code })

this.disposeProcess(process)
this.disposeProcess(child)
})

process.stderr.once('data', () => {
child.stderr.once('data', () => {
log.info(`Server process #${this.#process?.pid} started at ${address}`)
})

process.stderr.on('data', (data) => {
child.stderr.on('data', (data) => {
if (this.#loggingEnabled) {
log.info(data.toString())
}
})

this.#process = process
this.#process = child

return Promise.race([
new Promise<string>((resolve, reject) => {
Expand All @@ -280,7 +290,7 @@ class KernelServer implements IServer {
}

if (log.addr) {
process.stderr.off('data', cb)
child.stderr.off('data', cb)
return resolve(log.addr)
}
}
Expand All @@ -289,7 +299,7 @@ class KernelServer implements IServer {
}
}

process.stderr.on('data', cb)
child.stderr.on('data', cb)
}),
new Promise<never>((_, reject) => {
const { dispose } = this.#onClose.event(() => {
Expand All @@ -303,6 +313,21 @@ class KernelServer implements IServer {
])
}

protected getConfiguredEnv(): NodeJS.ProcessEnv {
const penv: NodeJS.ProcessEnv = Object.assign(process.env)

if (!env.isTelemetryEnabled) {
penv['DO_NOT_TRACK'] = 'true'
return penv
}

Object.entries(this.envProps).forEach(([k, v]) => {
penv[`TELEMETRY_${k.toUpperCase()}`] = v
})

return penv
}

protected async acceptsConnection(): Promise<void> {
const INTERVAL = this.#acceptsInterval
const INTENTS = this.#acceptsIntents
Expand Down
36 changes: 36 additions & 0 deletions src/extension/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import vscode, {
Uri,
workspace,
env,
UIKind,
window,
Disposable,
NotebookCell,
Expand Down Expand Up @@ -822,3 +823,38 @@ export async function getGitContext(path: string) {
}
}
}

export interface EnvProps {
extname: string
extversion: string
remotename: string
appname: string
product: string
platform: string
uikind: string
}

export function getEnvProps(extension: { id: string; version: string }) {
const extProps: EnvProps = {
extname: extension.id,
extversion: extension.version,
remotename: env.remoteName ?? 'none',
appname: env.appName,
product: env.appHost,
platform: `${os.platform()}_${os.arch()}`,
uikind: 'other',
}

switch (env.uiKind) {
case UIKind.Web:
extProps['uikind'] = 'web'
break
case UIKind.Desktop:
extProps['uikind'] = 'desktop'
break
default:
extProps['uikind'] = 'other'
}

return extProps
}
15 changes: 15 additions & 0 deletions tests/extension/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ vi.mock('../../src/extension/utils', async () => ({
resetNotebookSettings: vi.fn(),
getGithubAuthSession: vi.fn().mockResolvedValue(undefined),
getPlatformAuthSession: vi.fn().mockResolvedValue(undefined),
getEnvProps: vi.fn().mockReturnValue({
extname: 'stateful.runme',
extversion: '1.2.3-foo.1',
remotename: 'none',
appname: 'Visual Studio Code',
product: 'desktop',
platform: 'darwin_arm64',
uikind: 'desktop',
}),
}))

vi.mock('../../src/extension/grpc/runner/v1', () => ({}))
Expand All @@ -92,6 +101,12 @@ test('initializes all providers', async () => {
const context: any = {
subscriptions: [],
extensionUri: { fsPath: '/foo/bar' },
extension: {
id: 'foo.bar',
packageJSON: {
version: '1.2.3-rc.4',
},
},
environmentVariableCollection: {
prepend: vi.fn(),
append: vi.fn(),
Expand Down
25 changes: 25 additions & 0 deletions tests/extension/kernel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ vi.mock('../../src/extension/utils', async () => {
getGithubAuthSession: vi.fn().mockResolvedValue({
accessToken: '123',
}),
getEnvProps: vi.fn().mockReturnValue({
extname: 'stateful.runme',
extversion: '1.2.3-foo.1',
remotename: 'none',
appname: 'Visual Studio Code',
product: 'desktop',
platform: 'darwin_arm64',
uikind: 'desktop',
}),
}
})
vi.mock('../../src/utils/configuration', async (importActual) => {
Expand Down Expand Up @@ -414,3 +423,19 @@ test('supportedLanguages', async () => {

expect(k.getSupportedLanguages()![0]).toStrictEqual('shellscript')
})

test('#envProps', async () => {
const k = new Kernel({
extension: { id: 'stateful.runme', packageJSON: { version: '1.2.3-rc.0' } },
} as any)

expect(k.envProps).toStrictEqual({
appname: 'Visual Studio Code',
extname: 'stateful.runme',
extversion: '1.2.3-foo.1',
platform: 'darwin_arm64',
product: 'desktop',
remotename: 'none',
uikind: 'desktop',
})
})
21 changes: 19 additions & 2 deletions tests/extension/server/kernelServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import fs from 'node:fs/promises'

import { suite, test, expect, vi, beforeEach } from 'vitest'
import { Uri, workspace } from 'vscode'
import { Uri, workspace, env } from 'vscode'
// eslint-disable-next-line max-len
import { HealthCheckResponse_ServingStatus } from '@buf/grpc_grpc.community_timostamm-protobuf-ts/grpc/health/v1/health_pb'

Expand Down Expand Up @@ -98,6 +98,7 @@ suite('Kernel server spawn process', () => {

const server = new Server(
Uri.file('/Users/user/.vscode/extension/stateful.runme'),
<any>{},
{
retryOnFailure: true,
maxNumberOfIntents: 2,
Expand All @@ -114,6 +115,7 @@ suite('Kernel server spawn process', () => {

const server = new Server(
Uri.file('/Users/user/.vscode/extension/stateful.runme'),
<any>{},
{
retryOnFailure: true,
maxNumberOfIntents: 2,
Expand All @@ -130,6 +132,7 @@ suite('Kernel server spawn process', () => {

const server = new Server(
Uri.file('/Users/user/.vscode/extension/stateful.runme'),
<any>{},
{
retryOnFailure: true,
maxNumberOfIntents: 2,
Expand All @@ -146,6 +149,7 @@ suite('Kernel server spawn process', () => {

const server = new Server(
Uri.file('/Users/user/.vscode/extension/stateful.runme'),
<any>{},
{
retryOnFailure: true,
maxNumberOfIntents: 2,
Expand All @@ -157,7 +161,7 @@ suite('Kernel server spawn process', () => {
expect(address).toStrictEqual('localhost:7863')
})

test('Should try 2 times before failing', async () => {
test('Should try twice before failing', async () => {
configValues.enableTLS = true

const server = createServer({
Expand Down Expand Up @@ -189,6 +193,18 @@ suite('Kernel server spawn process', () => {

expect(server['_port']()).toStrictEqual(port + 1)
})

test('Should respect telemetry choice', async () => {
configValues.enableTLS = true

const server = createServer({
retryOnFailure: true,
maxNumberOfIntents: 2,
})

vi.mocked(env).isTelemetryEnabled = false
expect(server['getConfiguredEnv']()['DO_NOT_TRACK']).toBe('true')
})
})

suite('Kernel server accept connections', () => {
Expand Down Expand Up @@ -230,6 +246,7 @@ function createServer(
) {
return new Server(
Uri.file('/Users/user/.vscode/extension/stateful.runme'),
<any>{},
config,
externalServer,
)
Expand Down

0 comments on commit ae812b7

Please sign in to comment.