mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 19:17:28 +00:00
* perf(usage): keep per-source usage caches off the main thread The usage stores kept every provider's per-source scan cache (processedFiles / processedDatabases) inside the one JSON file main loads at startup. On a large Claude history that file is 56 MB, 43 MB of it per-source records only a scan reads, so main paid a sync read+parse at launch, a structured clone of the whole cache to and from the scan worker on every scan, and a pretty-printed rewrite after every scan. The usage scan worker now owns those records in a sidecar (`<cache>-sources.json`): it reads it before a scan, writes it after (compact, durable rename), and returns only sessions and daily aggregates. Main keeps and writes only that report, compact. A cache still carrying inline records is split on the worker the first time it is loaded (above 8 MB); smaller ones parse on main and rescan cold. Sync IPC getters and automation lookups wait for the load. Synthetic 56 MB cache, same machine, interleaved runs: - first launch: 108 ms sync parse -> 18 ms main busy, 4 ms longest block - later launches: ~69 ms -> ~7 ms - forced scan, longest main-thread block: 89 ms -> 11 ms (real built app, headless: 84-94 ms -> 11-13 ms) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(usage): preserve cache attribution and legacy warm scans * fix(usage): reclaim cache temps after worker retirement --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Neil <neil@stably.ai>
169 lines
5.0 KiB
TypeScript
169 lines
5.0 KiB
TypeScript
import { codexOpenCodeTokenSessions } from '../usage/agent-token-usage'
|
|
import { app } from 'electron'
|
|
import { join } from 'node:path'
|
|
import type {
|
|
OpenCodeUsageBreakdownKind,
|
|
OpenCodeUsageBreakdownRow,
|
|
OpenCodeUsageDailyPoint,
|
|
OpenCodeUsageRange,
|
|
OpenCodeUsageScope,
|
|
OpenCodeUsageSessionRow,
|
|
OpenCodeUsageSnapshot,
|
|
OpenCodeUsageSummary
|
|
} from '../../shared/opencode-usage-types'
|
|
import type { Store } from '../persistence'
|
|
import type {
|
|
OpenCodeUsageDailyAggregate,
|
|
OpenCodeUsagePersistedState,
|
|
OpenCodeUsageSession
|
|
} from './types'
|
|
import { openCodeUsageProvider } from './opencode-usage-provider'
|
|
import { getDefaultState, normalizePersistedState } from './persisted-state-normalization'
|
|
import {
|
|
filterDailyAggregatesByScopeAndRange,
|
|
filterSessionsByScopeAndRange
|
|
} from './scope-range-filter'
|
|
import {
|
|
buildOpenCodeUsageBreakdownRows,
|
|
buildOpenCodeUsageDailyPoints,
|
|
buildOpenCodeUsageRecentSessions,
|
|
buildOpenCodeUsageSummary
|
|
} from './snapshot-rollups'
|
|
import { UsageProviderStoreLifecycle } from '../usage/usage-provider-store-lifecycle'
|
|
import { splitUsageCacheFileViaWorker } from '../usage/usage-scan-worker-spawn'
|
|
|
|
let _openCodeUsageFile: string | null = null
|
|
|
|
export function initOpenCodeUsagePath(): void {
|
|
_openCodeUsageFile = join(app.getPath('userData'), 'orca-opencode-usage.json')
|
|
}
|
|
|
|
function getOpenCodeUsageFile(): string {
|
|
if (!_openCodeUsageFile) {
|
|
_openCodeUsageFile = join(app.getPath('userData'), 'orca-opencode-usage.json')
|
|
}
|
|
return _openCodeUsageFile
|
|
}
|
|
|
|
export class OpenCodeUsageStore extends UsageProviderStoreLifecycle<
|
|
'processedDatabases',
|
|
OpenCodeUsagePersistedState,
|
|
'hasAnyOpenCodeData'
|
|
> {
|
|
constructor(store: Pick<Store, 'getRepos' | 'getAllWorktreeMeta'>) {
|
|
super(store, {
|
|
tokenUsage: {
|
|
provider: 'opencode',
|
|
selectSessions: (state) => codexOpenCodeTokenSessions(state.sessions)
|
|
},
|
|
logTag: '[opencode-usage]',
|
|
resolveCacheFile: getOpenCodeUsageFile,
|
|
createDefaultState: getDefaultState,
|
|
normalizeState: normalizePersistedState,
|
|
sourceKey: 'processedDatabases',
|
|
dataPresenceKey: 'hasAnyOpenCodeData',
|
|
scan: openCodeUsageProvider.scan,
|
|
splitCacheFile: splitUsageCacheFileViaWorker
|
|
})
|
|
}
|
|
|
|
getSnapshot(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange,
|
|
recentSessionLimit = 10
|
|
): OpenCodeUsageSnapshot {
|
|
return {
|
|
scanState: this.getScanState(),
|
|
summary: this.buildSummary(scope, range),
|
|
daily: this.buildDaily(scope, range),
|
|
modelBreakdown: this.buildBreakdown(scope, range, 'model'),
|
|
projectBreakdown: this.buildBreakdown(scope, range, 'project'),
|
|
recentSessions: this.buildRecentSessions(scope, range, recentSessionLimit)
|
|
}
|
|
}
|
|
|
|
async getSummary(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange
|
|
): Promise<OpenCodeUsageSummary> {
|
|
await this.refresh(false)
|
|
return this.buildSummary(scope, range)
|
|
}
|
|
|
|
private buildSummary(scope: OpenCodeUsageScope, range: OpenCodeUsageRange): OpenCodeUsageSummary {
|
|
return buildOpenCodeUsageSummary(
|
|
scope,
|
|
range,
|
|
this.getFilteredDaily(scope, range),
|
|
this.getFilteredSessions(scope, range)
|
|
)
|
|
}
|
|
|
|
async getDaily(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange
|
|
): Promise<OpenCodeUsageDailyPoint[]> {
|
|
await this.refresh(false)
|
|
return this.buildDaily(scope, range)
|
|
}
|
|
|
|
private buildDaily(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange
|
|
): OpenCodeUsageDailyPoint[] {
|
|
return buildOpenCodeUsageDailyPoints(this.getFilteredDaily(scope, range))
|
|
}
|
|
|
|
async getBreakdown(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange,
|
|
kind: OpenCodeUsageBreakdownKind
|
|
): Promise<OpenCodeUsageBreakdownRow[]> {
|
|
await this.refresh(false)
|
|
return this.buildBreakdown(scope, range, kind)
|
|
}
|
|
|
|
private buildBreakdown(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange,
|
|
kind: OpenCodeUsageBreakdownKind
|
|
): OpenCodeUsageBreakdownRow[] {
|
|
return buildOpenCodeUsageBreakdownRows(
|
|
kind,
|
|
this.getFilteredDaily(scope, range),
|
|
this.getFilteredSessions(scope, range)
|
|
)
|
|
}
|
|
|
|
async getRecentSessions(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange,
|
|
limit = 10
|
|
): Promise<OpenCodeUsageSessionRow[]> {
|
|
await this.refresh(false)
|
|
return this.buildRecentSessions(scope, range, limit)
|
|
}
|
|
|
|
private buildRecentSessions(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange,
|
|
limit = 10
|
|
): OpenCodeUsageSessionRow[] {
|
|
return buildOpenCodeUsageRecentSessions(this.getFilteredSessions(scope, range), limit)
|
|
}
|
|
|
|
private getFilteredDaily(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange
|
|
): OpenCodeUsageDailyAggregate[] {
|
|
return filterDailyAggregatesByScopeAndRange(this.state.dailyAggregates, scope, range)
|
|
}
|
|
|
|
private getFilteredSessions(
|
|
scope: OpenCodeUsageScope,
|
|
range: OpenCodeUsageRange
|
|
): OpenCodeUsageSession[] {
|
|
return filterSessionsByScopeAndRange(this.state.sessions, scope, range)
|
|
}
|
|
}
|