cctally 1.103.0 → 1.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +6 -6
- package/bin/_cctally_alerts.py +104 -10
- package/bin/_cctally_cache.py +193 -71
- package/bin/_cctally_config.py +62 -2
- package/bin/_cctally_core.py +62 -1
- package/bin/_cctally_dashboard.py +694 -70
- package/bin/_cctally_dashboard_conversation.py +400 -6
- package/bin/_cctally_dashboard_envelope.py +336 -14
- package/bin/_cctally_dashboard_perf.py +93 -0
- package/bin/_cctally_dashboard_share.py +56 -25
- package/bin/_cctally_dashboard_sources.py +988 -94
- package/bin/_cctally_db.py +30 -3
- package/bin/_cctally_diagnosis_sources.py +699 -186
- package/bin/_cctally_doctor.py +77 -0
- package/bin/_cctally_forecast.py +932 -51
- package/bin/_cctally_journal.py +430 -24
- package/bin/_cctally_parser.py +66 -0
- package/bin/_cctally_project.py +535 -10
- package/bin/_cctally_quota.py +28 -0
- package/bin/_cctally_quota_calibration.py +146 -0
- package/bin/_cctally_quota_model.py +2142 -0
- package/bin/_cctally_record.py +204 -30
- package/bin/_cctally_share.py +16 -8
- package/bin/_cctally_statusline.py +34 -0
- package/bin/_cctally_tui.py +531 -115
- package/bin/_lib_codex_conversation_query.py +258 -12
- package/bin/_lib_codex_hooks.py +26 -0
- package/bin/_lib_conversation_query.py +145 -15
- package/bin/_lib_dashboard_json.py +105 -0
- package/bin/_lib_dashboard_settings_contract.py +2 -0
- package/bin/_lib_diagnosis.py +21 -2
- package/bin/_lib_doctor.py +215 -1
- package/bin/_lib_forecast.py +337 -43
- package/bin/_lib_ingest_frontier.py +889 -0
- package/bin/_lib_meter_rate_change.py +360 -0
- package/bin/_lib_perf.py +22 -0
- package/bin/_lib_pricing.py +30 -3
- package/bin/_lib_quota_calibration.py +311 -0
- package/bin/_lib_quota_copy.py +157 -0
- package/bin/_lib_quota_model.py +2520 -0
- package/bin/_lib_rate_change_delivery.py +119 -0
- package/bin/_lib_record.py +50 -0
- package/bin/_lib_rederive.py +10 -0
- package/bin/_lib_render.py +6 -0
- package/bin/_lib_retained_size.py +187 -0
- package/bin/_lib_share_templates.py +37 -5
- package/bin/_lib_snapshot_cache.py +504 -17
- package/bin/_lib_statusline.py +200 -2
- package/bin/_lib_tick_stats.py +28 -4
- package/bin/_lib_view_models.py +30 -12
- package/bin/cctally +32 -0
- package/dashboard/static/assets/ConversationsView-DA4j7Yso.js +72 -0
- package/dashboard/static/assets/DoctorModal-D4PbVnbE.js +1 -0
- package/dashboard/static/assets/ModalRoot-D4oSPxPp.js +1 -0
- package/dashboard/static/assets/ProjectsDrillPanel-DkPlB9JH.js +1 -0
- package/dashboard/static/assets/SourceDetailModal-BE7FONsS.js +1 -0
- package/dashboard/static/assets/UpdateModal-BJuI8glf.js +7 -0
- package/dashboard/static/assets/index-klO46NcU.css +1 -0
- package/dashboard/static/assets/index-w_bINkJ8.js +13 -0
- package/dashboard/static/assets/outlineNavigation-zDVm6Hdd.js +9 -0
- package/dashboard/static/assets/useKeymap-CJ-Pi17D.js +1 -0
- package/dashboard/static/dashboard.html +3 -2
- package/package.json +10 -1
- package/dashboard/static/assets/index-Di2hljvB.css +0 -1
- package/dashboard/static/assets/index-XYCIWjVG.js +0 -97
|
@@ -4,9 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
import datetime as dt
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
|
+
import os
|
|
7
8
|
import pathlib
|
|
8
9
|
import sqlite3
|
|
9
10
|
import sys
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
from collections import OrderedDict
|
|
10
14
|
from collections.abc import Mapping
|
|
11
15
|
from dataclasses import dataclass, replace
|
|
12
16
|
from types import MappingProxyType, SimpleNamespace
|
|
@@ -32,7 +36,14 @@ from _cctally_source_analytics import (
|
|
|
32
36
|
)
|
|
33
37
|
import _lib_log
|
|
34
38
|
import _lib_accounts
|
|
39
|
+
import _lib_perf
|
|
35
40
|
import _lib_snapshot_cache
|
|
41
|
+
from _lib_retained_size import (
|
|
42
|
+
RETAINED_SIZE_WORK_LOCK,
|
|
43
|
+
RETAINED_SIZE_WORKER_DUTY,
|
|
44
|
+
RetainedSizeCancelled,
|
|
45
|
+
retained_size_bytes,
|
|
46
|
+
)
|
|
36
47
|
from _lib_dashboard_sources import (
|
|
37
48
|
CapabilityRecord,
|
|
38
49
|
ProjectionCoherence,
|
|
@@ -86,7 +97,86 @@ from _lib_view_models import (
|
|
|
86
97
|
UTC = dt.timezone.utc
|
|
87
98
|
|
|
88
99
|
|
|
89
|
-
|
|
100
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY = True
|
|
101
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK = threading.Lock()
|
|
102
|
+
_CODEX_SOURCE_ACCELERATOR_GENERATION = 0
|
|
103
|
+
_CACHE_MISSING = object()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _mark_codex_source_accelerators_dirty() -> None:
|
|
107
|
+
"""Record the first mutation since the last aggregate admission pass."""
|
|
108
|
+
global _CODEX_SOURCE_ACCELERATOR_DIRTY
|
|
109
|
+
global _CODEX_SOURCE_ACCELERATOR_GENERATION
|
|
110
|
+
if _CODEX_SOURCE_ACCELERATOR_DIRTY:
|
|
111
|
+
return
|
|
112
|
+
with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
|
|
113
|
+
if not _CODEX_SOURCE_ACCELERATOR_DIRTY:
|
|
114
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY = True
|
|
115
|
+
_CODEX_SOURCE_ACCELERATOR_GENERATION += 1
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class _ObservedCacheMixin:
|
|
119
|
+
"""Make every retained-cache mutation visible to aggregate admission."""
|
|
120
|
+
|
|
121
|
+
def __setitem__(self, key, value):
|
|
122
|
+
super().__setitem__(key, value)
|
|
123
|
+
_mark_codex_source_accelerators_dirty()
|
|
124
|
+
|
|
125
|
+
def __delitem__(self, key):
|
|
126
|
+
super().__delitem__(key)
|
|
127
|
+
_mark_codex_source_accelerators_dirty()
|
|
128
|
+
|
|
129
|
+
def clear(self):
|
|
130
|
+
if self:
|
|
131
|
+
super().clear()
|
|
132
|
+
_mark_codex_source_accelerators_dirty()
|
|
133
|
+
|
|
134
|
+
def pop(self, key, default=_CACHE_MISSING):
|
|
135
|
+
existed = key in self
|
|
136
|
+
if default is _CACHE_MISSING:
|
|
137
|
+
value = super().pop(key)
|
|
138
|
+
else:
|
|
139
|
+
value = super().pop(key, default)
|
|
140
|
+
if existed:
|
|
141
|
+
_mark_codex_source_accelerators_dirty()
|
|
142
|
+
return value
|
|
143
|
+
|
|
144
|
+
def popitem(self, *args, **kwargs):
|
|
145
|
+
value = super().popitem(*args, **kwargs)
|
|
146
|
+
_mark_codex_source_accelerators_dirty()
|
|
147
|
+
return value
|
|
148
|
+
|
|
149
|
+
def setdefault(self, key, default=None):
|
|
150
|
+
existed = key in self
|
|
151
|
+
value = super().setdefault(key, default)
|
|
152
|
+
if not existed:
|
|
153
|
+
_mark_codex_source_accelerators_dirty()
|
|
154
|
+
return value
|
|
155
|
+
|
|
156
|
+
def update(self, *args, **kwargs):
|
|
157
|
+
before = len(self)
|
|
158
|
+
super().update(*args, **kwargs)
|
|
159
|
+
if args or kwargs or len(self) != before:
|
|
160
|
+
_mark_codex_source_accelerators_dirty()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class _ObservedDict(_ObservedCacheMixin, dict):
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class _ObservedOrderedDict(_ObservedCacheMixin, OrderedDict):
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
_CODEX_QUOTA_OBSERVATION_MAX_ENTRIES = 32
|
|
172
|
+
_CODEX_QUOTA_OBSERVATION_MAX_BYTES = 64 * 1024 * 1024
|
|
173
|
+
_CODEX_QUOTA_OBSERVATION_CACHE: "OrderedDict[object, tuple[object, ...]]" = (
|
|
174
|
+
_ObservedOrderedDict())
|
|
175
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_SIZES: dict[object, int] = {}
|
|
176
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_BYTES = 0
|
|
177
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS = 0
|
|
178
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS = 0
|
|
179
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_LOCK = threading.Lock()
|
|
90
180
|
|
|
91
181
|
|
|
92
182
|
def reset_codex_quota_observation_cache() -> None:
|
|
@@ -98,7 +188,51 @@ def reset_codex_quota_observation_cache() -> None:
|
|
|
98
188
|
cold-reference comparisons use, and `_codex_source_caches` is what the
|
|
99
189
|
build's own checkpoint/restore goes through.
|
|
100
190
|
"""
|
|
101
|
-
|
|
191
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_BYTES
|
|
192
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS
|
|
193
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS
|
|
194
|
+
with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
|
|
195
|
+
_CODEX_QUOTA_OBSERVATION_CACHE.clear()
|
|
196
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_SIZES.clear()
|
|
197
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_BYTES = 0
|
|
198
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS = 0
|
|
199
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS = 0
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def codex_quota_observation_cache_stats() -> Mapping[str, int]:
|
|
203
|
+
"""Retained-byte admission and eviction evidence for the #618 memo."""
|
|
204
|
+
with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
|
|
205
|
+
return MappingProxyType({
|
|
206
|
+
"estimatedBytes": int(_CODEX_QUOTA_OBSERVATION_CACHE_BYTES),
|
|
207
|
+
"maxBytes": int(_CODEX_QUOTA_OBSERVATION_MAX_BYTES),
|
|
208
|
+
"entryCount": len(_CODEX_QUOTA_OBSERVATION_CACHE),
|
|
209
|
+
"maxEntries": int(_CODEX_QUOTA_OBSERVATION_MAX_ENTRIES),
|
|
210
|
+
"evictionCount": int(_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS),
|
|
211
|
+
"fallbackCount": int(_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS),
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _quota_observation_cache_checkpoint() -> tuple[dict, int, int, int]:
|
|
216
|
+
with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
|
|
217
|
+
return (
|
|
218
|
+
dict(_CODEX_QUOTA_OBSERVATION_CACHE_SIZES),
|
|
219
|
+
int(_CODEX_QUOTA_OBSERVATION_CACHE_BYTES),
|
|
220
|
+
int(_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS),
|
|
221
|
+
int(_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _restore_quota_observation_cache_checkpoint(checkpoint) -> None:
|
|
226
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_BYTES
|
|
227
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS
|
|
228
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS
|
|
229
|
+
sizes, retained_bytes, evictions, fallbacks = checkpoint
|
|
230
|
+
with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
|
|
231
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_SIZES.clear()
|
|
232
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_SIZES.update(sizes)
|
|
233
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_BYTES = int(retained_bytes)
|
|
234
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS = int(evictions)
|
|
235
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS = int(fallbacks)
|
|
102
236
|
|
|
103
237
|
|
|
104
238
|
def _codex_quota_reuse_identity(
|
|
@@ -280,34 +414,40 @@ def _cached_codex_quota_observations(**kwargs) -> tuple[object, ...]:
|
|
|
280
414
|
None if physical_groups is None else tuple(sorted(physical_groups)),
|
|
281
415
|
bool(kwargs.get("latest_per_identity", False)),
|
|
282
416
|
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
417
|
+
with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
|
|
418
|
+
cached = _CODEX_QUOTA_OBSERVATION_CACHE.get(key)
|
|
419
|
+
if cached is not None:
|
|
420
|
+
_CODEX_QUOTA_OBSERVATION_CACHE.move_to_end(key)
|
|
421
|
+
return cached
|
|
286
422
|
loaded = tuple(load_codex_quota_observations(**kwargs))
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
423
|
+
entry_bytes = retained_size_bytes(
|
|
424
|
+
(key, loaded), stop_after=_CODEX_QUOTA_OBSERVATION_MAX_BYTES)
|
|
425
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_BYTES
|
|
426
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS
|
|
427
|
+
global _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS
|
|
428
|
+
with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
|
|
429
|
+
# Another request may have filled this identity while the loader ran.
|
|
430
|
+
cached = _CODEX_QUOTA_OBSERVATION_CACHE.get(key)
|
|
431
|
+
if cached is not None:
|
|
432
|
+
_CODEX_QUOTA_OBSERVATION_CACHE.move_to_end(key)
|
|
433
|
+
return cached
|
|
434
|
+
if entry_bytes > _CODEX_QUOTA_OBSERVATION_MAX_BYTES:
|
|
435
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS += 1
|
|
436
|
+
return loaded
|
|
437
|
+
while _CODEX_QUOTA_OBSERVATION_CACHE and (
|
|
438
|
+
len(_CODEX_QUOTA_OBSERVATION_CACHE)
|
|
439
|
+
>= _CODEX_QUOTA_OBSERVATION_MAX_ENTRIES
|
|
440
|
+
or _CODEX_QUOTA_OBSERVATION_CACHE_BYTES + entry_bytes
|
|
441
|
+
> _CODEX_QUOTA_OBSERVATION_MAX_BYTES
|
|
442
|
+
):
|
|
443
|
+
old_key, _old_value = _CODEX_QUOTA_OBSERVATION_CACHE.popitem(
|
|
444
|
+
last=False)
|
|
445
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_BYTES -= (
|
|
446
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_SIZES.pop(old_key))
|
|
447
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS += 1
|
|
448
|
+
_CODEX_QUOTA_OBSERVATION_CACHE[key] = loaded
|
|
449
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_SIZES[key] = entry_bytes
|
|
450
|
+
_CODEX_QUOTA_OBSERVATION_CACHE_BYTES += entry_bytes
|
|
311
451
|
return loaded
|
|
312
452
|
SOURCE_HISTORY_LIMIT = 250
|
|
313
453
|
DASHBOARD_QUOTA_OBSERVATION_LIMIT = 1000
|
|
@@ -1120,6 +1260,30 @@ class DashboardReadContext:
|
|
|
1120
1260
|
raise ValueError("codex_budget must be a mapping or None")
|
|
1121
1261
|
|
|
1122
1262
|
|
|
1263
|
+
def _cache_database_identity(conn: sqlite3.Connection) -> tuple[object, ...]:
|
|
1264
|
+
"""Identity of the cache.db file currently backing ``conn``.
|
|
1265
|
+
|
|
1266
|
+
A pathname is not an identity: rebuild publishes a replacement at the same
|
|
1267
|
+
pathname. The device/inode pair makes every process memo fail closed on
|
|
1268
|
+
that replacement. In-memory and unreadable paths use the connection
|
|
1269
|
+
object, which intentionally prevents reuse across a newly-opened handle.
|
|
1270
|
+
"""
|
|
1271
|
+
try:
|
|
1272
|
+
db_path = next(
|
|
1273
|
+
str(row[2]) for row in conn.execute("PRAGMA database_list")
|
|
1274
|
+
if str(row[1]) == "main"
|
|
1275
|
+
)
|
|
1276
|
+
except (sqlite3.Error, StopIteration):
|
|
1277
|
+
return ("connection", id(conn))
|
|
1278
|
+
if not db_path:
|
|
1279
|
+
return ("connection", id(conn))
|
|
1280
|
+
try:
|
|
1281
|
+
stat = os.stat(db_path)
|
|
1282
|
+
except OSError:
|
|
1283
|
+
return ("path-unreadable", db_path, id(conn))
|
|
1284
|
+
return ("file", db_path, int(stat.st_dev), int(stat.st_ino))
|
|
1285
|
+
|
|
1286
|
+
|
|
1123
1287
|
_RESOURCE_ROWS = {
|
|
1124
1288
|
"session": ("sessions", "rows"),
|
|
1125
1289
|
"project": ("projects", "rows"),
|
|
@@ -1386,9 +1550,9 @@ def _period_wire(view: Any) -> dict[str, object]:
|
|
|
1386
1550
|
}
|
|
1387
1551
|
|
|
1388
1552
|
|
|
1389
|
-
_CODEX_PERIOD_VIEW_CACHE: dict[object, tuple] =
|
|
1390
|
-
_CODEX_CACHE_REPORT_ROWS: dict[object, tuple] =
|
|
1391
|
-
_CODEX_SESSION_VIEW_CACHE: dict[object, tuple[object, Any]] =
|
|
1553
|
+
_CODEX_PERIOD_VIEW_CACHE: dict[object, tuple] = _ObservedDict()
|
|
1554
|
+
_CODEX_CACHE_REPORT_ROWS: dict[object, tuple] = _ObservedDict()
|
|
1555
|
+
_CODEX_SESSION_VIEW_CACHE: dict[object, tuple[object, Any]] = _ObservedDict()
|
|
1392
1556
|
|
|
1393
1557
|
|
|
1394
1558
|
def _codex_session_row_key(row: object) -> tuple[str, str]:
|
|
@@ -3577,7 +3741,7 @@ def _alerts_wire(
|
|
|
3577
3741
|
)[:SOURCE_HISTORY_LIMIT])
|
|
3578
3742
|
|
|
3579
3743
|
|
|
3580
|
-
_CODEX_PROJECT_LABEL_CACHE: dict[object, dict[str, object]] =
|
|
3744
|
+
_CODEX_PROJECT_LABEL_CACHE: dict[object, dict[str, object]] = _ObservedDict()
|
|
3581
3745
|
|
|
3582
3746
|
|
|
3583
3747
|
def _cached_project_labeled_entries(
|
|
@@ -3661,7 +3825,7 @@ def _projects_wire(
|
|
|
3661
3825
|
}
|
|
3662
3826
|
|
|
3663
3827
|
|
|
3664
|
-
_CODEX_PROJECT_WIRE_CACHE: dict[object, tuple] =
|
|
3828
|
+
_CODEX_PROJECT_WIRE_CACHE: dict[object, tuple] = _ObservedDict()
|
|
3665
3829
|
|
|
3666
3830
|
|
|
3667
3831
|
def _cached_projects_wire(
|
|
@@ -3888,7 +4052,7 @@ def _partial_projects_wire(
|
|
|
3888
4052
|
}
|
|
3889
4053
|
|
|
3890
4054
|
|
|
3891
|
-
_CODEX_ENTRY_ADAPTER_CACHE: dict[int, tuple[object, CodexEntry]] =
|
|
4055
|
+
_CODEX_ENTRY_ADAPTER_CACHE: dict[int, tuple[object, CodexEntry]] = _ObservedDict()
|
|
3892
4056
|
|
|
3893
4057
|
|
|
3894
4058
|
def _codex_entries_from_accounting(entries: Iterable[object]) -> list[CodexEntry]:
|
|
@@ -4029,7 +4193,7 @@ def _build_codex_native_weekly_view(
|
|
|
4029
4193
|
)
|
|
4030
4194
|
|
|
4031
4195
|
|
|
4032
|
-
_CODEX_WEEKLY_VIEW_CACHE: dict[object, tuple] =
|
|
4196
|
+
_CODEX_WEEKLY_VIEW_CACHE: dict[object, tuple] = _ObservedDict()
|
|
4033
4197
|
|
|
4034
4198
|
|
|
4035
4199
|
def _codex_weekly_period_for_entry(
|
|
@@ -4198,6 +4362,9 @@ def _codex_accounts_wire(
|
|
|
4198
4362
|
accounting_start: dt.datetime,
|
|
4199
4363
|
accounting_end: dt.datetime,
|
|
4200
4364
|
population: tuple[object, ...],
|
|
4365
|
+
population_by_account: "Mapping[str, tuple[object, ...]] | None" = None,
|
|
4366
|
+
account_versions: "Mapping[str, int] | None" = None,
|
|
4367
|
+
population_signature: object | None = None,
|
|
4201
4368
|
) -> tuple[
|
|
4202
4369
|
list[dict[str, object]], list[dict[str, object]], dict[str, object]
|
|
4203
4370
|
]:
|
|
@@ -4261,7 +4428,11 @@ def _codex_accounts_wire(
|
|
|
4261
4428
|
# so the buckets are the sets those filters selected from, and it preserves
|
|
4262
4429
|
# encounter order, which `_aggregate_codex_buckets` depends on for a
|
|
4263
4430
|
# bucket's first-seen model order.
|
|
4264
|
-
by_account =
|
|
4431
|
+
by_account = (
|
|
4432
|
+
dict(population_by_account)
|
|
4433
|
+
if population_by_account is not None
|
|
4434
|
+
else _codex_partition_by_account(population)
|
|
4435
|
+
)
|
|
4265
4436
|
|
|
4266
4437
|
def _slice(
|
|
4267
4438
|
account_key: str,
|
|
@@ -4350,13 +4521,35 @@ def _codex_accounts_wire(
|
|
|
4350
4521
|
_codex_label_map = _cctally_account.display_label_map(
|
|
4351
4522
|
context.stats_conn, "codex")
|
|
4352
4523
|
|
|
4353
|
-
def _totals(
|
|
4524
|
+
def _totals(
|
|
4525
|
+
account_key: str,
|
|
4526
|
+
rows: tuple[object, ...],
|
|
4527
|
+
window_signature: object,
|
|
4528
|
+
) -> dict[str, object]:
|
|
4529
|
+
row_membership = (
|
|
4530
|
+
len(rows),
|
|
4531
|
+
None if not rows else int(
|
|
4532
|
+
getattr(rows[0], "cache_entry_id", 0) or 0),
|
|
4533
|
+
None if not rows else int(
|
|
4534
|
+
getattr(rows[-1], "cache_entry_id", 0) or 0),
|
|
4535
|
+
)
|
|
4536
|
+
signature = (
|
|
4537
|
+
population_signature,
|
|
4538
|
+
int(account_versions.get(account_key, 0)),
|
|
4539
|
+
window_signature,
|
|
4540
|
+
row_membership,
|
|
4541
|
+
context.display_tz_name,
|
|
4542
|
+
context.speed,
|
|
4543
|
+
) if account_versions is not None and population_signature is not None else None
|
|
4544
|
+
cached = _CODEX_ACCOUNT_CARD_TOTALS_CACHE.get(account_key)
|
|
4545
|
+
if signature is not None and cached is not None and cached[0] == signature:
|
|
4546
|
+
return cached[1]
|
|
4354
4547
|
entries = _codex_entries_from_accounting(rows)
|
|
4355
4548
|
cost = build_codex_daily_view(
|
|
4356
4549
|
entries, now_utc=context.now_utc, tz_name=context.display_tz_name,
|
|
4357
4550
|
speed=context.speed,
|
|
4358
4551
|
).total_cost_usd if entries else 0.0
|
|
4359
|
-
|
|
4552
|
+
value = {
|
|
4360
4553
|
"spendUsd": cost,
|
|
4361
4554
|
"inputTokens": sum(e.input_tokens for e in entries),
|
|
4362
4555
|
"cachedInputTokens": sum(e.cached_input_tokens for e in entries),
|
|
@@ -4364,6 +4557,20 @@ def _codex_accounts_wire(
|
|
|
4364
4557
|
"reasoningOutputTokens": sum(e.reasoning_output_tokens for e in entries),
|
|
4365
4558
|
"totalTokens": sum(e.total_tokens for e in entries),
|
|
4366
4559
|
}
|
|
4560
|
+
if signature is not None:
|
|
4561
|
+
global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
4562
|
+
admitted = (
|
|
4563
|
+
account_key in _CODEX_ACCOUNT_CARD_TOTALS_CACHE
|
|
4564
|
+
or (
|
|
4565
|
+
len(_CODEX_ACCOUNT_CARD_TOTALS_CACHE) + 1
|
|
4566
|
+
) * _CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES
|
|
4567
|
+
<= _CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES
|
|
4568
|
+
)
|
|
4569
|
+
if admitted:
|
|
4570
|
+
_CODEX_ACCOUNT_CARD_TOTALS_CACHE[account_key] = (signature, value)
|
|
4571
|
+
else:
|
|
4572
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS += 1
|
|
4573
|
+
return value
|
|
4367
4574
|
|
|
4368
4575
|
accounts_wire: list[dict[str, object]] = []
|
|
4369
4576
|
hero_cycles_wire: list[dict[str, object]] = []
|
|
@@ -4377,16 +4584,22 @@ def _codex_accounts_wire(
|
|
|
4377
4584
|
key, cyc.start_at, cycle_end,
|
|
4378
4585
|
source_root_keys=cyc.source_root_keys,
|
|
4379
4586
|
)
|
|
4380
|
-
totals = _totals(rows
|
|
4587
|
+
totals = _totals(key, rows, (
|
|
4588
|
+
"cycle", cyc.start_at, cyc.resets_at,
|
|
4589
|
+
tuple(cyc.source_root_keys)))
|
|
4381
4590
|
elif is_unattributed:
|
|
4382
|
-
totals = _totals(
|
|
4591
|
+
totals = _totals(
|
|
4592
|
+
key, unattributed_window_rows,
|
|
4593
|
+
("fallback",),
|
|
4594
|
+
)
|
|
4383
4595
|
else:
|
|
4384
4596
|
# A real account without a live weekly cycle: totals over ONE
|
|
4385
4597
|
# native cycle width ending now, so this card can be summed into a
|
|
4386
4598
|
# week-labelled headline without overstating it (#564). No bars or
|
|
4387
4599
|
# reset, because there is no live cycle to describe.
|
|
4388
4600
|
rows = _slice(key, fallback_start, accounting_end)
|
|
4389
|
-
totals = _totals(
|
|
4601
|
+
totals = _totals(
|
|
4602
|
+
key, rows, ("fallback",))
|
|
4390
4603
|
card: dict[str, object] = {
|
|
4391
4604
|
"accountKey": key,
|
|
4392
4605
|
"label": _codex_label_map.get(key) or _cctally_account.account_label(context.stats_conn, key),
|
|
@@ -4444,6 +4657,8 @@ def _codex_accounts_wire(
|
|
|
4444
4657
|
"cost_usd": totals["spendUsd"],
|
|
4445
4658
|
"total_tokens": totals["totalTokens"],
|
|
4446
4659
|
})
|
|
4660
|
+
for stale_key in set(_CODEX_ACCOUNT_CARD_TOTALS_CACHE) - set(ordered_keys):
|
|
4661
|
+
_CODEX_ACCOUNT_CARD_TOTALS_CACHE.pop(stale_key, None)
|
|
4447
4662
|
return accounts_wire, hero_cycles_wire, {
|
|
4448
4663
|
"scope": "account_cycles",
|
|
4449
4664
|
"status": "certified",
|
|
@@ -4451,6 +4666,11 @@ def _codex_accounts_wire(
|
|
|
4451
4666
|
}
|
|
4452
4667
|
|
|
4453
4668
|
|
|
4669
|
+
_CODEX_ACCOUNT_CARD_TOTALS_CACHE: dict[
|
|
4670
|
+
str, tuple[object, dict[str, object]]
|
|
4671
|
+
] = _ObservedDict()
|
|
4672
|
+
|
|
4673
|
+
|
|
4454
4674
|
def _codex_partition_by_account(
|
|
4455
4675
|
entries: Iterable[object],
|
|
4456
4676
|
) -> dict[str, tuple[object, ...]]:
|
|
@@ -4524,14 +4744,254 @@ def _codex_fold_visible_rows(
|
|
|
4524
4744
|
)
|
|
4525
4745
|
|
|
4526
4746
|
|
|
4747
|
+
_CODEX_VISIBLE_POPULATION_MAX_BYTES = 128 * 1024 * 1024
|
|
4748
|
+
_CODEX_VISIBLE_POPULATION_CACHE: dict[str, object] = _ObservedDict()
|
|
4749
|
+
_CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES = 256 * 1024
|
|
4750
|
+
_CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES = 1024
|
|
4751
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
|
|
4752
|
+
|
|
4753
|
+
|
|
4754
|
+
def _codex_population_order_key(entry: object) -> tuple[object, ...]:
|
|
4755
|
+
"""The accounting carrier's canonical encounter order (#582)."""
|
|
4756
|
+
return (
|
|
4757
|
+
getattr(entry, "timestamp"),
|
|
4758
|
+
str(getattr(entry, "source_root_key", "")),
|
|
4759
|
+
str(getattr(entry, "conversation_key", "")),
|
|
4760
|
+
int(getattr(entry, "cache_entry_id", 0) or 0),
|
|
4761
|
+
)
|
|
4762
|
+
|
|
4763
|
+
|
|
4764
|
+
def _codex_visible_population_estimated_bytes(
|
|
4765
|
+
rows: Iterable[object], entries: Iterable[CodexEntry], account_count: int,
|
|
4766
|
+
) -> int:
|
|
4767
|
+
"""Conservative retained-reference estimate for the one-generation memo.
|
|
4768
|
+
|
|
4769
|
+
Strings and immutable row values are already retained by the accounting
|
|
4770
|
+
carrier or shared by the adapter cache. This estimate charges both object
|
|
4771
|
+
shells, four dict/tuple references per row, and per-account container
|
|
4772
|
+
overhead. It is deliberately stable and cheap enough to compute only on a
|
|
4773
|
+
cold population; its role is a hard admission bound, not heap profiling.
|
|
4774
|
+
"""
|
|
4775
|
+
row_values = tuple(rows)
|
|
4776
|
+
adapted_values = tuple(entries)
|
|
4777
|
+
return (
|
|
4778
|
+
sum(sys.getsizeof(row) for row in row_values)
|
|
4779
|
+
+ sum(sys.getsizeof(entry) for entry in adapted_values)
|
|
4780
|
+
+ (len(row_values) * 8 * 4)
|
|
4781
|
+
+ (int(account_count) * 1024)
|
|
4782
|
+
+ 4096
|
|
4783
|
+
)
|
|
4784
|
+
|
|
4785
|
+
|
|
4786
|
+
def codex_visible_population_cache_stats() -> Mapping[str, object]:
|
|
4787
|
+
"""Observed size/fallback counters for both new source caches."""
|
|
4788
|
+
return MappingProxyType({
|
|
4789
|
+
"estimatedBytes": int(
|
|
4790
|
+
_CODEX_VISIBLE_POPULATION_CACHE.get("estimated_bytes", 0)),
|
|
4791
|
+
"maxBytes": _CODEX_VISIBLE_POPULATION_MAX_BYTES,
|
|
4792
|
+
"entryCount": int(
|
|
4793
|
+
_CODEX_VISIBLE_POPULATION_CACHE.get("entry_count", 0)),
|
|
4794
|
+
"fallbackCount": int(
|
|
4795
|
+
_CODEX_VISIBLE_POPULATION_CACHE.get("fallback_count", 0)),
|
|
4796
|
+
"accountCardEstimatedBytes": (
|
|
4797
|
+
len(_CODEX_ACCOUNT_CARD_TOTALS_CACHE)
|
|
4798
|
+
* _CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES
|
|
4799
|
+
),
|
|
4800
|
+
"accountCardMaxBytes": _CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES,
|
|
4801
|
+
"accountCardEntryCount": len(_CODEX_ACCOUNT_CARD_TOTALS_CACHE),
|
|
4802
|
+
"accountCardFallbackCount": _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS,
|
|
4803
|
+
})
|
|
4804
|
+
|
|
4805
|
+
|
|
4806
|
+
def _cached_codex_visible_rows(
|
|
4807
|
+
rows: tuple[object, ...],
|
|
4808
|
+
*,
|
|
4809
|
+
changed_old: tuple[object, ...],
|
|
4810
|
+
changed_new: tuple[object, ...],
|
|
4811
|
+
semantic_signature: object,
|
|
4812
|
+
) -> "tuple[list[CodexEntry], dict[str, tuple[object, ...]], dict[str, tuple[CodexEntry, ...]], dict[str, int]]":
|
|
4813
|
+
"""Reuse the immutable visible population, updating only dirty accounts.
|
|
4814
|
+
|
|
4815
|
+
The durable accounting ledger supplies exact old/new path populations. A
|
|
4816
|
+
clean generation therefore returns the prior fold without visiting a row;
|
|
4817
|
+
a dirty generation adapts only ``changed_new`` and rebuilds only accounts
|
|
4818
|
+
named by either side of that delta. The merged encounter order is restored
|
|
4819
|
+
from the authoritative accounting tuple by integer-id lookup, so downstream
|
|
4820
|
+
floating-point accumulation order remains byte-identical.
|
|
4821
|
+
|
|
4822
|
+
Rows without a durable positive id and populations above the byte budget
|
|
4823
|
+
take the cold path and are not retained. Both are complete invalidations,
|
|
4824
|
+
never partial reuse.
|
|
4825
|
+
"""
|
|
4826
|
+
state = _CODEX_VISIBLE_POPULATION_CACHE
|
|
4827
|
+
prior_fallbacks = int(state.get("fallback_count", 0))
|
|
4828
|
+
reusable = (
|
|
4829
|
+
state.get("signature") == semantic_signature
|
|
4830
|
+
and isinstance(state.get("rows_by_id"), dict)
|
|
4831
|
+
and isinstance(state.get("entries_by_id"), dict)
|
|
4832
|
+
and all(int(getattr(row, "cache_entry_id", 0) or 0) > 0 for row in rows)
|
|
4833
|
+
and all(int(getattr(row, "cache_entry_id", 0) or 0) > 0
|
|
4834
|
+
for row in (*changed_old, *changed_new))
|
|
4835
|
+
)
|
|
4836
|
+
if reusable and not changed_old and not changed_new:
|
|
4837
|
+
return (
|
|
4838
|
+
list(state["entries"]),
|
|
4839
|
+
dict(state["rows_by_account"]),
|
|
4840
|
+
dict(state["entries_by_account"]),
|
|
4841
|
+
dict(state["account_versions"]),
|
|
4842
|
+
)
|
|
4843
|
+
|
|
4844
|
+
if not reusable:
|
|
4845
|
+
entries, rows_by_account, entries_by_account = _codex_fold_visible_rows(rows)
|
|
4846
|
+
estimated_bytes = _codex_visible_population_estimated_bytes(
|
|
4847
|
+
rows, entries, len(rows_by_account))
|
|
4848
|
+
if (
|
|
4849
|
+
estimated_bytes > _CODEX_VISIBLE_POPULATION_MAX_BYTES
|
|
4850
|
+
or any(int(getattr(row, "cache_entry_id", 0) or 0) <= 0 for row in rows)
|
|
4851
|
+
):
|
|
4852
|
+
state.clear()
|
|
4853
|
+
state.update({
|
|
4854
|
+
"fallback_count": prior_fallbacks + 1,
|
|
4855
|
+
"estimated_bytes": 0,
|
|
4856
|
+
"entry_count": 0,
|
|
4857
|
+
})
|
|
4858
|
+
return entries, rows_by_account, entries_by_account, {}
|
|
4859
|
+
entry_by_id = {
|
|
4860
|
+
int(getattr(row, "cache_entry_id")): entry
|
|
4861
|
+
for row, entry in zip(rows, entries)
|
|
4862
|
+
}
|
|
4863
|
+
state.clear()
|
|
4864
|
+
state.update({
|
|
4865
|
+
"signature": semantic_signature,
|
|
4866
|
+
"entries": tuple(entries),
|
|
4867
|
+
"rows_by_id": {
|
|
4868
|
+
int(getattr(row, "cache_entry_id")): row for row in rows
|
|
4869
|
+
},
|
|
4870
|
+
"entries_by_id": entry_by_id,
|
|
4871
|
+
"rows_by_account": dict(rows_by_account),
|
|
4872
|
+
"entries_by_account": dict(entries_by_account),
|
|
4873
|
+
"account_versions": {key: 1 for key in rows_by_account},
|
|
4874
|
+
"estimated_bytes": estimated_bytes,
|
|
4875
|
+
"entry_count": len(rows),
|
|
4876
|
+
"account_count": len(rows_by_account),
|
|
4877
|
+
"fallback_count": prior_fallbacks,
|
|
4878
|
+
})
|
|
4879
|
+
return entries, rows_by_account, entries_by_account, dict(
|
|
4880
|
+
state["account_versions"])
|
|
4881
|
+
|
|
4882
|
+
rows_by_id = dict(state["rows_by_id"])
|
|
4883
|
+
entries_by_id = dict(state["entries_by_id"])
|
|
4884
|
+
rows_by_account = dict(state["rows_by_account"])
|
|
4885
|
+
entries_by_account = dict(state["entries_by_account"])
|
|
4886
|
+
account_versions = dict(state["account_versions"])
|
|
4887
|
+
estimated_bytes = int(state.get("estimated_bytes", 0))
|
|
4888
|
+
prior_account_count = int(state.get("account_count", len(rows_by_account)))
|
|
4889
|
+
affected: set[str] = set()
|
|
4890
|
+
old_ids: set[int] = set()
|
|
4891
|
+
for row in changed_old:
|
|
4892
|
+
row_id = int(getattr(row, "cache_entry_id"))
|
|
4893
|
+
old_ids.add(row_id)
|
|
4894
|
+
affected.add(str(
|
|
4895
|
+
getattr(row, "account_key", "") or _lib_accounts.UNATTRIBUTED))
|
|
4896
|
+
prior_entry = entries_by_id.get(row_id)
|
|
4897
|
+
estimated_bytes -= (
|
|
4898
|
+
sys.getsizeof(row)
|
|
4899
|
+
+ (0 if prior_entry is None else sys.getsizeof(prior_entry))
|
|
4900
|
+
+ (8 * 4)
|
|
4901
|
+
)
|
|
4902
|
+
rows_by_id.pop(row_id, None)
|
|
4903
|
+
entries_by_id.pop(row_id, None)
|
|
4904
|
+
converted_new = _codex_entries_from_accounting(changed_new)
|
|
4905
|
+
for row, converted in zip(changed_new, converted_new):
|
|
4906
|
+
row_id = int(getattr(row, "cache_entry_id"))
|
|
4907
|
+
key = str(
|
|
4908
|
+
getattr(row, "account_key", "") or _lib_accounts.UNATTRIBUTED)
|
|
4909
|
+
affected.add(key)
|
|
4910
|
+
estimated_bytes += (
|
|
4911
|
+
sys.getsizeof(row) + sys.getsizeof(converted) + (8 * 4)
|
|
4912
|
+
)
|
|
4913
|
+
rows_by_id[row_id] = row
|
|
4914
|
+
entries_by_id[row_id] = converted
|
|
4915
|
+
|
|
4916
|
+
for key in affected:
|
|
4917
|
+
retained_rows = [
|
|
4918
|
+
row for row in rows_by_account.get(key, ())
|
|
4919
|
+
if int(getattr(row, "cache_entry_id")) not in old_ids
|
|
4920
|
+
]
|
|
4921
|
+
retained_entries = {
|
|
4922
|
+
int(getattr(entry, "cache_entry_id")): entry
|
|
4923
|
+
for entry in entries_by_account.get(key, ())
|
|
4924
|
+
if int(getattr(entry, "cache_entry_id")) not in old_ids
|
|
4925
|
+
}
|
|
4926
|
+
for row, converted in zip(changed_new, converted_new):
|
|
4927
|
+
if str(getattr(row, "account_key", "") or
|
|
4928
|
+
_lib_accounts.UNATTRIBUTED) == key:
|
|
4929
|
+
retained_rows.append(row)
|
|
4930
|
+
retained_entries[int(getattr(row, "cache_entry_id"))] = converted
|
|
4931
|
+
retained_rows.sort(key=_codex_population_order_key)
|
|
4932
|
+
if retained_rows:
|
|
4933
|
+
rows_by_account[key] = tuple(retained_rows)
|
|
4934
|
+
entries_by_account[key] = tuple(
|
|
4935
|
+
retained_entries[int(getattr(row, "cache_entry_id"))]
|
|
4936
|
+
for row in retained_rows
|
|
4937
|
+
)
|
|
4938
|
+
else:
|
|
4939
|
+
rows_by_account.pop(key, None)
|
|
4940
|
+
entries_by_account.pop(key, None)
|
|
4941
|
+
account_versions[key] = int(account_versions.get(key, 0)) + 1
|
|
4942
|
+
|
|
4943
|
+
try:
|
|
4944
|
+
entries = tuple(
|
|
4945
|
+
entries_by_id[int(getattr(row, "cache_entry_id"))] for row in rows
|
|
4946
|
+
)
|
|
4947
|
+
except KeyError:
|
|
4948
|
+
# An inconsistent delta must fail closed to the exact cold fold.
|
|
4949
|
+
state.clear()
|
|
4950
|
+
return _cached_codex_visible_rows(
|
|
4951
|
+
rows,
|
|
4952
|
+
changed_old=rows,
|
|
4953
|
+
changed_new=rows,
|
|
4954
|
+
semantic_signature=semantic_signature,
|
|
4955
|
+
)
|
|
4956
|
+
estimated_bytes += max(0, len(rows_by_account) - prior_account_count) * 1024
|
|
4957
|
+
estimated_bytes = max(0, estimated_bytes)
|
|
4958
|
+
if estimated_bytes > _CODEX_VISIBLE_POPULATION_MAX_BYTES:
|
|
4959
|
+
state.clear()
|
|
4960
|
+
state.update({
|
|
4961
|
+
"fallback_count": prior_fallbacks + 1,
|
|
4962
|
+
"estimated_bytes": 0,
|
|
4963
|
+
"entry_count": 0,
|
|
4964
|
+
})
|
|
4965
|
+
else:
|
|
4966
|
+
state.clear()
|
|
4967
|
+
state.update({
|
|
4968
|
+
"signature": semantic_signature,
|
|
4969
|
+
"entries": entries,
|
|
4970
|
+
"rows_by_id": rows_by_id,
|
|
4971
|
+
"entries_by_id": entries_by_id,
|
|
4972
|
+
"rows_by_account": rows_by_account,
|
|
4973
|
+
"entries_by_account": entries_by_account,
|
|
4974
|
+
"account_versions": account_versions,
|
|
4975
|
+
"estimated_bytes": estimated_bytes,
|
|
4976
|
+
"entry_count": len(rows),
|
|
4977
|
+
"account_count": len(rows_by_account),
|
|
4978
|
+
"fallback_count": prior_fallbacks,
|
|
4979
|
+
})
|
|
4980
|
+
return list(entries), rows_by_account, entries_by_account, account_versions
|
|
4981
|
+
|
|
4982
|
+
|
|
4527
4983
|
_CODEX_ACCOUNT_SCOPE_CACHE: dict[
|
|
4528
4984
|
str, tuple[object, dict[str, object]]
|
|
4529
|
-
] =
|
|
4985
|
+
] = _ObservedDict()
|
|
4530
4986
|
|
|
4531
4987
|
|
|
4532
4988
|
def reset_codex_account_scope_cache() -> None:
|
|
4533
4989
|
"""Test/process reset for #582's immutable finalized account scopes."""
|
|
4990
|
+
global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
4534
4991
|
_CODEX_ACCOUNT_SCOPE_CACHE.clear()
|
|
4992
|
+
_CODEX_VISIBLE_POPULATION_CACHE.clear()
|
|
4993
|
+
_CODEX_ACCOUNT_CARD_TOTALS_CACHE.clear()
|
|
4994
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
|
|
4535
4995
|
_CODEX_ENTRY_ADAPTER_CACHE.clear()
|
|
4536
4996
|
_CODEX_PROJECT_LABEL_CACHE.clear()
|
|
4537
4997
|
_CODEX_PERIOD_VIEW_CACHE.clear()
|
|
@@ -5058,11 +5518,343 @@ def _codex_source_caches() -> tuple[dict, ...]:
|
|
|
5058
5518
|
_CODEX_PROJECT_LABEL_CACHE,
|
|
5059
5519
|
_CODEX_PROJECT_WIRE_CACHE,
|
|
5060
5520
|
_CODEX_ENTRY_ADAPTER_CACHE,
|
|
5521
|
+
_CODEX_VISIBLE_POPULATION_CACHE,
|
|
5522
|
+
_CODEX_ACCOUNT_CARD_TOTALS_CACHE,
|
|
5061
5523
|
_CODEX_WEEKLY_VIEW_CACHE,
|
|
5062
5524
|
_CODEX_ACCOUNT_SCOPE_CACHE,
|
|
5063
5525
|
)
|
|
5064
5526
|
|
|
5065
5527
|
|
|
5528
|
+
_CODEX_SOURCE_ACCELERATOR_MAX_BYTES = 768 * 1024 * 1024
|
|
5529
|
+
_CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES = 500_000
|
|
5530
|
+
_CODEX_SOURCE_ACCELERATOR_STATS_LOCK = threading.Lock()
|
|
5531
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
|
|
5532
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
|
|
5533
|
+
_CODEX_SOURCE_ACCELERATOR_EVICTIONS = 0
|
|
5534
|
+
_CODEX_SOURCE_ACCELERATOR_FALLBACKS = 0
|
|
5535
|
+
_CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = -1
|
|
5536
|
+
_CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS = 0
|
|
5537
|
+
_CODEX_SOURCE_MEMORY_COMPLETION_LOCK = None
|
|
5538
|
+
|
|
5539
|
+
|
|
5540
|
+
class _CodexSourceMemoryWorker:
|
|
5541
|
+
"""One latest-wins, cancellable retained-size verifier.
|
|
5542
|
+
|
|
5543
|
+
The source caches are shallow-copied at a successful build boundary. The
|
|
5544
|
+
worker traverses that immutable container snapshot so its multi-second
|
|
5545
|
+
production-scale admission pass neither blocks the five-second publisher
|
|
5546
|
+
nor races a live dict mutation. A newer generation cancels the old walk;
|
|
5547
|
+
shutdown uses the same cooperative cancellation path.
|
|
5548
|
+
"""
|
|
5549
|
+
|
|
5550
|
+
def __init__(self) -> None:
|
|
5551
|
+
self._condition = threading.Condition()
|
|
5552
|
+
self._pending: tuple[int, tuple[dict, ...]] | None = None
|
|
5553
|
+
self._result: tuple[int, int | None, str | None] | None = None
|
|
5554
|
+
self._latest_generation = -1
|
|
5555
|
+
self._active_generation: int | None = None
|
|
5556
|
+
self._stop = False
|
|
5557
|
+
self._thread: threading.Thread | None = None
|
|
5558
|
+
self._duty_wall = 0.0
|
|
5559
|
+
self._duty_cpu = 0.0
|
|
5560
|
+
|
|
5561
|
+
def submit(self, generation: int, snapshot: tuple[dict, ...]) -> None:
|
|
5562
|
+
with self._condition:
|
|
5563
|
+
if self._stop:
|
|
5564
|
+
return
|
|
5565
|
+
self._latest_generation = int(generation)
|
|
5566
|
+
self._pending = (int(generation), snapshot)
|
|
5567
|
+
if self._thread is None or not self._thread.is_alive():
|
|
5568
|
+
self._thread = threading.Thread(
|
|
5569
|
+
target=self._run,
|
|
5570
|
+
name="cctally-source-memory",
|
|
5571
|
+
daemon=True,
|
|
5572
|
+
)
|
|
5573
|
+
self._thread.start()
|
|
5574
|
+
self._condition.notify()
|
|
5575
|
+
|
|
5576
|
+
def take_result(self) -> tuple[int, int | None, str | None] | None:
|
|
5577
|
+
with self._condition:
|
|
5578
|
+
result = self._result
|
|
5579
|
+
self._result = None
|
|
5580
|
+
return result
|
|
5581
|
+
|
|
5582
|
+
def _cooperate(self) -> bool:
|
|
5583
|
+
with self._condition:
|
|
5584
|
+
cancelled = (
|
|
5585
|
+
self._stop
|
|
5586
|
+
or self._active_generation != self._latest_generation
|
|
5587
|
+
)
|
|
5588
|
+
if cancelled:
|
|
5589
|
+
return True
|
|
5590
|
+
cpu_elapsed = time.thread_time() - self._duty_cpu
|
|
5591
|
+
wall_elapsed = time.monotonic() - self._duty_wall
|
|
5592
|
+
delay = (cpu_elapsed / RETAINED_SIZE_WORKER_DUTY) - wall_elapsed
|
|
5593
|
+
if delay > 0:
|
|
5594
|
+
time.sleep(min(delay, 0.01))
|
|
5595
|
+
with self._condition:
|
|
5596
|
+
return (
|
|
5597
|
+
self._stop
|
|
5598
|
+
or self._active_generation != self._latest_generation
|
|
5599
|
+
)
|
|
5600
|
+
|
|
5601
|
+
def _run(self) -> None:
|
|
5602
|
+
while True:
|
|
5603
|
+
with self._condition:
|
|
5604
|
+
while self._pending is None and not self._stop:
|
|
5605
|
+
self._condition.wait()
|
|
5606
|
+
if self._stop:
|
|
5607
|
+
return
|
|
5608
|
+
generation, snapshot = self._pending
|
|
5609
|
+
self._pending = None
|
|
5610
|
+
self._active_generation = generation
|
|
5611
|
+
estimated: int | None = None
|
|
5612
|
+
error: str | None = None
|
|
5613
|
+
entry_count = sum(len(cache) for cache in snapshot)
|
|
5614
|
+
try:
|
|
5615
|
+
while not RETAINED_SIZE_WORK_LOCK.acquire(timeout=0.05):
|
|
5616
|
+
if self._cooperate():
|
|
5617
|
+
raise RetainedSizeCancelled()
|
|
5618
|
+
try:
|
|
5619
|
+
self._duty_wall = time.monotonic()
|
|
5620
|
+
self._duty_cpu = time.thread_time()
|
|
5621
|
+
estimated = retained_size_bytes(
|
|
5622
|
+
snapshot,
|
|
5623
|
+
stop_after=_CODEX_SOURCE_ACCELERATOR_MAX_BYTES,
|
|
5624
|
+
cancelled=self._cooperate,
|
|
5625
|
+
)
|
|
5626
|
+
finally:
|
|
5627
|
+
RETAINED_SIZE_WORK_LOCK.release()
|
|
5628
|
+
except RetainedSizeCancelled:
|
|
5629
|
+
continue
|
|
5630
|
+
except Exception as exc: # noqa: BLE001 - keep the sole worker alive
|
|
5631
|
+
error = f"{type(exc).__name__}: {exc}"
|
|
5632
|
+
finally:
|
|
5633
|
+
snapshot = ()
|
|
5634
|
+
published = (
|
|
5635
|
+
error is None
|
|
5636
|
+
and estimated is not None
|
|
5637
|
+
and _publish_codex_source_measurement(
|
|
5638
|
+
generation, int(estimated), entry_count)
|
|
5639
|
+
)
|
|
5640
|
+
enforced = False
|
|
5641
|
+
completion_lock = _CODEX_SOURCE_MEMORY_COMPLETION_LOCK
|
|
5642
|
+
if not published and completion_lock is not None:
|
|
5643
|
+
with completion_lock:
|
|
5644
|
+
enforced = _enforce_codex_source_unsafe_completion(
|
|
5645
|
+
generation,
|
|
5646
|
+
None if estimated is None else int(estimated),
|
|
5647
|
+
error,
|
|
5648
|
+
)
|
|
5649
|
+
with self._condition:
|
|
5650
|
+
self._active_generation = None
|
|
5651
|
+
if (
|
|
5652
|
+
not published and not enforced
|
|
5653
|
+
and not self._stop
|
|
5654
|
+
and generation == self._latest_generation
|
|
5655
|
+
):
|
|
5656
|
+
self._result = (
|
|
5657
|
+
generation,
|
|
5658
|
+
None if estimated is None else int(estimated),
|
|
5659
|
+
error,
|
|
5660
|
+
)
|
|
5661
|
+
|
|
5662
|
+
def shutdown(self) -> bool:
|
|
5663
|
+
with self._condition:
|
|
5664
|
+
self._stop = True
|
|
5665
|
+
self._pending = None
|
|
5666
|
+
thread = self._thread
|
|
5667
|
+
self._condition.notify_all()
|
|
5668
|
+
if thread is not None:
|
|
5669
|
+
thread.join(timeout=1.0)
|
|
5670
|
+
return thread is None or not thread.is_alive()
|
|
5671
|
+
|
|
5672
|
+
def is_alive(self) -> bool:
|
|
5673
|
+
with self._condition:
|
|
5674
|
+
return self._thread is not None and self._thread.is_alive()
|
|
5675
|
+
|
|
5676
|
+
|
|
5677
|
+
_CODEX_SOURCE_MEMORY_WORKER = _CodexSourceMemoryWorker()
|
|
5678
|
+
|
|
5679
|
+
|
|
5680
|
+
def set_codex_source_memory_completion_lock(lock) -> None:
|
|
5681
|
+
"""Bind unsafe verifier completions to the dashboard publisher lock."""
|
|
5682
|
+
global _CODEX_SOURCE_MEMORY_COMPLETION_LOCK
|
|
5683
|
+
_CODEX_SOURCE_MEMORY_COMPLETION_LOCK = lock
|
|
5684
|
+
|
|
5685
|
+
|
|
5686
|
+
def _enforce_codex_source_unsafe_completion(
|
|
5687
|
+
generation: int, estimated: int | None, error: str | None,
|
|
5688
|
+
) -> bool:
|
|
5689
|
+
"""Evict a current over-cap/error result without awaiting another tick."""
|
|
5690
|
+
if error is None and (
|
|
5691
|
+
estimated is None or estimated <= _CODEX_SOURCE_ACCELERATOR_MAX_BYTES
|
|
5692
|
+
):
|
|
5693
|
+
return False
|
|
5694
|
+
with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
|
|
5695
|
+
if (
|
|
5696
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY
|
|
5697
|
+
or generation != _CODEX_SOURCE_ACCELERATOR_GENERATION
|
|
5698
|
+
):
|
|
5699
|
+
return False
|
|
5700
|
+
caches = _codex_source_caches()
|
|
5701
|
+
entry_count = sum(len(cache) for cache in caches)
|
|
5702
|
+
global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
|
|
5703
|
+
global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
|
|
5704
|
+
global _CODEX_SOURCE_ACCELERATOR_EVICTIONS
|
|
5705
|
+
global _CODEX_SOURCE_ACCELERATOR_FALLBACKS
|
|
5706
|
+
global _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS
|
|
5707
|
+
with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
|
|
5708
|
+
reset_codex_quota_observation_cache()
|
|
5709
|
+
for cache in caches[1:]:
|
|
5710
|
+
cache.clear()
|
|
5711
|
+
_CODEX_SOURCE_ACCELERATOR_EVICTIONS += entry_count
|
|
5712
|
+
_CODEX_SOURCE_ACCELERATOR_FALLBACKS += 1
|
|
5713
|
+
if error is not None:
|
|
5714
|
+
_CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS += 1
|
|
5715
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
|
|
5716
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
|
|
5717
|
+
return True
|
|
5718
|
+
|
|
5719
|
+
|
|
5720
|
+
def _publish_codex_source_measurement(
|
|
5721
|
+
generation: int, estimated: int, entry_count: int,
|
|
5722
|
+
) -> bool:
|
|
5723
|
+
"""Publish an under-cap current generation without waiting for a tick."""
|
|
5724
|
+
if estimated > _CODEX_SOURCE_ACCELERATOR_MAX_BYTES:
|
|
5725
|
+
return False
|
|
5726
|
+
with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
|
|
5727
|
+
if (
|
|
5728
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY
|
|
5729
|
+
or generation != _CODEX_SOURCE_ACCELERATOR_GENERATION
|
|
5730
|
+
):
|
|
5731
|
+
return False
|
|
5732
|
+
global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
|
|
5733
|
+
global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
|
|
5734
|
+
global _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
|
|
5735
|
+
with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
|
|
5736
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = int(estimated)
|
|
5737
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = int(entry_count)
|
|
5738
|
+
_CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = int(generation)
|
|
5739
|
+
return True
|
|
5740
|
+
|
|
5741
|
+
|
|
5742
|
+
def _take_codex_source_accelerator_dirty() -> int | None:
|
|
5743
|
+
"""Claim one dirty generation for a serialized source-build admission."""
|
|
5744
|
+
global _CODEX_SOURCE_ACCELERATOR_DIRTY
|
|
5745
|
+
with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
|
|
5746
|
+
if not _CODEX_SOURCE_ACCELERATOR_DIRTY:
|
|
5747
|
+
return None
|
|
5748
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY = False
|
|
5749
|
+
return int(_CODEX_SOURCE_ACCELERATOR_GENERATION)
|
|
5750
|
+
|
|
5751
|
+
|
|
5752
|
+
def _mark_codex_source_accelerators_clean() -> None:
|
|
5753
|
+
global _CODEX_SOURCE_ACCELERATOR_DIRTY
|
|
5754
|
+
with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
|
|
5755
|
+
_CODEX_SOURCE_ACCELERATOR_DIRTY = False
|
|
5756
|
+
|
|
5757
|
+
|
|
5758
|
+
def enforce_codex_source_accelerator_bounds() -> None:
|
|
5759
|
+
"""Schedule byte admission and apply completed results without cadence I/O."""
|
|
5760
|
+
caches = _codex_source_caches()
|
|
5761
|
+
entry_count = sum(len(cache) for cache in caches)
|
|
5762
|
+
global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
|
|
5763
|
+
global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
|
|
5764
|
+
global _CODEX_SOURCE_ACCELERATOR_EVICTIONS
|
|
5765
|
+
global _CODEX_SOURCE_ACCELERATOR_FALLBACKS
|
|
5766
|
+
global _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
|
|
5767
|
+
global _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS
|
|
5768
|
+
completed = _CODEX_SOURCE_MEMORY_WORKER.take_result()
|
|
5769
|
+
with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
|
|
5770
|
+
current_generation = int(_CODEX_SOURCE_ACCELERATOR_GENERATION)
|
|
5771
|
+
currently_dirty = bool(_CODEX_SOURCE_ACCELERATOR_DIRTY)
|
|
5772
|
+
measured = (
|
|
5773
|
+
None if completed is None or completed[1] is None
|
|
5774
|
+
else int(completed[1])
|
|
5775
|
+
)
|
|
5776
|
+
measured_generation = -1 if completed is None else int(completed[0])
|
|
5777
|
+
measurement_error = None if completed is None else completed[2]
|
|
5778
|
+
measurement_is_current = (
|
|
5779
|
+
completed is not None
|
|
5780
|
+
and measured_generation == current_generation
|
|
5781
|
+
and not currently_dirty
|
|
5782
|
+
)
|
|
5783
|
+
with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
|
|
5784
|
+
if measurement_is_current and measurement_error is not None:
|
|
5785
|
+
reset_codex_quota_observation_cache()
|
|
5786
|
+
for cache in caches[1:]:
|
|
5787
|
+
cache.clear()
|
|
5788
|
+
_CODEX_SOURCE_ACCELERATOR_EVICTIONS += entry_count
|
|
5789
|
+
_CODEX_SOURCE_ACCELERATOR_FALLBACKS += 1
|
|
5790
|
+
_CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS += 1
|
|
5791
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
|
|
5792
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
|
|
5793
|
+
elif (
|
|
5794
|
+
(measurement_is_current
|
|
5795
|
+
and measured is not None
|
|
5796
|
+
and measured > _CODEX_SOURCE_ACCELERATOR_MAX_BYTES)
|
|
5797
|
+
or entry_count > _CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES
|
|
5798
|
+
):
|
|
5799
|
+
# The caller already owns the completed SourceDashboardState. A
|
|
5800
|
+
# cold next build preserves wire semantics and prevents any one
|
|
5801
|
+
# history/account/root churn pattern from growing process state.
|
|
5802
|
+
reset_codex_quota_observation_cache()
|
|
5803
|
+
for cache in caches[1:]:
|
|
5804
|
+
cache.clear()
|
|
5805
|
+
_CODEX_SOURCE_ACCELERATOR_EVICTIONS += entry_count
|
|
5806
|
+
_CODEX_SOURCE_ACCELERATOR_FALLBACKS += 1
|
|
5807
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
|
|
5808
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
|
|
5809
|
+
elif measured is not None:
|
|
5810
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = measured
|
|
5811
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = int(entry_count)
|
|
5812
|
+
_CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = measured_generation
|
|
5813
|
+
else:
|
|
5814
|
+
# Entry count is exact immediately; bytes remain the last complete
|
|
5815
|
+
# generation until the latest-wins verifier publishes a result.
|
|
5816
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = int(entry_count)
|
|
5817
|
+
|
|
5818
|
+
generation = _take_codex_source_accelerator_dirty()
|
|
5819
|
+
if generation is not None:
|
|
5820
|
+
_CODEX_SOURCE_MEMORY_WORKER.submit(
|
|
5821
|
+
generation, tuple(dict(cache) for cache in caches))
|
|
5822
|
+
|
|
5823
|
+
|
|
5824
|
+
def shutdown_codex_source_memory_worker() -> None:
|
|
5825
|
+
"""Cancel and join the one aggregate verifier during dashboard shutdown."""
|
|
5826
|
+
global _CODEX_SOURCE_MEMORY_WORKER
|
|
5827
|
+
worker = _CODEX_SOURCE_MEMORY_WORKER
|
|
5828
|
+
if not worker.shutdown():
|
|
5829
|
+
raise RuntimeError("Codex source memory verifier did not stop")
|
|
5830
|
+
_CODEX_SOURCE_MEMORY_WORKER = _CodexSourceMemoryWorker()
|
|
5831
|
+
|
|
5832
|
+
|
|
5833
|
+
def codex_source_accelerator_memory_stats() -> Mapping[str, int]:
|
|
5834
|
+
"""Safe aggregate diagnostics for every source-build accelerator."""
|
|
5835
|
+
with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
|
|
5836
|
+
return MappingProxyType({
|
|
5837
|
+
"estimatedBytes": int(_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES),
|
|
5838
|
+
"maxBytes": int(_CODEX_SOURCE_ACCELERATOR_MAX_BYTES),
|
|
5839
|
+
"entryCount": int(_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT),
|
|
5840
|
+
"maxEntries": int(_CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES),
|
|
5841
|
+
"evictionCount": int(_CODEX_SOURCE_ACCELERATOR_EVICTIONS),
|
|
5842
|
+
"fallbackCount": int(_CODEX_SOURCE_ACCELERATOR_FALLBACKS),
|
|
5843
|
+
"measurementErrorCount": int(
|
|
5844
|
+
_CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS),
|
|
5845
|
+
"measuredGeneration": int(
|
|
5846
|
+
_CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION),
|
|
5847
|
+
"currentGeneration": int(_CODEX_SOURCE_ACCELERATOR_GENERATION),
|
|
5848
|
+
"measurementPending": int(
|
|
5849
|
+
_CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
|
|
5850
|
+
!= _CODEX_SOURCE_ACCELERATOR_GENERATION
|
|
5851
|
+
),
|
|
5852
|
+
"workerAlive": int(
|
|
5853
|
+
getattr(_CODEX_SOURCE_MEMORY_WORKER, "is_alive", lambda: False)()
|
|
5854
|
+
),
|
|
5855
|
+
})
|
|
5856
|
+
|
|
5857
|
+
|
|
5066
5858
|
def reset_codex_source_caches() -> None:
|
|
5067
5859
|
"""Discard every reuse a Codex source build could draw on.
|
|
5068
5860
|
|
|
@@ -5071,9 +5863,21 @@ def reset_codex_source_caches() -> None:
|
|
|
5071
5863
|
cache state too, which lives in ``_lib_snapshot_cache`` rather than in a
|
|
5072
5864
|
dict here and is the other half of what a build retains.
|
|
5073
5865
|
"""
|
|
5074
|
-
|
|
5866
|
+
global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
5867
|
+
global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
|
|
5868
|
+
global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
|
|
5869
|
+
global _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
|
|
5870
|
+
reset_codex_quota_observation_cache()
|
|
5871
|
+
for cache in _codex_source_caches()[1:]:
|
|
5075
5872
|
cache.clear()
|
|
5873
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
|
|
5076
5874
|
_lib_snapshot_cache.reset_codex_accounting_cache_state()
|
|
5875
|
+
with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
|
|
5876
|
+
_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
|
|
5877
|
+
_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
|
|
5878
|
+
_mark_codex_source_accelerators_clean()
|
|
5879
|
+
_CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = (
|
|
5880
|
+
_CODEX_SOURCE_ACCELERATOR_GENERATION)
|
|
5077
5881
|
|
|
5078
5882
|
|
|
5079
5883
|
@dataclass(frozen=True)
|
|
@@ -5110,6 +5914,7 @@ def _capture_codex_accounting(
|
|
|
5110
5914
|
context.speed,
|
|
5111
5915
|
tuple(str(root) for root in path_scope.roots),
|
|
5112
5916
|
active_roots,
|
|
5917
|
+
_cache_database_identity(context.cache_conn),
|
|
5113
5918
|
),
|
|
5114
5919
|
load_all=lambda: load_qualified_codex_entries(
|
|
5115
5920
|
accounting_start,
|
|
@@ -5203,13 +6008,20 @@ class CodexSourceCapture:
|
|
|
5203
6008
|
card_population: tuple[object, ...]
|
|
5204
6009
|
breakdowns: Mapping
|
|
5205
6010
|
cache_checkpoint: tuple[dict, ...]
|
|
6011
|
+
quota_cache_stats_checkpoint: tuple[dict, int, int, int]
|
|
6012
|
+
account_card_fallbacks_checkpoint: int
|
|
5206
6013
|
accounting_checkpoint: object
|
|
5207
6014
|
|
|
5208
6015
|
|
|
5209
6016
|
def _restore_codex_source_capture(capture: CodexSourceCapture) -> None:
|
|
6017
|
+
global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
5210
6018
|
for cache, prior in zip(_codex_source_caches(), capture.cache_checkpoint):
|
|
5211
6019
|
cache.clear()
|
|
5212
6020
|
cache.update(prior)
|
|
6021
|
+
_restore_quota_observation_cache_checkpoint(
|
|
6022
|
+
capture.quota_cache_stats_checkpoint)
|
|
6023
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = (
|
|
6024
|
+
capture.account_card_fallbacks_checkpoint)
|
|
5213
6025
|
_lib_snapshot_cache.restore_codex_accounting_cache_state(
|
|
5214
6026
|
capture.accounting_checkpoint)
|
|
5215
6027
|
|
|
@@ -5220,8 +6032,11 @@ def capture_codex_source_state(
|
|
|
5220
6032
|
path_scope: object,
|
|
5221
6033
|
) -> CodexSourceCapture:
|
|
5222
6034
|
"""Capture Codex cache evidence without constructing public view models."""
|
|
6035
|
+
global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
5223
6036
|
caches = _codex_source_caches()
|
|
5224
6037
|
cache_checkpoint = tuple(dict(cache) for cache in caches)
|
|
6038
|
+
quota_cache_stats_checkpoint = _quota_observation_cache_checkpoint()
|
|
6039
|
+
account_card_fallbacks_checkpoint = _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
|
|
5225
6040
|
accounting_checkpoint = (
|
|
5226
6041
|
_lib_snapshot_cache.checkpoint_codex_accounting_cache_state()
|
|
5227
6042
|
)
|
|
@@ -5231,19 +6046,21 @@ def capture_codex_source_state(
|
|
|
5231
6046
|
"SELECT source_root_key FROM codex_source_roots"
|
|
5232
6047
|
)
|
|
5233
6048
|
))
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
6049
|
+
with _lib_perf.phase("source.quota"):
|
|
6050
|
+
quota_observations = tuple(_cached_codex_quota_observations(
|
|
6051
|
+
source_root_keys=active_roots,
|
|
6052
|
+
cache_conn=context.cache_conn,
|
|
6053
|
+
stats_conn=context.stats_conn,
|
|
6054
|
+
stats_identity=context.stats_identity,
|
|
6055
|
+
memoize=False,
|
|
6056
|
+
captured_at_or_after=(
|
|
6057
|
+
context.now_utc - dt.timedelta(days=DASHBOARD_QUOTA_RECENT_DAYS)
|
|
6058
|
+
),
|
|
6059
|
+
active_at=context.now_utc,
|
|
6060
|
+
max_rows=DASHBOARD_QUOTA_OBSERVATION_LIMIT,
|
|
6061
|
+
))
|
|
6062
|
+
with _lib_perf.phase("source.projection"):
|
|
6063
|
+
projection_incoherent = not codex_projection_coherence(context).coherent
|
|
5247
6064
|
ingest_backlog = _codex_ingest_backlog_wire(context.cache_conn)
|
|
5248
6065
|
accounting_end = context.now_utc + dt.timedelta(microseconds=1)
|
|
5249
6066
|
accounting_start = context.range_start
|
|
@@ -5256,19 +6073,20 @@ def capture_codex_source_state(
|
|
|
5256
6073
|
_warn_codex_budget_window_once("accounting_range")
|
|
5257
6074
|
else:
|
|
5258
6075
|
accounting_start = min(accounting_start, budget_start)
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
6076
|
+
with _lib_perf.phase("source.accounting"):
|
|
6077
|
+
health = load_codex_project_metadata_health(
|
|
6078
|
+
cache_conn=context.cache_conn,
|
|
6079
|
+
start=accounting_start,
|
|
6080
|
+
end=accounting_end,
|
|
6081
|
+
)
|
|
6082
|
+
accounting = _capture_codex_accounting(
|
|
6083
|
+
context,
|
|
6084
|
+
accounting_start=accounting_start,
|
|
6085
|
+
accounting_end=accounting_end,
|
|
6086
|
+
active_roots=active_roots,
|
|
6087
|
+
path_scope=path_scope,
|
|
6088
|
+
metadata_incomplete=health.incomplete_rows > 0,
|
|
6089
|
+
)
|
|
5272
6090
|
try:
|
|
5273
6091
|
accounting_exists = has_cached_codex_accounting_entries(
|
|
5274
6092
|
cache_conn=context.cache_conn)
|
|
@@ -5281,12 +6099,13 @@ def capture_codex_source_state(
|
|
|
5281
6099
|
accounting_exists_error = exc
|
|
5282
6100
|
conversation_metadata = MappingProxyType(
|
|
5283
6101
|
_codex_conversation_metadata(context.cache_conn))
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
6102
|
+
with _lib_perf.phase("source.accounts"):
|
|
6103
|
+
try:
|
|
6104
|
+
import _cctally_account
|
|
6105
|
+
decorated = _cctally_account.provider_is_decorated(
|
|
6106
|
+
context.stats_conn, "codex")
|
|
6107
|
+
except Exception:
|
|
6108
|
+
decorated = False
|
|
5290
6109
|
card_population_start = accounting_start
|
|
5291
6110
|
if decorated:
|
|
5292
6111
|
card_population_start = min(
|
|
@@ -5318,8 +6137,9 @@ def capture_codex_source_state(
|
|
|
5318
6137
|
cache_conn=context.cache_conn,
|
|
5319
6138
|
)
|
|
5320
6139
|
)
|
|
5321
|
-
|
|
5322
|
-
|
|
6140
|
+
with _lib_perf.phase("source.models"):
|
|
6141
|
+
breakdowns = _capture_quota_breakdown_evidence(
|
|
6142
|
+
context, quota_observations)
|
|
5323
6143
|
return CodexSourceCapture(
|
|
5324
6144
|
context=context,
|
|
5325
6145
|
active_roots=active_roots,
|
|
@@ -5337,12 +6157,19 @@ def capture_codex_source_state(
|
|
|
5337
6157
|
card_population=tuple(card_population),
|
|
5338
6158
|
breakdowns=breakdowns,
|
|
5339
6159
|
cache_checkpoint=cache_checkpoint,
|
|
6160
|
+
quota_cache_stats_checkpoint=quota_cache_stats_checkpoint,
|
|
6161
|
+
account_card_fallbacks_checkpoint=(
|
|
6162
|
+
account_card_fallbacks_checkpoint),
|
|
5340
6163
|
accounting_checkpoint=accounting_checkpoint,
|
|
5341
6164
|
)
|
|
5342
6165
|
except Exception:
|
|
5343
6166
|
for cache, prior in zip(caches, cache_checkpoint):
|
|
5344
6167
|
cache.clear()
|
|
5345
6168
|
cache.update(prior)
|
|
6169
|
+
_restore_quota_observation_cache_checkpoint(
|
|
6170
|
+
quota_cache_stats_checkpoint)
|
|
6171
|
+
_CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = (
|
|
6172
|
+
account_card_fallbacks_checkpoint)
|
|
5346
6173
|
_lib_snapshot_cache.restore_codex_accounting_cache_state(
|
|
5347
6174
|
accounting_checkpoint)
|
|
5348
6175
|
raise
|
|
@@ -5356,12 +6183,15 @@ def build_codex_source_state_from_capture(
|
|
|
5356
6183
|
) -> SourceDashboardState:
|
|
5357
6184
|
"""Fold one captured Codex generation without issuing cache SQL."""
|
|
5358
6185
|
try:
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
6186
|
+
with _lib_perf.phase("source.serialization"):
|
|
6187
|
+
result = _build_codex_source_state(
|
|
6188
|
+
capture.context,
|
|
6189
|
+
data_version=data_version,
|
|
6190
|
+
path_scope=path_scope,
|
|
6191
|
+
capture=capture,
|
|
6192
|
+
)
|
|
6193
|
+
enforce_codex_source_accelerator_bounds()
|
|
6194
|
+
return result
|
|
5365
6195
|
except Exception:
|
|
5366
6196
|
_restore_codex_source_capture(capture)
|
|
5367
6197
|
raise
|
|
@@ -5558,7 +6388,6 @@ def _build_codex_source_state(
|
|
|
5558
6388
|
accounting_changed_new = accounting_capture.changed_new
|
|
5559
6389
|
accounting_entries = accounting_capture.entries
|
|
5560
6390
|
metadata_incomplete = accounting_capture.metadata_incomplete
|
|
5561
|
-
budget_entries = _codex_entries_from_accounting(accounting_entries)
|
|
5562
6391
|
cycles_all: list[CodexCycleBoundary] = []
|
|
5563
6392
|
try:
|
|
5564
6393
|
# Per-account list (#341 Task 2). ``cycles_all`` drives the per-account
|
|
@@ -5612,15 +6441,15 @@ def _build_codex_source_state(
|
|
|
5612
6441
|
display_tz_name=context.display_tz_name,
|
|
5613
6442
|
speed=context.speed,
|
|
5614
6443
|
)
|
|
5615
|
-
visible_accounting_entries =
|
|
5616
|
-
|
|
5617
|
-
if context.range_start
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
6444
|
+
visible_accounting_entries = (
|
|
6445
|
+
accounting_entries
|
|
6446
|
+
if accounting_start == context.range_start
|
|
6447
|
+
else tuple(
|
|
6448
|
+
entry for entry in accounting_entries
|
|
6449
|
+
if context.range_start
|
|
6450
|
+
<= getattr(entry, "timestamp").astimezone(UTC)
|
|
6451
|
+
< accounting_end
|
|
6452
|
+
)
|
|
5624
6453
|
)
|
|
5625
6454
|
changed_old_visible = tuple(
|
|
5626
6455
|
entry for entry in accounting_changed_old
|
|
@@ -5630,6 +6459,51 @@ def _build_codex_source_state(
|
|
|
5630
6459
|
entry for entry in accounting_changed_new
|
|
5631
6460
|
if context.range_start <= entry.timestamp.astimezone(UTC) < accounting_end
|
|
5632
6461
|
)
|
|
6462
|
+
population_signature = (
|
|
6463
|
+
"codex-visible-population-v1",
|
|
6464
|
+
_cache_database_identity(context.cache_conn),
|
|
6465
|
+
context.range_start,
|
|
6466
|
+
metadata_incomplete,
|
|
6467
|
+
context.speed,
|
|
6468
|
+
active_roots,
|
|
6469
|
+
)
|
|
6470
|
+
# #681: the durable path delta updates only the affected account partitions.
|
|
6471
|
+
# A quota/stat-only generation returns the prior fold directly. The parent
|
|
6472
|
+
# tuple is restored in authoritative accounting encounter order, preserving
|
|
6473
|
+
# every downstream floating-point and first-seen-model byte.
|
|
6474
|
+
if metadata_incomplete:
|
|
6475
|
+
# The cache-only carrier has no durable old/new path population. Its
|
|
6476
|
+
# full reload is therefore also the presentation fold: no process memo
|
|
6477
|
+
# may interpret an empty delta as proof that an id-stable update did
|
|
6478
|
+
# not happen. Every downstream aggregate cache has the same evidence
|
|
6479
|
+
# gap, so use the established complete accounting reset rather than
|
|
6480
|
+
# clearing only the two newest generations: period, weekly, project,
|
|
6481
|
+
# session, cache-report and adapter memos would otherwise retain an
|
|
6482
|
+
# id-stable update. Returning to qualified reads also starts cold.
|
|
6483
|
+
reset_codex_account_scope_cache()
|
|
6484
|
+
(
|
|
6485
|
+
entries,
|
|
6486
|
+
visible_rows_by_account,
|
|
6487
|
+
visible_entries_by_account,
|
|
6488
|
+
) = _codex_fold_visible_rows(visible_accounting_entries)
|
|
6489
|
+
visible_account_versions: dict[str, int] = {}
|
|
6490
|
+
else:
|
|
6491
|
+
(
|
|
6492
|
+
entries,
|
|
6493
|
+
visible_rows_by_account,
|
|
6494
|
+
visible_entries_by_account,
|
|
6495
|
+
visible_account_versions,
|
|
6496
|
+
) = _cached_codex_visible_rows(
|
|
6497
|
+
visible_accounting_entries,
|
|
6498
|
+
changed_old=changed_old_visible,
|
|
6499
|
+
changed_new=changed_new_visible,
|
|
6500
|
+
semantic_signature=population_signature,
|
|
6501
|
+
)
|
|
6502
|
+
budget_entries = (
|
|
6503
|
+
entries
|
|
6504
|
+
if accounting_start == context.range_start
|
|
6505
|
+
else _codex_entries_from_accounting(accounting_entries)
|
|
6506
|
+
)
|
|
5633
6507
|
changed_old_entries = tuple(_codex_entries_from_accounting(changed_old_visible))
|
|
5634
6508
|
changed_new_entries = tuple(_codex_entries_from_accounting(changed_new_visible))
|
|
5635
6509
|
|
|
@@ -5903,6 +6777,26 @@ def _build_codex_source_state(
|
|
|
5903
6777
|
accounting_start=accounting_start,
|
|
5904
6778
|
accounting_end=accounting_end,
|
|
5905
6779
|
population=card_population,
|
|
6780
|
+
population_by_account=(
|
|
6781
|
+
visible_rows_by_account
|
|
6782
|
+
if card_population is accounting_entries
|
|
6783
|
+
and accounting_start == context.range_start
|
|
6784
|
+
else None
|
|
6785
|
+
),
|
|
6786
|
+
account_versions=(
|
|
6787
|
+
visible_account_versions
|
|
6788
|
+
if card_population is accounting_entries
|
|
6789
|
+
and accounting_start == context.range_start
|
|
6790
|
+
and visible_account_versions
|
|
6791
|
+
else None
|
|
6792
|
+
),
|
|
6793
|
+
population_signature=(
|
|
6794
|
+
population_signature
|
|
6795
|
+
if card_population is accounting_entries
|
|
6796
|
+
and accounting_start == context.range_start
|
|
6797
|
+
and visible_account_versions
|
|
6798
|
+
else None
|
|
6799
|
+
),
|
|
5906
6800
|
)
|
|
5907
6801
|
# #416 §5.3: the per-account CHILDREN beside the merged parent. The
|
|
5908
6802
|
# scope set is exactly the card set, so every chip the client can
|