Skip to content
Merged
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
25 changes: 16 additions & 9 deletions Packages/WhoopStore/Sources/WhoopStore/Reads.swift
Original file line number Diff line number Diff line change
Expand Up @@ -348,18 +348,25 @@ extension WhoopStore {
/// Aggregate storage footprint: total decoded rows, raw batch count, total raw byteSize.
public func storageStats() async throws -> (decodedRows: Int, rawBatches: Int, rawBytes: Int) {
try syncRead { db in
let hr = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM hrSample") ?? 0
let rr = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM rrInterval") ?? 0
let ev = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM event") ?? 0
let bat = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM battery") ?? 0
let spo2 = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM spo2Sample") ?? 0
let skin = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM skinTempSample") ?? 0
let resp = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM respSample") ?? 0
let grav = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM gravitySample") ?? 0
// The COMPLETE set of accumulating decoded raw streams — KEEP IN SYNC with
// `TimestampHeal.rawTables` (its per-timestamp purge is the canonical list) and the Android
// `WhoopRepository.storageRowCounts`. Summed by iterating the list rather than a hand-written
// expression, because the old fixed sum silently under-reported: it omitted stepSample,
// ppgHrSample, sleepStateSample, ppgWaveformSample, rawImuSample and v18AuxSample — and a 4.0
// with PPG (ppgHrSample/ppgWaveformSample) or IMU capture (rawImuSample) banks millions of rows.
// Table names are compile-time constants (never user input), so the interpolation is safe.
let rawTables = ["hrSample", "rrInterval", "event", "battery",
"spo2Sample", "skinTempSample", "respSample", "gravitySample",
"stepSample", "ppgHrSample", "sleepStateSample", "ppgWaveformSample",
"rawImuSample", "v18AuxSample"]
var decoded = 0
for t in rawTables {
decoded += try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM \(t)") ?? 0
}
let batches = try Int.fetchOne(db, sql: "SELECT COUNT(*) FROM rawBatch") ?? 0
let bytes = try Int.fetchOne(db,
sql: "SELECT COALESCE(SUM(byteSize), 0) FROM rawBatch") ?? 0
return (hr + rr + ev + bat + spo2 + skin + resp + grav, batches, bytes)
return (decoded, batches, bytes)
}
}
}
21 changes: 16 additions & 5 deletions Packages/WhoopStore/Tests/WhoopStoreTests/ReadTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -256,22 +256,33 @@ final class ReadTests: XCTestCase {

func testStorageStats() async throws {
let store = try await seeded()
// Add one of each biometric stream so the count proves all 8 tables are summed.
// Add one of each decoded raw stream so the count proves ALL of them are summed — including the
// ones the old hand-listed footprint omitted (stepSample, sleepStateSample, ppgHrSample,
// ppgWaveformSample, v18AuxSample, and rawImuSample below).
_ = try await store.insert(
Streams(spo2: [SpO2Sample(ts: 400, red: 1, ir: 2)],
skinTemp: [SkinTempSample(ts: 400, raw: 930)],
resp: [RespSample(ts: 400, raw: 3073)],
gravity: [GravitySample(ts: 400, x: 0.1, y: 0.2, z: 0.3)]),
gravity: [GravitySample(ts: 400, x: 0.1, y: 0.2, z: 0.3)],
steps: [StepSample(ts: 400, counter: 5)],
sleepState: [SleepStateSample(ts: 400, state: 1)],
ppgHr: [PpgHrSample(ts: 400, bpm: 60, conf: 0.9)],
ppgWaveform: [PpgWaveformSample(ts: 400, samples: [1, 2, 3])],
v18Aux: [V18AuxSample(ts: 400, slotValues: [1, 2])]),
deviceId: "dev1")
_ = try await store.insertRawImu(deviceId: "dev1",
rows: [(ts: 400, cols: [Int16(1), Int16(2)])],
retentionRows: 1000)
try await store.enqueueRawBatch(
RawBatchMeta(batchId: "b1", deviceId: "dev1",
clockRef: ClockRef(device: 0, wall: 0), capturedAt: 1,
startTs: 0, endTs: 0, frameCount: 1, byteSize: 4),
frames: [[0xAA, 0x00, 0x01, 0x02]])
let stats = try await store.storageStats()
// dev1: 3 hr + 2 rr + 1 event + 1 battery + 1 spo2 + 1 skinTemp + 1 resp + 1 gravity = 11
// other: 1 hr = 1 → 12 decoded rows across all 8 tables.
XCTAssertEqual(stats.decodedRows, 12)
// dev1: 3 hr + 2 rr + 1 event + 1 battery + 1 spo2 + 1 skinTemp + 1 resp + 1 gravity
// + 1 step + 1 sleepState + 1 ppgHr + 1 ppgWaveform + 1 v18Aux + 1 rawImu = 17
// other: 1 hr = 1 → 18 decoded rows across all 14 raw tables.
XCTAssertEqual(stats.decodedRows, 18)
XCTAssertEqual(stats.rawBatches, 1)
XCTAssertEqual(stats.rawBytes, 4)
}
Expand Down
8 changes: 8 additions & 0 deletions android/app/src/main/java/com/noop/data/WhoopDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,14 @@ interface WhoopDao : DeviceRegistryDao {
@Query("SELECT COUNT(*) FROM spo2Sample") suspend fun countSpo2(): Int
@Query("SELECT COUNT(*) FROM skinTempSample") suspend fun countSkinTemp(): Int
@Query("SELECT COUNT(*) FROM stepSample") suspend fun countSteps(): Int
// The remaining accumulating decoded raw streams, so the Test-Centre footprint counts ALL of them
// (keep in sync with Swift storageStats / TimestampHeal's raw-table list). ppgHrSample (#156 v26
// PPG-derived HR), ppgWaveformSample (raw v26 optical waveform) and rawImuSample can each bank a lot.
@Query("SELECT COUNT(*) FROM ppgHrSample") suspend fun countPpgHr(): Int
@Query("SELECT COUNT(*) FROM sleepStateSample") suspend fun countSleepState(): Int
@Query("SELECT COUNT(*) FROM ppgWaveformSample") suspend fun countPpgWaveform(): Int
@Query("SELECT COUNT(*) FROM rawImuSample") suspend fun countRawImu(): Int
@Query("SELECT COUNT(*) FROM v18AuxSample") suspend fun countV18Aux(): Int
@Query("SELECT COUNT(*) FROM respSample") suspend fun countResp(): Int
@Query("SELECT COUNT(*) FROM gravitySample") suspend fun countGravity(): Int

Expand Down
6 changes: 6 additions & 0 deletions android/app/src/main/java/com/noop/data/WhoopRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,12 @@ class WhoopRepository(
"battery" to dao.countBattery(), "spo2" to dao.countSpo2(),
"skinTemp" to dao.countSkinTemp(), "steps" to dao.countSteps(),
"resp" to dao.countResp(), "gravity" to dao.countGravity(),
// The rest of the accumulating decoded raw streams, so the Test-Centre footprint counts ALL of
// them (keep in sync with Swift storageStats / TimestampHeal's raw-table list). ppgHr/ppgWaveform
// /rawImu can each be large.
"ppgHr" to dao.countPpgHr(), "sleepState" to dao.countSleepState(),
"ppgWaveform" to dao.countPpgWaveform(), "rawImu" to dao.countRawImu(),
"v18Aux" to dao.countV18Aux(),
)
}.getOrDefault(emptyMap())

Expand Down