Skip to content
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

Measuring node main thread occupancy #6223

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 6 additions & 0 deletions vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ import { VSCodeSecretStorage, secretStorage } from './services/SecretStorageProv
import { registerSidebarCommands } from './services/SidebarCommands'
import { CodyStatusBar } from './services/StatusBar'
import { createOrUpdateTelemetryRecorderProvider } from './services/telemetry-v2'
import { ThreadOccupancyMonitor } from './services/thread-occupancy-monitor'
import {
enableVerboseDebugMode,
exportOutputLog,
Expand All @@ -123,6 +124,11 @@ export async function start(
): Promise<vscode.Disposable> {
const disposables: vscode.Disposable[] = []

const threadMonitor = new ThreadOccupancyMonitor()
threadMonitor.start()

disposables.push(new vscode.Disposable(() => threadMonitor.stop()))

//TODO: Add override flag
const isExtensionModeDevOrTest =
context.extensionMode === vscode.ExtensionMode.Development ||
Expand Down
62 changes: 62 additions & 0 deletions vscode/src/services/thread-occupancy-monitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { telemetryRecorder } from '@sourcegraph/cody-shared'

export class ThreadOccupancyMonitor {
private lastMeasurement: number = performance.now()
private measurements: number[] = []
private measurementInterval: NodeJS.Timeout | null = null

// Sample interval in ms (e.g. every 1 minute)
private readonly SAMPLE_INTERVAL = 60_000
// How many samples to keep for rolling average
private readonly SAMPLE_WINDOW = 6

start(): void {
if (this.measurementInterval) {
return
}

this.measurementInterval = setInterval(() => {
this.measure()
}, this.SAMPLE_INTERVAL)
}

stop(): void {
if (this.measurementInterval) {
clearInterval(this.measurementInterval)
this.measurementInterval = null
}
}

private measure(): void {
const now = performance.now()
const elapsed = now - this.lastMeasurement
this.lastMeasurement = now

// Get CPU usage for the main thread
const cpuUsage = process.cpuUsage()
const userCPUMs = cpuUsage.user / 1000 // Convert to ms
const systemCPUMs = cpuUsage.system / 1000

// Calculate occupancy as percentage
const occupancy = ((userCPUMs + systemCPUMs) / elapsed) * 100

// Keep rolling window of measurements
this.measurements.push(occupancy)
if (this.measurements.length > this.SAMPLE_WINDOW) {
this.measurements.shift()
}

this.emitTelemetry(occupancy)
}

private emitTelemetry(occupancy: number): void {
telemetryRecorder.recordEvent('cody.performance', 'threadOccupancy', {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@akalia25 Would doing something like this be acceptable from the frame of reference of the data team? Basically it will emit a telemetry event every 1 minute of usage. Is that something we can handle?

Copy link
Contributor

@akalia25 akalia25 Dec 2, 2024

Choose a reason for hiding this comment

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

Hey @arafatkatze, thanks for checking in with us! This could be a lot of events. When you say usage, is this active usage? Or does it still record when the IDE is idle?

It seems like this telemetry will be used for monitoring CPU usage (correct me if I'm wrong). I'd love to explore options on how we can limit this to ~1M events a day, if possible. :) ~ If this data is super important and will unlock a lot for us, let me know, and we can make it work with the current load.

metadata: {
occupancyPercent: Math.round(occupancy),
rollingAveragePercent: Math.round(
this.measurements.reduce((a, b) => a + b, 0) / this.measurements.length
),
},
})
}
}
Loading