Files
roacont/.archon/scripts/claude_extract.pl
2026-09-25 12:35:52 +03:00

95 lines
3.5 KiB
Perl

#!/usr/bin/perl
use strict; use warnings;
use JSON::PP;
use Time::Local;
my ($file, $cutoff_start_iso, $cutoff_end_iso) = @ARGV; # optional: only count entries in [start,end]
sub iso_to_epoch {
my ($s) = @_;
return 0 unless defined $s;
if ($s =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)\.(\d+)Z/) {
return timegm($6,$5,$4,$3,$2-1,$1) * 1000 + $7;
}
return 0;
}
my $cutoff_start = $cutoff_start_iso ? iso_to_epoch($cutoff_start_iso) : 0;
my $cutoff_end = $cutoff_end_iso ? iso_to_epoch($cutoff_end_iso) : 0;
open(my $fh, "<:encoding(UTF-8)", $file) or die $!;
my $nAssistant=0; my %toolCounts;
my %callById; # toolu_id -> {name,input,issued_ts}
my @durs; my @errs;
my $maxCtx=0; my $sumIn=0; my $sumOut=0;
my $firstTs; my $lastTs;
while (my $line = <$fh>) {
chomp $line;
next unless $line;
my $obj = eval { decode_json($line) };
next unless $obj;
my $ts = iso_to_epoch($obj->{timestamp});
next unless $ts;
next if $cutoff_start && $ts < $cutoff_start;
next if $cutoff_end && $ts > $cutoff_end;
$firstTs = $ts if !defined($firstTs) || $ts < $firstTs;
$lastTs = $ts if !defined($lastTs) || $ts > $lastTs;
if ($obj->{type} eq 'assistant') {
$nAssistant++;
my $msg = $obj->{message};
my $usage = $msg->{usage};
if ($usage) {
my $ctx = ($usage->{input_tokens}//0) + ($usage->{cache_read_input_tokens}//0) + ($usage->{cache_creation_input_tokens}//0);
$maxCtx = $ctx if $ctx > $maxCtx;
$sumIn += $ctx; $sumOut += ($usage->{output_tokens}//0);
}
for my $c (@{$msg->{content}}) {
if ($c->{type} eq 'tool_use') {
$toolCounts{$c->{name}}++;
$callById{$c->{id}} = { name=>$c->{name}, input=>encode_json($c->{input}), issued=>$ts };
}
}
} elsif ($obj->{type} eq 'user') {
my $msg = $obj->{message};
next unless ref($msg->{content}) eq 'ARRAY';
for my $c (@{$msg->{content}}) {
next unless $c->{type} && $c->{type} eq 'tool_result';
my $tcid = $c->{tool_use_id};
my $call = $callById{$tcid};
next unless $call;
my $dur = $ts - $call->{issued};
my $isErr = $c->{is_error} ? 1 : 0;
my $text = ref($c->{content}) ? encode_json($c->{content}) : ($c->{content}//'');
push @durs, { name=>$call->{name}, input=>$call->{input}, dur=>$dur, isError=>$isErr, err=>substr($text,0,200) };
push @errs, $durs[-1] if $isErr;
}
}
}
@durs = sort { $b->{dur} <=> $a->{dur} } @durs;
print "=== $file ===\n";
print "cutoff: $cutoff_start_iso .. ", ($cutoff_end_iso//''), "\n" if $cutoff_start_iso;
print "assistant turns: $nAssistant\n";
print "tool calls total: ", scalar(keys %callById), "\n";
print "tool calls by type: "; for my $k (sort keys %toolCounts) { print "$k=$toolCounts{$k} "; } print "\n";
print "max context (in+cache_read+cache_creation) single call: $maxCtx sum_in(approx): $sumIn sum_out: $sumOut\n";
print "span: ", (($lastTs-$firstTs)//0)/1000, "s\n";
my $toolTimeSum = 0; $toolTimeSum += $_->{dur} for @durs;
print "total tool time (sum all calls): ", $toolTimeSum/1000, "s\n";
print "--- top 10 longest tool calls ---\n";
for my $i (0..9) {
last unless $durs[$i];
my $d = $durs[$i];
my $a = $d->{input}; $a =~ s/\s+/ /g; $a = substr($a,0,100);
printf("%7.1fs %-10s %s %s\n", $d->{dur}/1000, $d->{name}, $a, ($d->{isError}?"ERROR":""));
}
print "--- errors (", scalar(@errs), ") ---\n";
for my $d (@errs) {
my $a = $d->{input}; $a =~ s/\s+/ /g; $a = substr($a,0,100);
my $e = $d->{err}; $e =~ s/\s+/ /g;
print "$d->{name} $a :: $e\n";
}
# sum total tool time (appended quick calc, reuse @durs if still in scope - not, so separate script)