Skip to content
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
30 changes: 30 additions & 0 deletions src/__tests__/sessions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import { _testing } from "../sessions";

const { durationPercentile, summarizeDurations } = _testing;

// Test sessions.ts functions that don't depend on the real ~/.copilot directory
// by creating temporary fixture directories
Expand Down Expand Up @@ -83,6 +86,33 @@ summary_count: 3
});

describe("analytics aggregation logic", () => {
it("returns zero percentiles when no sessions have duration data", () => {
expect(summarizeDurations([])).toEqual({
avgDuration: 0,
minDuration: 0,
maxDuration: 0,
p50Duration: 0,
p90Duration: 0,
p99Duration: 0,
totalDuration: 0,
});
});

it("uses the single duration for every percentile", () => {
expect(durationPercentile([42_000], 0.5)).toBe(42_000);
expect(durationPercentile([42_000], 0.9)).toBe(42_000);
expect(durationPercentile([42_000], 0.99)).toBe(42_000);
});

it("calculates nearest-rank duration percentiles without mutating input", () => {
const durations = [100, 10, 90, 20, 80, 30, 70, 40, 60, 50];

expect(durationPercentile(durations, 0.5)).toBe(50);
expect(durationPercentile(durations, 0.9)).toBe(90);
expect(durationPercentile(durations, 0.99)).toBe(100);
expect(durations).toEqual([100, 10, 90, 20, 80, 30, 70, 40, 60, 50]);
});

it("counts tool usage correctly", () => {
const toolUsage: Record<string, number> = {};
const events = [
Expand Down
34 changes: 29 additions & 5 deletions src/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export interface AnalyticsData {
avgDuration: number;
minDuration: number;
maxDuration: number;
p50Duration: number;
p90Duration: number;
p99Duration: number;
totalDuration: number;
toolUsage: Record<string, number>;
topDirectories: Record<string, number>;
Expand Down Expand Up @@ -295,6 +298,28 @@ export function getAnalytics(source: AnalyticsSourceFilter = "all"): AnalyticsDa
return cachedCall(`getAnalytics:${source}`, CACHE_TTL, () => _computeAnalytics(source));
}

/** Return the nearest-rank percentile without changing the caller's samples. */
function durationPercentile(durations: number[], percentile: number): number {
if (durations.length === 0) return 0;

const sorted = [...durations].sort((a, b) => a - b);
const index = Math.ceil(percentile * sorted.length) - 1;
return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
}

function summarizeDurations(durations: number[]) {
const totalDuration = durations.reduce((a, b) => a + b, 0);
return {
avgDuration: durations.length ? totalDuration / durations.length : 0,
minDuration: durations.length ? Math.min(...durations) : 0,
maxDuration: durations.length ? Math.max(...durations) : 0,
p50Duration: durationPercentile(durations, 0.5),
p90Duration: durationPercentile(durations, 0.9),
p99Duration: durationPercentile(durations, 0.99),
totalDuration,
};
}

function _computeAnalytics(source: AnalyticsSourceFilter = "all"): AnalyticsData {
const allSessions = listSessions();
const sessions = source === "all" ? allSessions : allSessions.filter((s) => s.source === source);
Expand Down Expand Up @@ -453,15 +478,12 @@ function _computeAnalytics(source: AnalyticsSourceFilter = "all"): AnalyticsData
} catch {}
}

const totalDuration = durations.reduce((a, b) => a + b, 0);
const durationSummary = summarizeDurations(durations);

return {
totalSessions: sessions.length,
sessionsPerDay,
avgDuration: durations.length ? totalDuration / durations.length : 0,
minDuration: durations.length ? Math.min(...durations) : 0,
maxDuration: durations.length ? Math.max(...durations) : 0,
totalDuration,
...durationSummary,
toolUsage,
topDirectories,
branchTime,
Expand Down Expand Up @@ -918,3 +940,5 @@ export function getVSCodeScore(): RepoScore {
tips,
};
}

export const _testing = { durationPercentile, summarizeDurations };