1111import os
1212import re
1313
14- PARSER = "review_context/2 "
14+ PARSER = "review_context/3 "
1515MAX_BYTES = 256 << 20 # a log larger than this is not parsed
1616SAVED = re .compile (r"Full output saved to:?\s*(\S+?\.txt)" )
1717SHELLS = ("exec" , "exec_command" , "shell" , "local_shell" , "container.exec" )
@@ -299,59 +299,23 @@ def exec_label(name, given):
299299 return label (first ) + ("+" if len (commands ) > 1 else "" )
300300
301301
302- def codex (paths ):
302+ def codex (threads ):
303+ """threads: each thread's rollouts, oldest first. The running total and
304+ the order of usage events carry across one thread's rollouts (a resume)
305+ and start again for the next thread."""
303306 out = empty ("codex" )
304307 out ["subagents" ] = None # child sessions are not linked yet
305- by_id , rises , calls , damaged = {}, [], {}, [0 ]
306- for path in paths :
307- out ["sources" ].append (str (path ))
308- previous = None
309- for row in lines (path , damaged ):
310- payload = row .get ("payload" ) if isinstance (row .get ("payload" ), dict ) else {}
311- kind = payload .get ("type" )
312- if row .get ("type" ) == "session_meta" :
313- out ["session_id" ] = out ["session_id" ] or payload .get ("id" )
314- out ["cli_version" ] = out ["cli_version" ] or payload .get ("cli_version" )
315- if kind in ("compacted" , "context_compacted" ) or row .get ("type" ) == "compacted" :
316- out ["compactions" ] += 1
317- if row .get ("type" ) == "token_usage_record" :
318- usage = payload .get ("usage" )
319- ident = payload .get ("response_id" )
320- if not isinstance (usage , dict ):
321- damaged [0 ] += 1
322- continue
323- check_usage (usage , damaged ) # every record, kept or not
324- if ident is None :
325- ident = "unidentified-%d" % len (by_id )
326- out ["warnings" ].append ("usage record without response id" )
327- by_id .setdefault (ident , usage )
328- elif kind == "token_count" and isinstance (payload .get ("info" ), dict ):
329- # a request shows as a change in the cumulative total; a fall
330- # is a reset (a resume), still a request; the same total again
331- # is a repeated record
332- info = payload ["info" ]
333- totals = info .get ("total_token_usage" )
334- last = info .get ("last_token_usage" )
335- if totals is None and last is None :
336- continue # a rate-limit-only record
337- total = totals .get ("total_tokens" ) if isinstance (totals , dict ) else None
338- if not count_ok (total ) or not isinstance (last , dict ):
339- damaged [0 ] += 1
340- continue
341- check_usage (last , damaged )
342- if total != previous :
343- rises .append (last )
344- previous = total
345- elif kind in ("custom_tool_call" , "function_call" ):
346- calls [payload .get ("call_id" )] = exec_label (payload .get ("name" ), payload .get ("input" ) or payload .get ("arguments" ))
347- elif kind in ("custom_tool_call_output" , "function_call_output" ):
348- body = payload .get ("output" )
349- if isinstance (body , list ):
350- text = "" .join (x .get ("text" , "" ) for x in body if isinstance (x , dict ) and isinstance (x .get ("text" ), str ))
351- else :
352- text = body if isinstance (body , str ) else json .dumps (body )
353- if payload .get ("call_id" ) in calls :
354- add_tool (out , calls .pop (payload .get ("call_id" )), len (text ))
308+ by_id , rises , calls , damaged , compacting = {}, [], {}, [0 ], set ()
309+ for paths in threads :
310+ order , previous = [], None # this thread's usage events
311+ for path in paths :
312+ out ["sources" ].append (str (path ))
313+ previous = parse_rollout (path , out , by_id , rises , calls , damaged , order , previous )
314+ # a compaction request is the response record whose next usage event
315+ # is the compaction itself: the total does not include it. Every
316+ # record keeps its own position, repeats included.
317+ compacting |= {ident for (what , ident ), (after , _ ) in zip (order , order [1 :])
318+ if what == "U" and after == "C" }
355319 requests = list (by_id .values ()) or rises
356320 if not requests :
357321 raise ValueError ("no usage records" )
@@ -363,17 +327,80 @@ def codex(paths):
363327 if damaged [0 ]:
364328 raise Damaged ("%d damaged records" % damaged [0 ], out )
365329 if by_id and rises :
366- # both sources describe the same requests in the same order; where
367- # they differ, which is right cannot be told: the figures stay as
368- # found, but nothing reports them as known
369- pairs = zip ( by_id . values (), rises )
370- if len ( rises ) != len ( by_id ) or any (
371- a . get ( k ) != b . get ( k ) for a , b in pairs for k in ( "input_tokens" , "cached_input_tokens" , "output_tokens" )):
372- raise Damaged ( "request sources disagree: %d usage records, %d total changes"
373- % ( len (by_id ), len ( rises )), out )
330+ ordinary = [ usage for ident , usage in by_id . items () if ident not in compacting ]
331+ if not agree ( ordinary , rises ):
332+ # which source is right cannot be told: the figures stay as
333+ # found, but nothing reports them as known
334+ raise Damaged ( "request sources disagree: %d usage records (%d compacting), %d total changes"
335+ % ( len ( by_id ), len ( compacting ), len ( rises )), out )
336+ if compacting :
337+ out [ "warnings" ]. append ( "%d compaction requests missing from totals" % len (compacting ) )
374338 return out
375339
376340
341+ def parse_rollout (path , out , by_id , rises , calls , damaged , order , previous ):
342+ """One rollout's records into the running measurement; returns the last
343+ cumulative total, for the thread's next rollout."""
344+ for row in lines (path , damaged ):
345+ payload = row .get ("payload" ) if isinstance (row .get ("payload" ), dict ) else {}
346+ kind = payload .get ("type" )
347+ if row .get ("type" ) == "session_meta" :
348+ out ["session_id" ] = out ["session_id" ] or payload .get ("id" )
349+ out ["cli_version" ] = out ["cli_version" ] or payload .get ("cli_version" )
350+ if kind in ("compacted" , "context_compacted" ) or row .get ("type" ) == "compacted" :
351+ out ["compactions" ] += 1
352+ order .append (("C" , None ))
353+ if row .get ("type" ) == "token_usage_record" :
354+ usage = payload .get ("usage" )
355+ ident = payload .get ("response_id" )
356+ if not isinstance (usage , dict ):
357+ damaged [0 ] += 1
358+ continue
359+ check_usage (usage , damaged ) # every record, kept or not
360+ if ident is None :
361+ ident = "unidentified-%d" % len (by_id )
362+ out ["warnings" ].append ("usage record without response id" )
363+ order .append (("U" , ident ))
364+ by_id .setdefault (ident , usage )
365+ elif kind == "token_count" and isinstance (payload .get ("info" ), dict ):
366+ # a request shows as a change in the cumulative total; a fall is
367+ # a reset, still a request; the same total again is a repeat
368+ info = payload ["info" ]
369+ totals = info .get ("total_token_usage" )
370+ last = info .get ("last_token_usage" )
371+ if totals is None and last is None :
372+ continue # a rate-limit-only record
373+ total = totals .get ("total_tokens" ) if isinstance (totals , dict ) else None
374+ if not count_ok (total ) or not isinstance (last , dict ):
375+ damaged [0 ] += 1
376+ continue
377+ check_usage (last , damaged )
378+ if total != previous :
379+ rises .append (last )
380+ order .append (("T" , None ))
381+ previous = total
382+ elif kind in ("custom_tool_call" , "function_call" ):
383+ calls [payload .get ("call_id" )] = exec_label (payload .get ("name" ), payload .get ("input" ) or payload .get ("arguments" ))
384+ elif kind in ("custom_tool_call_output" , "function_call_output" ):
385+ body = payload .get ("output" )
386+ if isinstance (body , list ):
387+ text = "" .join (x .get ("text" , "" ) for x in body if isinstance (x , dict ) and isinstance (x .get ("text" ), str ))
388+ else :
389+ text = body if isinstance (body , str ) else json .dumps (body )
390+ if payload .get ("call_id" ) in calls :
391+ add_tool (out , calls .pop (payload .get ("call_id" )), len (text ))
392+ return previous
393+
394+
395+ def agree (records , rises ):
396+ """Whether the response records (compaction requests excluded) and the
397+ cumulative total changes describe the same requests, one for one, in
398+ order, with the same figures."""
399+ keys = ("input_tokens" , "cached_input_tokens" , "output_tokens" )
400+ return len (records ) == len (rises ) and all (
401+ a .get (k ) == b .get (k ) for a , b in zip (records , rises ) for k in keys )
402+
403+
377404def failed (provider , error , retry , partial = None ):
378405 out = {k : None for k in FIELDS }
379406 if partial :
@@ -403,7 +430,7 @@ def measure(attempt, job):
403430 if missing :
404431 # every thread the pass ran must be read, or it is undercounted
405432 return failed (provider , "no rollout for %s" % "," .join (missing ), True )
406- return codex ([p for t in threads for p in found [ t ] ])
433+ return codex ([found [ t ] for t in threads ])
407434 return failed (provider , "unknown provider %r" % provider , False )
408435 except Damaged as error :
409436 return failed (provider , "Damaged: %s" % error .args [0 ], False , error .args [1 ])
0 commit comments