Skip to content

Commit f0e0508

Browse files
committed
Skip unchanged cost snapshot conversions
1 parent 1ba7968 commit f0e0508

3 files changed

Lines changed: 85 additions & 20 deletions

File tree

Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore+CodexCache.swift

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -216,13 +216,8 @@ extension CostUsageStore {
216216
baseline: PersistedFileBaseline,
217217
calendar: Calendar)
218218
{
219-
let snapshots = (usage.codexTokenSnapshots ?? []).enumerated().map {
220-
Self.tokenSnapshot(path: path, eventIndex: $0.offset, snapshot: $0.element, calendar: calendar)
221-
}
222-
let rows = (usage.codexRows ?? []).enumerated().compactMap { index, row -> CostUsageStoreUsageRow? in
223-
guard let payload = try? JSONEncoder().encode(row) else { return nil }
224-
return CostUsageStoreUsageRow(path: path, rowIndex: index, payload: payload)
225-
}
219+
let tokenSnapshots = usage.codexTokenSnapshots ?? []
220+
let usageRows = usage.codexRows ?? []
226221
let details = StoredFileDetails(
227222
lastTotals: usage.lastTotals,
228223
projectPath: usage.projectPath,
@@ -269,19 +264,34 @@ extension CostUsageStore {
269264
let appendSafe = baseline.canReuseRows
270265
&& baseline.file?.scanState.fileIdentity == file.scanState.fileIdentity
271266
&& oldParsedBytes < newParsedBytes
272-
if baseline.canReuseRows, oldParsedBytes == newParsedBytes, baseline.snapshotCount == snapshots.count {
267+
if baseline.canReuseRows, oldParsedBytes == newParsedBytes, baseline.snapshotCount == tokenSnapshots.count {
273268
// Stable cursor: the persisted prefix is already authoritative.
274-
} else if appendSafe, baseline.snapshotCount <= snapshots.count {
275-
_ = self.appendTokenSnapshots(Array(snapshots.dropFirst(baseline.snapshotCount)))
269+
} else if appendSafe, baseline.snapshotCount <= tokenSnapshots.count {
270+
_ = self.appendTokenSnapshots(Self.storeTokenSnapshots(
271+
path: path,
272+
snapshots: tokenSnapshots,
273+
startingAt: baseline.snapshotCount,
274+
calendar: calendar))
276275
} else {
277-
_ = self.replaceTokenSnapshots(path: path, snapshots: snapshots)
276+
_ = self.replaceTokenSnapshots(
277+
path: path,
278+
snapshots: Self.storeTokenSnapshots(
279+
path: path,
280+
snapshots: tokenSnapshots,
281+
startingAt: 0,
282+
calendar: calendar))
278283
}
279-
if baseline.canReuseRows, oldParsedBytes == newParsedBytes, baseline.rowCount == rows.count {
284+
if baseline.canReuseRows, oldParsedBytes == newParsedBytes, baseline.rowCount == usageRows.count {
280285
// Stable cursor: metadata/aggregate updates do not rewrite historical rows.
281-
} else if appendSafe, baseline.rowCount <= rows.count {
282-
_ = self.appendUsageRows(Array(rows.dropFirst(baseline.rowCount)))
286+
} else if appendSafe, baseline.rowCount <= usageRows.count {
287+
_ = self.appendUsageRows(Self.storeUsageRows(
288+
path: path,
289+
rows: usageRows,
290+
startingAt: baseline.rowCount))
283291
} else {
284-
_ = self.replaceUsageRows(path: path, rows: rows)
292+
_ = self.replaceUsageRows(
293+
path: path,
294+
rows: Self.storeUsageRows(path: path, rows: usageRows, startingAt: 0))
285295
}
286296
_ = self.replaceFileDayAggregates(path: path, aggregates: Self.fileAggregates(usage))
287297
_ = self.upsertForkLineage(CostUsageStoreForkLineage(
@@ -295,7 +305,7 @@ extension CostUsageStore {
295305
self.persistBuffers(path: path, usage: usage)
296306
_ = self.upsertAccumulator(CostUsageStoreAccumulator(
297307
path: path,
298-
eventCount: snapshots.count,
308+
eventCount: tokenSnapshots.count,
299309
nextUsageRowIndex: CostUsageScanner.nextCodexUsageRowIndex(usage.codexRows),
300310
countedTotals: Self.totals(usage.lastCountedTotals),
301311
rawTotalsBaseline: Self.totals(usage.lastRawTotalsBaseline),
@@ -579,6 +589,7 @@ extension CostUsageStore {
579589
snapshot: CostUsageCodexTokenSnapshot,
580590
calendar: Calendar) -> CostUsageStoreTokenSnapshot
581591
{
592+
tokenSnapshotConversionForTesting?(path, eventIndex)
582593
let date = CostUsageScanner.dateFromTimestamp(snapshot.timestamp)
583594
return CostUsageStoreTokenSnapshot(
584595
path: path,
@@ -591,6 +602,28 @@ extension CostUsageStore {
591602
endOffset: snapshot.endOffset)
592603
}
593604

605+
private static func storeTokenSnapshots(
606+
path: String,
607+
snapshots: [CostUsageCodexTokenSnapshot],
608+
startingAt startIndex: Int,
609+
calendar: Calendar) -> [CostUsageStoreTokenSnapshot]
610+
{
611+
snapshots.indices.dropFirst(startIndex).map { index in
612+
Self.tokenSnapshot(path: path, eventIndex: index, snapshot: snapshots[index], calendar: calendar)
613+
}
614+
}
615+
616+
private static func storeUsageRows(
617+
path: String,
618+
rows: [CostUsageScanner.CodexUsageRow],
619+
startingAt startIndex: Int) -> [CostUsageStoreUsageRow]
620+
{
621+
rows.indices.dropFirst(startIndex).compactMap { index in
622+
guard let payload = try? JSONEncoder().encode(rows[index]) else { return nil }
623+
return CostUsageStoreUsageRow(path: path, rowIndex: index, payload: payload)
624+
}
625+
}
626+
594627
private static func tokenSnapshot(from value: CostUsageStoreTokenSnapshot) -> CostUsageCodexTokenSnapshot {
595628
CostUsageCodexTokenSnapshot(
596629
timestamp: value.timestamp,

Sources/CodexBarCore/Vendored/CostUsage/CostUsageStore.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ actor CostUsageStore {
7777
/// process at a deterministic mid-save point. Never set in production.
7878
nonisolated(unsafe) static var saveCycleCheckpointForTesting: ((Int) -> Void)?
7979

80+
/// Test-only observer for snapshot rows converted during a save. Stable rows should
81+
/// remain in SQLite without repeating timestamp parsing on every bounded catch-up pass.
82+
nonisolated(unsafe) static var tokenSnapshotConversionForTesting: ((String, Int) -> Void)?
83+
8084
/// Process-wide serialization keeps every writable store connection on the same queue.
8185
/// This matches the scan pipeline's single-writer contract without multiplying executor
8286
/// threads when tests or short-lived readers create several store actors.

Tests/CodexBarTests/CostUsageStoreCutoverTests.swift

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ struct CostUsageStoreCutoverTests {
105105
now: day,
106106
options: options)
107107

108+
let storedPath = try #require(CostUsageStoreAccess.read(cacheRoot: env.cacheRoot).files.keys.first {
109+
$0.hasSuffix("linear.jsonl")
110+
})
111+
let conversionCounter = StoreSnapshotConversionCounter()
112+
CostUsageStore.tokenSnapshotConversionForTesting = { path, _ in
113+
guard path == storedPath else { return }
114+
conversionCounter.increment()
115+
}
116+
defer { CostUsageStore.tokenSnapshotConversionForTesting = nil }
117+
108118
try Self.append(Self.tokenLine(timestamp: timestamp, input: 2) + "\n", to: fileURL)
109119
let appendRecorder = CostUsageScanner.CodexScanWorkRecorder()
110120
options.codexScanWorkRecorderForTesting = appendRecorder
@@ -116,15 +126,15 @@ struct CostUsageStoreCutoverTests {
116126
options: options)
117127
#expect(appendRecorder.snapshot().usageRowsProcessed == 1)
118128
#expect(appendRecorder.snapshot().usageRowsRepriced == 1)
129+
#expect(conversionCounter.value == 1)
119130

120-
let cache = CostUsageStoreAccess.read(cacheRoot: env.cacheRoot)
121-
let path = try #require(cache.files.keys.first { $0.hasSuffix("linear.jsonl") })
122131
let store = CostUsageStore(cacheRoot: env.cacheRoot)
123-
#expect(await store.fetchUsageRows(path: path).count == 2)
124-
#expect(await store.fetchAccumulator(path: path)?.eventCount == 2)
132+
#expect(await store.fetchUsageRows(path: storedPath).count == 2)
133+
#expect(await store.fetchAccumulator(path: storedPath)?.eventCount == 2)
125134

126135
let stableRecorder = CostUsageScanner.CodexScanWorkRecorder()
127136
options.codexScanWorkRecorderForTesting = stableRecorder
137+
conversionCounter.reset()
128138
let stable = CostUsageScanner.loadDailyReport(
129139
provider: .codex,
130140
since: day,
@@ -133,6 +143,7 @@ struct CostUsageStoreCutoverTests {
133143
options: options)
134144
#expect(stableRecorder.snapshot().usageRowsProcessed == 0)
135145
#expect(stableRecorder.snapshot().usageRowsRepriced == 0)
146+
#expect(conversionCounter.value == 0)
136147
#expect(stable.data == appended.data)
137148
#expect(stable.summary == appended.summary)
138149
}
@@ -183,3 +194,20 @@ struct CostUsageStoreCutoverTests {
183194
try handle.write(contentsOf: Data(contents.utf8))
184195
}
185196
}
197+
198+
private final class StoreSnapshotConversionCounter: @unchecked Sendable {
199+
private let lock = NSLock()
200+
private var count = 0
201+
202+
var value: Int {
203+
self.lock.withLock { self.count }
204+
}
205+
206+
func increment() {
207+
self.lock.withLock { self.count += 1 }
208+
}
209+
210+
func reset() {
211+
self.lock.withLock { self.count = 0 }
212+
}
213+
}

0 commit comments

Comments
 (0)