Files
comun/.archon/scripts/pi_extract.pl
2026-09-25 12:35:54 +03:00

105 lines
3.7 KiB
Perl

#!/usr/bin/perl
use strict; use warnings;
use JSON::PP;
use Time::Local;
my ($file, $cutoff_iso) = @ARGV; # cutoff_iso optional: only count entries with timestamp >= this
my $cutoff = 0;
if ($cutoff_iso) {
$cutoff = iso_to_epoch($cutoff_iso);
}
sub iso_to_epoch {
my ($s) = @_;
return 0 unless defined $s;
if ($s =~ /^\d+$/) { return $s + 0; } # already epoch ms
if ($s =~ /(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)\.(\d+)Z/) {
return timegm($6,$5,$4,$3,$2-1,$1) * 1000 + $7;
}
return 0;
}
open(my $fh, "<:encoding(UTF-8)", $file) or die $!;
my @msgs;
while (my $line = <$fh>) {
chomp $line;
next unless $line;
my $obj = eval { decode_json($line) };
next unless $obj;
next unless $obj->{type} eq "message";
my $ts = iso_to_epoch($obj->{message}{timestamp} // $obj->{timestamp} // '');
next if $cutoff && $ts < $cutoff;
push @msgs, { obj=>$obj, ts=>$ts };
}
my $nAssistant=0; my %toolCounts; my @calls; # each: name,args,issued_ts,id
my %callById;
my @results; # {id, ts, isError, textlen}
my $totalInTok=0; my $totalOutTok=0; my $maxInTok=0;
my $firstTs; my $lastTs;
for my $m (@msgs) {
my $obj = $m->{obj};
my $role = $obj->{message}{role} // '?';
$firstTs = $m->{ts} if !defined($firstTs) || $m->{ts} < $firstTs;
$lastTs = $m->{ts} if !defined($lastTs) || $m->{ts} > $lastTs;
if ($role eq 'assistant') {
$nAssistant++;
my $usage = $obj->{message}{usage};
if ($usage) {
my $in = $usage->{input} // $usage->{inputTokens} // $usage->{prompt_tokens} // 0;
my $out = $usage->{output} // $usage->{outputTokens} // $usage->{completion_tokens} // 0;
$totalInTok += $in; $totalOutTok += $out;
$maxInTok = $in if $in > $maxInTok;
}
for my $c (@{$obj->{message}{content}}) {
if ($c->{type} eq 'toolCall') {
$toolCounts{$c->{name}}++;
my $argstr = encode_json($c->{arguments});
$callById{$c->{id}} = { name=>$c->{name}, args=>$argstr, issued=>$m->{ts} };
}
}
} elsif ($role eq 'toolResult') {
my $tcid = $obj->{message}{toolCallId};
my $isErr = $obj->{message}{isError} ? 1 : 0;
my $text = '';
for my $c (@{$obj->{message}{content}}) { $text .= ($c->{text}//'') if $c->{type} eq 'text'; }
push @results, { id=>$tcid, ts=>$m->{ts}, isError=>$isErr, text=>substr($text,0,200) };
}
}
# compute durations: result.ts - issued.ts (per call)
my @durs;
for my $r (@results) {
my $c = $callById{$r->{id}};
next unless $c;
my $dur = $r->{ts} - $c->{issued};
push @durs, { name=>$c->{name}, args=>$c->{args}, dur=>$dur, isError=>$r->{isError}, err=>$r->{text} };
}
@durs = sort { $b->{dur} <=> $a->{dur} } @durs;
print "=== $file ===\n";
print "cutoff: $cutoff_iso ($cutoff)\n" if $cutoff_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 "total input tokens (sum): $totalInTok max single-call input: $maxInTok total output tokens: $totalOutTok\n";
print "span: ", ($lastTs-$firstTs)/1000, "s first=$firstTs last=$lastTs\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->{args}; $a =~ s/\s+/ /g; $a = substr($a,0,100);
printf("%7.1fs %-8s %s %s\n", $d->{dur}/1000, $d->{name}, $a, ($d->{isError}?"ERROR":""));
}
print "--- errors ---\n";
for my $d (@durs) {
if ($d->{isError}) {
my $a = $d->{args}; $a =~ s/\s+/ /g; $a = substr($a,0,100);
my $e = $d->{err}; $e =~ s/\s+/ /g;
print "$d->{name} $a :: $e\n";
}
}