105 lines
3.7 KiB
Perl
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";
|
|
}
|
|
}
|