@@ -591,7 +591,9 @@ rows exist". |
||
| 591 | 591 |
| 2026-06-04 | `d4de48c` | Copy unchanged daily aggregates inside SQLite. | First small-delta run before this commit completed in `23.6s` with `127/127` complete, `0` degraded, `CaptureModes: unchangedDelta=120, delta=7`, and `DeltaEvents: 11`. The hash change helped processing (`9.6s -> 8.1s`; Heart Rate `5.7s -> 4.2s`), but finalization stayed high (`11.4s`, Heart Rate `4.8s`). Changed daily aggregates were copying all previous daily rows through Swift before replacing affected buckets. This commit moved unchanged daily aggregate copying to SQLite `INSERT ... SELECT`, while affected buckets remain recalculated. Follow-up report completed in `21.1s` with `127/127` complete, `0` degraded, `CaptureModes: unchangedDelta=123, delta=4`, and `DeltaEvents: 50`. `SummedFinalizeElapsed` improved `11.4s -> 9.3s` and wall clock improved `23.6s -> 21.1s`; however Heart Rate finalize was still `5.0s` with `4` events, so this helped overall finalize cost but did not remove the high-volume changed-type floor. | |
| 592 | 592 |
| 2026-06-04 | `c9091de` | Include build identity in diagnostic reports. | The latest diagnostic report only included `App Version: 1.0(1)` near the end and did not include a commit/build source identifier. This makes it too easy to compare reports from the wrong installed binary. Diagnostics now emit `appVersion`, `buildFingerprint`, `sourceCommit`, and `sourceDirty` in `OPERATION METADATA`. `buildFingerprint` is derived from the installed executable and should change when a different binary is installed; `sourceCommit/sourceDirty` remain available for builds that inject those Info.plist keys. Expected signal: future pasted reports have enough build identity to detect wrong-version reports immediately. | |
| 593 | 593 |
| 2026-06-04 | `7e3b997` | Avoid summary full scans when delta safely replaces extremes. | Follow-up after `d4de48c` still showed `SummedFinalizeElapsed: 9.3s`, with Heart Rate finalize at `5.0s` despite only `4` Heart Rate delta events. `markVerification` already tries to update type summaries incrementally, but it fell back to a full visible-row aggregate scan whenever a removed row matched the previous earliest/latest/max, even if the same delta added a row that safely preserved or extended that extreme. The fallback is now narrower: full scan is still used when an extreme becomes unknown, but not when added rows replace the removed earliest/latest/max with an equivalent or stronger value. Follow-up report with `reportSchemaVersion: 3` and `buildFingerprint: 1.0(1)-1780603665-92064` completed in `21.0s`, with `127/127` complete, `CaptureModes: unchangedDelta=121, delta=6`, and `DeltaEvents: 20`. This was effectively flat versus the `d4de48c` report: wall `21.1s -> 21.0s`, processing `7.8s -> 7.4s`, finalize `9.3s -> 9.4s`, and Heart Rate finalize `5.0s -> 4.8s` while Heart Rate had `7` delta events. Conclusion: this optimization did not materially move the bottleneck; either the safe-extreme case did not trigger or type-summary fallback is no longer the dominant finalize cost. | |
| 594 |
-| 2026-06-04 | pending | Add archive finalization phase timings to diagnostics. | The post-`7e3b997` report proves the top-level `finalizeElapsed` bucket is too opaque for the next optimization. Diagnostics now split finalization into event-count/previous-summary lookup, type-summary work, daily-aggregate work, observation-type-run update, and residual other time. Expected signal: the next report should show whether Heart Rate's `~4.8s` finalize cost is mostly daily aggregate replacement, type summary work, event lookup, or unaccounted SQLite/transaction overhead. Use that result before attempting another finalize optimization. | |
|
| 594 |
+| 2026-06-04 | `f73f076` | Add archive finalization phase timings to diagnostics. | The post-`7e3b997` report proved the top-level `finalizeElapsed` bucket was too opaque for the next optimization. Diagnostics now split finalization into event-count/previous-summary lookup, type-summary work, daily-aggregate work, observation-type-run update, and residual other time. Follow-up report with `reportSchemaVersion: 3` and `buildFingerprint: 1.0(1)-1780606903-92064` completed in `22.6s`, with `127/127` complete, `CaptureModes: unchangedDelta=119, delta=8`, and `DeltaEvents: 27`. Finalization was `10.3s`: event-count/previous-summary lookup `1.8s`, type-summary `0.0s`, daily aggregates `7.3s`, run update `0.0s`, and other `1.2s`. Heart Rate had `9` delta events and spent `4.8s` finalizing, of which `3.8s` was daily aggregate work and `0.9s` was event-count/previous-summary lookup. Conclusion: the remaining finalize bottleneck is not type-summary fallback; it is changed-type daily aggregate maintenance, especially Heart Rate. | |
|
| 595 |
+| 2026-06-04 | older build / schema v2 | Captured large first-import baseline on a bigger device database. | Initial full-profile snapshot on an older build completed with `127/127` metrics and `8,421,978` records, but it used `reportSchemaVersion: 2` and has no build fingerprint. Treat it as a volume/shape baseline, not a precise current-build comparison. Wall clock was `166m10s`; summed fetch `5m19s`, processing `20m29s`, insert `137m31s`, finalize `1m53s`. The high-volume types dominated: Heart Rate `2,225,738` records and `46m57s` total (`39m16s` insert), Active Energy `1,914,449` records and `41m35s` total (`35m21s` insert), another high-volume type around `2,007,920` records and `41m20s` total (`34m29s` insert), and Basal Energy `1,116,074` records and `21m37s` total (`17m48s` insert). Conclusion: for clean first imports on very large databases, SQLite insert/index/write-path cost remains the central risk; incremental daily-aggregate optimization should not add first-import indexes without measurement. | |
|
| 596 |
+| 2026-06-05 | pending | Split daily aggregate finalization timings. | The first finalization phase report identified daily aggregate work as the remaining changed-type bottleneck, but `finalizeDailyAggregateElapsed` still mixed affected-bucket lookup, previous aggregate copy, destination delete, affected-bucket rebuild, replacement insert, and residual SQL/transaction overhead. Diagnostics now emit aggregate and per-type daily subphase fields: bucket lookup, copy, delete, rebuild, insert, and other. Expected signal: the next repeated full-profile report should say whether Heart Rate's `~3.8s` daily aggregate cost is mostly copying previous materialized rows or rebuilding affected buckets from visible samples. Do not add `sample_versions`/visibility date indexes until this split shows rebuild is dominant, because the `8.4M`-record first-import baseline shows insert/index overhead is already the large-database risk. | |
|
| 595 | 597 |
|
| 596 | 598 |
## Current Diagnosis |
| 597 | 599 |
|
@@ -700,6 +702,19 @@ The likely bottleneck is per-row SQLite work: |
||
| 700 | 702 |
inside finalization without phase timings; the diagnostic report now needs to |
| 701 | 703 |
show whether the remaining time is type-summary, daily-aggregate, |
| 702 | 704 |
event-count, run-update, or other overhead. |
| 705 |
+- Finalization phase timings identified the current small-delta bottleneck: |
|
| 706 |
+ type-summary work is effectively gone, while daily aggregate maintenance is |
|
| 707 |
+ `7.3s` of `10.3s` finalize on the latest full-profile repeated capture. Heart |
|
| 708 |
+ Rate spent `3.8s` in daily aggregates for only `9` delta events. The changed |
|
| 709 |
+ daily aggregate path still copies previous materialized rows for the type and |
|
| 710 |
+ then rebuilds affected buckets; distinguish copy cost from affected-bucket |
|
| 711 |
+ rebuild cost before adding new SQLite indexes, because first-import reports |
|
| 712 |
+ show insert/index overhead is already the dominant large-database risk. |
|
| 713 |
+- A large older-build first import on an `8.4M`-record database completed but |
|
| 714 |
+ took `166m10s`, with `137m31s` summed insert time. This confirms that full |
|
| 715 |
+ authorized backup volume can be much larger than the original 15-type test |
|
| 716 |
+ subset and that first-import write-path performance must remain a separate |
|
| 717 |
+ acceptance track from repeated incremental capture performance. |
|
| 703 | 718 |
|
| 704 | 719 |
## Open Issues / Observations |
| 705 | 720 |
|
@@ -758,11 +773,15 @@ Prioritize experiments in this order: |
||
| 758 | 773 |
identity unless the build provenance is otherwise certain. `sourceCommit` |
| 759 | 774 |
and `sourceDirty` are useful when present, but may be `unknown` for normal |
| 760 | 775 |
Xcode test installs. |
| 761 |
-8. Run a repeated full-profile capture with finalization phase timings. Compare |
|
| 762 |
- `SummedFinalizeTypeSummaryElapsed`, `SummedFinalizeDailyAggregateElapsed`, |
|
| 763 |
- `SummedFinalizeEventCountElapsed`, `SummedFinalizeRunUpdateElapsed`, and |
|
| 764 |
- `SummedFinalizeOtherElapsed`, plus the same per-metric fields for Heart Rate. |
|
| 765 |
- This determines the next real finalize optimization target. |
|
| 776 |
+8. Run a repeated full-profile capture with daily aggregate subphase timings. |
|
| 777 |
+ The current known target is Heart Rate small deltas: |
|
| 778 |
+ `finalizeDailyAggregateElapsed` was `3.8s` for `9` events. Compare |
|
| 779 |
+ `finalizeDailyAggregateCopyElapsed`, |
|
| 780 |
+ `finalizeDailyAggregateRebuildElapsed`, |
|
| 781 |
+ `finalizeDailyAggregateBucketLookupElapsed`, |
|
| 782 |
+ `finalizeDailyAggregateInsertElapsed`, and |
|
| 783 |
+ `finalizeDailyAggregateOtherElapsed` before adding indexes that could slow |
|
| 784 |
+ first import. |
|
| 766 | 785 |
9. Investigate replacing legacy compact `recordArchiveData` delta rebuild with |
| 767 | 786 |
a SQLite-derived capture-state/hash path. The current repeated full-profile |
| 768 | 787 |
reports still spend about `4s` processing Heart Rate for tiny deltas because |
@@ -2967,6 +2967,12 @@ private struct DistributionCaptureTimings: Sendable, Equatable {
|
||
| 2967 | 2967 |
var finalizeEventCountElapsedSeconds: TimeInterval = 0 |
| 2968 | 2968 |
var finalizeTypeSummaryElapsedSeconds: TimeInterval = 0 |
| 2969 | 2969 |
var finalizeDailyAggregateElapsedSeconds: TimeInterval = 0 |
| 2970 |
+ var finalizeDailyAggregateBucketLookupElapsedSeconds: TimeInterval = 0 |
|
| 2971 |
+ var finalizeDailyAggregateCopyElapsedSeconds: TimeInterval = 0 |
|
| 2972 |
+ var finalizeDailyAggregateDeleteElapsedSeconds: TimeInterval = 0 |
|
| 2973 |
+ var finalizeDailyAggregateRebuildElapsedSeconds: TimeInterval = 0 |
|
| 2974 |
+ var finalizeDailyAggregateInsertElapsedSeconds: TimeInterval = 0 |
|
| 2975 |
+ var finalizeDailyAggregateOtherElapsedSeconds: TimeInterval = 0 |
|
| 2970 | 2976 |
var finalizeRunUpdateElapsedSeconds: TimeInterval = 0 |
| 2971 | 2977 |
var finalizeOtherElapsedSeconds: TimeInterval = 0 |
| 2972 | 2978 |
|
@@ -2977,6 +2983,12 @@ private struct DistributionCaptureTimings: Sendable, Equatable {
|
||
| 2977 | 2983 |
finalizeEventCountElapsedSeconds += breakdown.eventCountElapsedSeconds |
| 2978 | 2984 |
finalizeTypeSummaryElapsedSeconds += breakdown.typeSummaryElapsedSeconds |
| 2979 | 2985 |
finalizeDailyAggregateElapsedSeconds += breakdown.dailyAggregateElapsedSeconds |
| 2986 |
+ finalizeDailyAggregateBucketLookupElapsedSeconds += breakdown.dailyAggregateBucketLookupElapsedSeconds |
|
| 2987 |
+ finalizeDailyAggregateCopyElapsedSeconds += breakdown.dailyAggregateCopyElapsedSeconds |
|
| 2988 |
+ finalizeDailyAggregateDeleteElapsedSeconds += breakdown.dailyAggregateDeleteElapsedSeconds |
|
| 2989 |
+ finalizeDailyAggregateRebuildElapsedSeconds += breakdown.dailyAggregateRebuildElapsedSeconds |
|
| 2990 |
+ finalizeDailyAggregateInsertElapsedSeconds += breakdown.dailyAggregateInsertElapsedSeconds |
|
| 2991 |
+ finalizeDailyAggregateOtherElapsedSeconds += breakdown.dailyAggregateOtherElapsedSeconds |
|
| 2980 | 2992 |
finalizeRunUpdateElapsedSeconds += breakdown.runUpdateElapsedSeconds |
| 2981 | 2993 |
finalizeOtherElapsedSeconds += breakdown.otherElapsedSeconds |
| 2982 | 2994 |
} |
@@ -2990,6 +3002,12 @@ private struct DistributionCaptureTimings: Sendable, Equatable {
|
||
| 2990 | 3002 |
finalizeEventCountElapsedSeconds: finalizeEventCountElapsedSeconds, |
| 2991 | 3003 |
finalizeTypeSummaryElapsedSeconds: finalizeTypeSummaryElapsedSeconds, |
| 2992 | 3004 |
finalizeDailyAggregateElapsedSeconds: finalizeDailyAggregateElapsedSeconds, |
| 3005 |
+ finalizeDailyAggregateBucketLookupElapsedSeconds: finalizeDailyAggregateBucketLookupElapsedSeconds, |
|
| 3006 |
+ finalizeDailyAggregateCopyElapsedSeconds: finalizeDailyAggregateCopyElapsedSeconds, |
|
| 3007 |
+ finalizeDailyAggregateDeleteElapsedSeconds: finalizeDailyAggregateDeleteElapsedSeconds, |
|
| 3008 |
+ finalizeDailyAggregateRebuildElapsedSeconds: finalizeDailyAggregateRebuildElapsedSeconds, |
|
| 3009 |
+ finalizeDailyAggregateInsertElapsedSeconds: finalizeDailyAggregateInsertElapsedSeconds, |
|
| 3010 |
+ finalizeDailyAggregateOtherElapsedSeconds: finalizeDailyAggregateOtherElapsedSeconds, |
|
| 2993 | 3011 |
finalizeRunUpdateElapsedSeconds: finalizeRunUpdateElapsedSeconds, |
| 2994 | 3012 |
finalizeOtherElapsedSeconds: finalizeOtherElapsedSeconds |
| 2995 | 3013 |
) |
@@ -36,11 +36,36 @@ struct HealthArchiveFinalizationBreakdown: Equatable, Sendable {
|
||
| 36 | 36 |
var eventCountElapsedSeconds: TimeInterval = 0 |
| 37 | 37 |
var typeSummaryElapsedSeconds: TimeInterval = 0 |
| 38 | 38 |
var dailyAggregateElapsedSeconds: TimeInterval = 0 |
| 39 |
+ var dailyAggregateBucketLookupElapsedSeconds: TimeInterval = 0 |
|
| 40 |
+ var dailyAggregateCopyElapsedSeconds: TimeInterval = 0 |
|
| 41 |
+ var dailyAggregateDeleteElapsedSeconds: TimeInterval = 0 |
|
| 42 |
+ var dailyAggregateRebuildElapsedSeconds: TimeInterval = 0 |
|
| 43 |
+ var dailyAggregateInsertElapsedSeconds: TimeInterval = 0 |
|
| 39 | 44 |
var runUpdateElapsedSeconds: TimeInterval = 0 |
| 40 | 45 |
var totalElapsedSeconds: TimeInterval = 0 |
| 41 | 46 |
|
| 42 | 47 |
static let zero = HealthArchiveFinalizationBreakdown() |
| 43 | 48 |
|
| 49 |
+ mutating func addDailyAggregateBreakdown(_ breakdown: HealthArchiveFinalizationBreakdown) {
|
|
| 50 |
+ dailyAggregateBucketLookupElapsedSeconds += breakdown.dailyAggregateBucketLookupElapsedSeconds |
|
| 51 |
+ dailyAggregateCopyElapsedSeconds += breakdown.dailyAggregateCopyElapsedSeconds |
|
| 52 |
+ dailyAggregateDeleteElapsedSeconds += breakdown.dailyAggregateDeleteElapsedSeconds |
|
| 53 |
+ dailyAggregateRebuildElapsedSeconds += breakdown.dailyAggregateRebuildElapsedSeconds |
|
| 54 |
+ dailyAggregateInsertElapsedSeconds += breakdown.dailyAggregateInsertElapsedSeconds |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ var dailyAggregateAccountedElapsedSeconds: TimeInterval {
|
|
| 58 |
+ dailyAggregateBucketLookupElapsedSeconds |
|
| 59 |
+ + dailyAggregateCopyElapsedSeconds |
|
| 60 |
+ + dailyAggregateDeleteElapsedSeconds |
|
| 61 |
+ + dailyAggregateRebuildElapsedSeconds |
|
| 62 |
+ + dailyAggregateInsertElapsedSeconds |
|
| 63 |
+ } |
|
| 64 |
+ |
|
| 65 |
+ var dailyAggregateOtherElapsedSeconds: TimeInterval {
|
|
| 66 |
+ max(0, dailyAggregateElapsedSeconds - dailyAggregateAccountedElapsedSeconds) |
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 44 | 69 |
var accountedElapsedSeconds: TimeInterval {
|
| 45 | 70 |
eventCountElapsedSeconds |
| 46 | 71 |
+ typeSummaryElapsedSeconds |
@@ -1981,7 +1981,7 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 1981 | 1981 |
) |
| 1982 | 1982 |
if rebuildDerivedState {
|
| 1983 | 1983 |
try rebuildTypeSummary(observationID: observationID, sampleTypeID: sampleTypeID, db: db) |
| 1984 |
- try rebuildDailyAggregates( |
|
| 1984 |
+ _ = try rebuildDailyAggregates( |
|
| 1985 | 1985 |
observationID: observationID, |
| 1986 | 1986 |
sampleTypeID: sampleTypeID, |
| 1987 | 1987 |
observedAt: observedAt, |
@@ -2050,12 +2050,13 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 2050 | 2050 |
) |
| 2051 | 2051 |
breakdown.typeSummaryElapsedSeconds += Date().timeIntervalSince(summaryStartedAt) |
| 2052 | 2052 |
let dailyStartedAt = Date() |
| 2053 |
- try copyDailyAggregates( |
|
| 2053 |
+ let dailyBreakdown = try copyDailyAggregates( |
|
| 2054 | 2054 |
fromObservationID: previousSummary.observationID, |
| 2055 | 2055 |
toObservationID: observationID, |
| 2056 | 2056 |
sampleTypeID: sampleTypeID, |
| 2057 | 2057 |
db: db |
| 2058 | 2058 |
) |
| 2059 |
+ breakdown.addDailyAggregateBreakdown(dailyBreakdown) |
|
| 2059 | 2060 |
breakdown.dailyAggregateElapsedSeconds += Date().timeIntervalSince(dailyStartedAt) |
| 2060 | 2061 |
breakdown.totalElapsedSeconds = Date().timeIntervalSince(startedAt) |
| 2061 | 2062 |
return breakdown |
@@ -2093,25 +2094,29 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 2093 | 2094 |
) |
| 2094 | 2095 |
breakdown.typeSummaryElapsedSeconds += Date().timeIntervalSince(summaryUpsertStartedAt) |
| 2095 | 2096 |
let dailyStartedAt = Date() |
| 2096 |
- if let previousSummary, |
|
| 2097 |
- try replaceChangedDailyAggregates( |
|
| 2098 |
- fromObservationID: previousSummary.observationID, |
|
| 2099 |
- toObservationID: observationID, |
|
| 2100 |
- sampleTypeID: sampleTypeID, |
|
| 2101 |
- observedAt: verifiedAt, |
|
| 2102 |
- db: db |
|
| 2103 |
- ) {
|
|
| 2104 |
- breakdown.dailyAggregateElapsedSeconds += Date().timeIntervalSince(dailyStartedAt) |
|
| 2105 |
- breakdown.totalElapsedSeconds = Date().timeIntervalSince(startedAt) |
|
| 2106 |
- return breakdown |
|
| 2097 |
+ if let previousSummary {
|
|
| 2098 |
+ let replacement = try replaceChangedDailyAggregates( |
|
| 2099 |
+ fromObservationID: previousSummary.observationID, |
|
| 2100 |
+ toObservationID: observationID, |
|
| 2101 |
+ sampleTypeID: sampleTypeID, |
|
| 2102 |
+ observedAt: verifiedAt, |
|
| 2103 |
+ db: db |
|
| 2104 |
+ ) |
|
| 2105 |
+ breakdown.addDailyAggregateBreakdown(replacement.breakdown) |
|
| 2106 |
+ if replacement.replaced {
|
|
| 2107 |
+ breakdown.dailyAggregateElapsedSeconds += Date().timeIntervalSince(dailyStartedAt) |
|
| 2108 |
+ breakdown.totalElapsedSeconds = Date().timeIntervalSince(startedAt) |
|
| 2109 |
+ return breakdown |
|
| 2110 |
+ } |
|
| 2107 | 2111 |
} |
| 2108 | 2112 |
|
| 2109 |
- try rebuildDailyAggregates( |
|
| 2113 |
+ let dailyBreakdown = try rebuildDailyAggregates( |
|
| 2110 | 2114 |
observationID: observationID, |
| 2111 | 2115 |
sampleTypeID: sampleTypeID, |
| 2112 | 2116 |
observedAt: verifiedAt, |
| 2113 | 2117 |
db: db |
| 2114 | 2118 |
) |
| 2119 |
+ breakdown.addDailyAggregateBreakdown(dailyBreakdown) |
|
| 2115 | 2120 |
breakdown.dailyAggregateElapsedSeconds += Date().timeIntervalSince(dailyStartedAt) |
| 2116 | 2121 |
breakdown.totalElapsedSeconds = Date().timeIntervalSince(startedAt) |
| 2117 | 2122 |
return breakdown |
@@ -2174,12 +2179,13 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 2174 | 2179 |
) |
| 2175 | 2180 |
breakdown.typeSummaryElapsedSeconds += Date().timeIntervalSince(summaryStartedAt) |
| 2176 | 2181 |
let dailyStartedAt = Date() |
| 2177 |
- try copyDailyAggregates( |
|
| 2182 |
+ let dailyBreakdown = try copyDailyAggregates( |
|
| 2178 | 2183 |
fromObservationID: previousSummary.observationID, |
| 2179 | 2184 |
toObservationID: observationID, |
| 2180 | 2185 |
sampleTypeID: sampleTypeID, |
| 2181 | 2186 |
db: db |
| 2182 | 2187 |
) |
| 2188 |
+ breakdown.addDailyAggregateBreakdown(dailyBreakdown) |
|
| 2183 | 2189 |
breakdown.dailyAggregateElapsedSeconds += Date().timeIntervalSince(dailyStartedAt) |
| 2184 | 2190 |
breakdown.totalElapsedSeconds = Date().timeIntervalSince(startedAt) |
| 2185 | 2191 |
return breakdown |
@@ -2272,7 +2278,7 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 2272 | 2278 |
) |
| 2273 | 2279 |
if rebuildDerivedState {
|
| 2274 | 2280 |
try rebuildTypeSummary(observationID: observationID, sampleTypeID: sampleTypeID, db: db) |
| 2275 |
- try rebuildDailyAggregates( |
|
| 2281 |
+ _ = try rebuildDailyAggregates( |
|
| 2276 | 2282 |
observationID: observationID, |
| 2277 | 2283 |
sampleTypeID: sampleTypeID, |
| 2278 | 2284 |
observedAt: observedMissingAt, |
@@ -3468,8 +3474,10 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 3468 | 3474 |
sampleTypeID: Int64, |
| 3469 | 3475 |
observedAt: Date, |
| 3470 | 3476 |
db: OpaquePointer? |
| 3471 |
- ) throws {
|
|
| 3477 |
+ ) throws -> HealthArchiveFinalizationBreakdown {
|
|
| 3478 |
+ var breakdown = HealthArchiveFinalizationBreakdown.zero |
|
| 3472 | 3479 |
let secondsFromGMT = TimeZone.current.secondsFromGMT(for: observedAt) |
| 3480 |
+ let deleteStartedAt = Date() |
|
| 3473 | 3481 |
try withStatement( |
| 3474 | 3482 |
"DELETE FROM daily_type_aggregates WHERE observation_id = ? AND sample_type_id = ?", |
| 3475 | 3483 |
db: db |
@@ -3480,9 +3488,15 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 3480 | 3488 |
throw SQLiteHealthArchiveStoreError.stepFailed(lastErrorMessage(db)) |
| 3481 | 3489 |
} |
| 3482 | 3490 |
} |
| 3491 |
+ breakdown.dailyAggregateDeleteElapsedSeconds += Date().timeIntervalSince(deleteStartedAt) |
|
| 3483 | 3492 |
|
| 3493 |
+ let rebuildStartedAt = Date() |
|
| 3484 | 3494 |
let rows = try dailyAggregateRows(sampleTypeID: sampleTypeID, secondsFromGMT: secondsFromGMT, db: db) |
| 3495 |
+ breakdown.dailyAggregateRebuildElapsedSeconds += Date().timeIntervalSince(rebuildStartedAt) |
|
| 3496 |
+ let insertStartedAt = Date() |
|
| 3485 | 3497 |
try insertDailyAggregateRows(rows, observationID: observationID, sampleTypeID: sampleTypeID, db: db) |
| 3498 |
+ breakdown.dailyAggregateInsertElapsedSeconds += Date().timeIntervalSince(insertStartedAt) |
|
| 3499 |
+ return breakdown |
|
| 3486 | 3500 |
} |
| 3487 | 3501 |
|
| 3488 | 3502 |
private func copyDailyAggregates( |
@@ -3490,7 +3504,9 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 3490 | 3504 |
toObservationID: Int64, |
| 3491 | 3505 |
sampleTypeID: Int64, |
| 3492 | 3506 |
db: OpaquePointer? |
| 3493 |
- ) throws {
|
|
| 3507 |
+ ) throws -> HealthArchiveFinalizationBreakdown {
|
|
| 3508 |
+ var breakdown = HealthArchiveFinalizationBreakdown.zero |
|
| 3509 |
+ let deleteStartedAt = Date() |
|
| 3494 | 3510 |
try withStatement( |
| 3495 | 3511 |
"DELETE FROM daily_type_aggregates WHERE observation_id = ? AND sample_type_id = ?", |
| 3496 | 3512 |
db: db |
@@ -3501,13 +3517,17 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 3501 | 3517 |
throw SQLiteHealthArchiveStoreError.stepFailed(lastErrorMessage(db)) |
| 3502 | 3518 |
} |
| 3503 | 3519 |
} |
| 3520 |
+ breakdown.dailyAggregateDeleteElapsedSeconds += Date().timeIntervalSince(deleteStartedAt) |
|
| 3504 | 3521 |
|
| 3522 |
+ let copyStartedAt = Date() |
|
| 3505 | 3523 |
try copyDailyAggregateRows( |
| 3506 | 3524 |
fromObservationID: fromObservationID, |
| 3507 | 3525 |
toObservationID: toObservationID, |
| 3508 | 3526 |
sampleTypeID: sampleTypeID, |
| 3509 | 3527 |
db: db |
| 3510 | 3528 |
) |
| 3529 |
+ breakdown.dailyAggregateCopyElapsedSeconds += Date().timeIntervalSince(copyStartedAt) |
|
| 3530 |
+ return breakdown |
|
| 3511 | 3531 |
} |
| 3512 | 3532 |
|
| 3513 | 3533 |
private func replaceChangedDailyAggregates( |
@@ -3516,41 +3536,51 @@ actor SQLiteHealthArchiveStore: HealthArchiveStore {
|
||
| 3516 | 3536 |
sampleTypeID: Int64, |
| 3517 | 3537 |
observedAt: Date, |
| 3518 | 3538 |
db: OpaquePointer? |
| 3519 |
- ) throws -> Bool {
|
|
| 3539 |
+ ) throws -> (replaced: Bool, breakdown: HealthArchiveFinalizationBreakdown) {
|
|
| 3540 |
+ var breakdown = HealthArchiveFinalizationBreakdown.zero |
|
| 3520 | 3541 |
let secondsFromGMT = TimeZone.current.secondsFromGMT(for: observedAt) |
| 3542 |
+ let bucketLookupStartedAt = Date() |
|
| 3521 | 3543 |
let affectedBuckets = try changedDailyAggregateBuckets( |
| 3522 | 3544 |
observationID: toObservationID, |
| 3523 | 3545 |
sampleTypeID: sampleTypeID, |
| 3524 | 3546 |
secondsFromGMT: secondsFromGMT, |
| 3525 | 3547 |
db: db |
| 3526 | 3548 |
) |
| 3527 |
- guard !affectedBuckets.isEmpty else { return false }
|
|
| 3549 |
+ breakdown.dailyAggregateBucketLookupElapsedSeconds += Date().timeIntervalSince(bucketLookupStartedAt) |
|
| 3550 |
+ guard !affectedBuckets.isEmpty else { return (false, breakdown) }
|
|
| 3528 | 3551 |
|
| 3529 |
- try copyDailyAggregates( |
|
| 3552 |
+ let copyBreakdown = try copyDailyAggregates( |
|
| 3530 | 3553 |
fromObservationID: fromObservationID, |
| 3531 | 3554 |
toObservationID: toObservationID, |
| 3532 | 3555 |
sampleTypeID: sampleTypeID, |
| 3533 | 3556 |
db: db |
| 3534 | 3557 |
) |
| 3558 |
+ breakdown.addDailyAggregateBreakdown(copyBreakdown) |
|
| 3559 |
+ let deleteStartedAt = Date() |
|
| 3535 | 3560 |
try deleteDailyAggregateRows( |
| 3536 | 3561 |
observationID: toObservationID, |
| 3537 | 3562 |
sampleTypeID: sampleTypeID, |
| 3538 | 3563 |
bucketStarts: affectedBuckets, |
| 3539 | 3564 |
db: db |
| 3540 | 3565 |
) |
| 3566 |
+ breakdown.dailyAggregateDeleteElapsedSeconds += Date().timeIntervalSince(deleteStartedAt) |
|
| 3567 |
+ let rebuildStartedAt = Date() |
|
| 3541 | 3568 |
let replacementRows = try dailyAggregateRows( |
| 3542 | 3569 |
sampleTypeID: sampleTypeID, |
| 3543 | 3570 |
secondsFromGMT: secondsFromGMT, |
| 3544 | 3571 |
bucketStarts: affectedBuckets, |
| 3545 | 3572 |
db: db |
| 3546 | 3573 |
) |
| 3574 |
+ breakdown.dailyAggregateRebuildElapsedSeconds += Date().timeIntervalSince(rebuildStartedAt) |
|
| 3575 |
+ let insertStartedAt = Date() |
|
| 3547 | 3576 |
try insertDailyAggregateRows( |
| 3548 | 3577 |
replacementRows, |
| 3549 | 3578 |
observationID: toObservationID, |
| 3550 | 3579 |
sampleTypeID: sampleTypeID, |
| 3551 | 3580 |
db: db |
| 3552 | 3581 |
) |
| 3553 |
- return true |
|
| 3582 |
+ breakdown.dailyAggregateInsertElapsedSeconds += Date().timeIntervalSince(insertStartedAt) |
|
| 3583 |
+ return (true, breakdown) |
|
| 3554 | 3584 |
} |
| 3555 | 3585 |
|
| 3556 | 3586 |
private func copyDailyAggregateRows( |
@@ -8,6 +8,12 @@ struct ImportTimingBreakdown: Sendable, Equatable {
|
||
| 8 | 8 |
var finalizeEventCountElapsedSeconds: TimeInterval = 0 |
| 9 | 9 |
var finalizeTypeSummaryElapsedSeconds: TimeInterval = 0 |
| 10 | 10 |
var finalizeDailyAggregateElapsedSeconds: TimeInterval = 0 |
| 11 |
+ var finalizeDailyAggregateBucketLookupElapsedSeconds: TimeInterval = 0 |
|
| 12 |
+ var finalizeDailyAggregateCopyElapsedSeconds: TimeInterval = 0 |
|
| 13 |
+ var finalizeDailyAggregateDeleteElapsedSeconds: TimeInterval = 0 |
|
| 14 |
+ var finalizeDailyAggregateRebuildElapsedSeconds: TimeInterval = 0 |
|
| 15 |
+ var finalizeDailyAggregateInsertElapsedSeconds: TimeInterval = 0 |
|
| 16 |
+ var finalizeDailyAggregateOtherElapsedSeconds: TimeInterval = 0 |
|
| 11 | 17 |
var finalizeRunUpdateElapsedSeconds: TimeInterval = 0 |
| 12 | 18 |
var finalizeOtherElapsedSeconds: TimeInterval = 0 |
| 13 | 19 |
|
@@ -24,6 +30,15 @@ struct ImportTimingBreakdown: Sendable, Equatable {
|
||
| 24 | 30 |
+ finalizeRunUpdateElapsedSeconds |
| 25 | 31 |
+ finalizeOtherElapsedSeconds |
| 26 | 32 |
} |
| 33 |
+ |
|
| 34 |
+ var finalizeDailyAggregateAccountedElapsedSeconds: TimeInterval {
|
|
| 35 |
+ finalizeDailyAggregateBucketLookupElapsedSeconds |
|
| 36 |
+ + finalizeDailyAggregateCopyElapsedSeconds |
|
| 37 |
+ + finalizeDailyAggregateDeleteElapsedSeconds |
|
| 38 |
+ + finalizeDailyAggregateRebuildElapsedSeconds |
|
| 39 |
+ + finalizeDailyAggregateInsertElapsedSeconds |
|
| 40 |
+ + finalizeDailyAggregateOtherElapsedSeconds |
|
| 41 |
+ } |
|
| 27 | 42 |
} |
| 28 | 43 |
|
| 29 | 44 |
struct HealthKitAPICallResult: Sendable, Equatable {
|
@@ -166,6 +181,12 @@ final class SnapshotFetchProgress {
|
||
| 166 | 181 |
combined.finalizeEventCountElapsedSeconds += type.timingBreakdown.finalizeEventCountElapsedSeconds |
| 167 | 182 |
combined.finalizeTypeSummaryElapsedSeconds += type.timingBreakdown.finalizeTypeSummaryElapsedSeconds |
| 168 | 183 |
combined.finalizeDailyAggregateElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateElapsedSeconds |
| 184 |
+ combined.finalizeDailyAggregateBucketLookupElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateBucketLookupElapsedSeconds |
|
| 185 |
+ combined.finalizeDailyAggregateCopyElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateCopyElapsedSeconds |
|
| 186 |
+ combined.finalizeDailyAggregateDeleteElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateDeleteElapsedSeconds |
|
| 187 |
+ combined.finalizeDailyAggregateRebuildElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateRebuildElapsedSeconds |
|
| 188 |
+ combined.finalizeDailyAggregateInsertElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateInsertElapsedSeconds |
|
| 189 |
+ combined.finalizeDailyAggregateOtherElapsedSeconds += type.timingBreakdown.finalizeDailyAggregateOtherElapsedSeconds |
|
| 169 | 190 |
combined.finalizeRunUpdateElapsedSeconds += type.timingBreakdown.finalizeRunUpdateElapsedSeconds |
| 170 | 191 |
combined.finalizeOtherElapsedSeconds += type.timingBreakdown.finalizeOtherElapsedSeconds |
| 171 | 192 |
return combined |
@@ -398,6 +398,14 @@ struct DashboardView: View {
|
||
| 398 | 398 |
lines.append("SummedFinalizeEventCountElapsed: \(formatDuration(aggregateTiming.finalizeEventCountElapsedSeconds))")
|
| 399 | 399 |
lines.append("SummedFinalizeTypeSummaryElapsed: \(formatDuration(aggregateTiming.finalizeTypeSummaryElapsedSeconds))")
|
| 400 | 400 |
lines.append("SummedFinalizeDailyAggregateElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateElapsedSeconds))")
|
| 401 |
+ if aggregateTiming.finalizeDailyAggregateAccountedElapsedSeconds > 0 {
|
|
| 402 |
+ lines.append("SummedFinalizeDailyAggregateBucketLookupElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateBucketLookupElapsedSeconds))")
|
|
| 403 |
+ lines.append("SummedFinalizeDailyAggregateCopyElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateCopyElapsedSeconds))")
|
|
| 404 |
+ lines.append("SummedFinalizeDailyAggregateDeleteElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateDeleteElapsedSeconds))")
|
|
| 405 |
+ lines.append("SummedFinalizeDailyAggregateRebuildElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateRebuildElapsedSeconds))")
|
|
| 406 |
+ lines.append("SummedFinalizeDailyAggregateInsertElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateInsertElapsedSeconds))")
|
|
| 407 |
+ lines.append("SummedFinalizeDailyAggregateOtherElapsed: \(formatDuration(aggregateTiming.finalizeDailyAggregateOtherElapsedSeconds))")
|
|
| 408 |
+ } |
|
| 401 | 409 |
lines.append("SummedFinalizeRunUpdateElapsed: \(formatDuration(aggregateTiming.finalizeRunUpdateElapsedSeconds))")
|
| 402 | 410 |
lines.append("SummedFinalizeOtherElapsed: \(formatDuration(aggregateTiming.finalizeOtherElapsedSeconds))")
|
| 403 | 411 |
} |
@@ -452,6 +460,14 @@ struct DashboardView: View {
|
||
| 452 | 460 |
lines.append(" finalizeEventCountElapsed: \(formatDuration(type.timingBreakdown.finalizeEventCountElapsedSeconds))")
|
| 453 | 461 |
lines.append(" finalizeTypeSummaryElapsed: \(formatDuration(type.timingBreakdown.finalizeTypeSummaryElapsedSeconds))")
|
| 454 | 462 |
lines.append(" finalizeDailyAggregateElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateElapsedSeconds))")
|
| 463 |
+ if type.timingBreakdown.finalizeDailyAggregateAccountedElapsedSeconds > 0 {
|
|
| 464 |
+ lines.append(" finalizeDailyAggregateBucketLookupElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateBucketLookupElapsedSeconds))")
|
|
| 465 |
+ lines.append(" finalizeDailyAggregateCopyElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateCopyElapsedSeconds))")
|
|
| 466 |
+ lines.append(" finalizeDailyAggregateDeleteElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateDeleteElapsedSeconds))")
|
|
| 467 |
+ lines.append(" finalizeDailyAggregateRebuildElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateRebuildElapsedSeconds))")
|
|
| 468 |
+ lines.append(" finalizeDailyAggregateInsertElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateInsertElapsedSeconds))")
|
|
| 469 |
+ lines.append(" finalizeDailyAggregateOtherElapsed: \(formatDuration(type.timingBreakdown.finalizeDailyAggregateOtherElapsedSeconds))")
|
|
| 470 |
+ } |
|
| 455 | 471 |
lines.append(" finalizeRunUpdateElapsed: \(formatDuration(type.timingBreakdown.finalizeRunUpdateElapsedSeconds))")
|
| 456 | 472 |
lines.append(" finalizeOtherElapsed: \(formatDuration(type.timingBreakdown.finalizeOtherElapsedSeconds))")
|
| 457 | 473 |
} |