Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions pgbadger
Original file line number Diff line number Diff line change
Expand Up @@ -4115,6 +4115,9 @@ sub process_file
$nlines++;
&update_progress_bar($tmpoutfile, $nlines, $stop_offset, $totalsize, \$cursize, \$old_queries_count, \$old_errors_count, $fmt);

# Reset state extracted from the previous csvlog row.
%prefix_vars = ();

next if ( ($row->[11] !~ $parse_regex) || ($row->[11] eq 'LOCATION'));

# Extract the date
Expand Down Expand Up @@ -15830,30 +15833,33 @@ sub load_stats
{
foreach my $stmt (keys %{$_normalyzed_info{$curdb}})
{
foreach my $dt (keys %{$_normalyzed_info{$curdb}{$stmt}{samples}} )
if (exists $_normalyzed_info{$curdb}{$stmt}{samples})
{
foreach my $k (keys %{$_normalyzed_info{$curdb}{$stmt}{samples}{$dt}} ) {
$normalyzed_info{$curdb}{$stmt}{samples}{$dt}{$k} = $_normalyzed_info{$curdb}{$stmt}{samples}{$dt}{$k};
foreach my $dt (keys %{$_normalyzed_info{$curdb}{$stmt}{samples}} )
{
foreach my $k (keys %{$_normalyzed_info{$curdb}{$stmt}{samples}{$dt}} ) {
$normalyzed_info{$curdb}{$stmt}{samples}{$dt}{$k} = $_normalyzed_info{$curdb}{$stmt}{samples}{$dt}{$k};
}
}
}

# Keep only the top N samples
my $i = 1;
foreach my $k (sort {$b <=> $a} keys %{$normalyzed_info{$curdb}{$stmt}{samples}})
{
if ($i > $sample) {
delete $normalyzed_info{$curdb}{$stmt}{samples}{$k};
# Keep only the top N samples
my $i = 1;
foreach my $k (sort {$b <=> $a} keys %{$normalyzed_info{$curdb}{$stmt}{samples}})
{
if ($i > $sample) {
delete $normalyzed_info{$curdb}{$stmt}{samples}{$k};
}
$i++;
}
$i++;
}

$normalyzed_info{$curdb}{$stmt}{count} += $_normalyzed_info{$curdb}{$stmt}{count};

# Set min / max duration for this query
if (!exists $normalyzed_info{$curdb}{$stmt}{min} || ($normalyzed_info{$curdb}{$stmt}{min} > $_normalyzed_info{$curdb}{$stmt}{min})) {
if (defined $_normalyzed_info{$curdb}{$stmt}{min} && (!exists $normalyzed_info{$curdb}{$stmt}{min} || ($normalyzed_info{$curdb}{$stmt}{min} > $_normalyzed_info{$curdb}{$stmt}{min}))) {
$normalyzed_info{$curdb}{$stmt}{min} = $_normalyzed_info{$curdb}{$stmt}{min};
}
if (!exists $normalyzed_info{$curdb}{$stmt}{max} || ($normalyzed_info{$curdb}{$stmt}{max} < $_normalyzed_info{$curdb}{$stmt}{max})) {
if (defined $_normalyzed_info{$curdb}{$stmt}{max} && (!exists $normalyzed_info{$curdb}{$stmt}{max} || ($normalyzed_info{$curdb}{$stmt}{max} < $_normalyzed_info{$curdb}{$stmt}{max}))) {
$normalyzed_info{$curdb}{$stmt}{max} = $_normalyzed_info{$curdb}{$stmt}{max};
}

Expand All @@ -15863,22 +15869,30 @@ sub load_stats
{
$normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{count} +=
$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{count};
$normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{duration} +=
$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{duration};
if (defined $_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{duration})
{
$normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{duration} +=
$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{duration};
}
foreach my $min (keys %{$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min}} )
{
$normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min}{$min} +=
$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min}{$min};
}
foreach my $min (keys %{$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration}} )
if (exists $_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration})
{
$normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration}{$min} +=
$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration}{$min};
foreach my $min (keys %{$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration}} )
{
$normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration}{$min} +=
$_normalyzed_info{$curdb}{$stmt}{chronos}{$day}{$hour}{min_duration}{$min};
}
}
}
}

$normalyzed_info{$curdb}{$stmt}{duration} += $_normalyzed_info{$curdb}{$stmt}{duration};
if (defined $_normalyzed_info{$curdb}{$stmt}{duration}) {
$normalyzed_info{$curdb}{$stmt}{duration} += $_normalyzed_info{$curdb}{$stmt}{duration};
}

if (exists $_normalyzed_info{$curdb}{$stmt}{locks})
{
Expand Down
34 changes: 34 additions & 0 deletions t/07_csv_statement_duration.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use Test::More;
use JSON::XS;

eval { require Text::CSV_XS; 1 }
or plan skip_all => 'Text::CSV_XS is required for csvlog parsing';

plan tests => 5;

my $json = JSON::XS->new;
my $LOG = 't/fixtures/csv_statement_without_duration.log';
my $OUT = 't/csv_statement_without_duration.json';

unlink $OUT;

my $ret = `perl pgbadger -f csv -q -x json -o $OUT $LOG 2>&1`;
is($?, 0, 'Generate json report from csvlog');
diag($ret) if $?;

my $json_ref = $json->decode(`cat $OUT`);
my $queries = $json_ref->{normalyzed_info}{postgres};
my ($statement_key) = grep { /health_check/ } keys %{$queries};
my ($duration_key) = grep { /slow_table/ } keys %{$queries};

ok($duration_key, 'Fixture contains a duration-bearing query');
is($queries->{$duration_key}{duration}, '688786.283', 'Duration-bearing query keeps its duration');
ok($statement_key, 'Fixture contains a statement-only query');
ok(
!exists $queries->{$statement_key}{duration}
&& !exists $queries->{$statement_key}{min}
&& !exists $queries->{$statement_key}{max},
'Statement-only csvlog query does not inherit previous duration'
);

unlink $OUT;
2 changes: 2 additions & 0 deletions t/fixtures/csv_statement_without_duration.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"2026-05-16 05:11:50.269 UTC","example_user","example_db",1001,"192.0.2.10:54321",00000001.000003e9,1,"SELECT","2026-05-16 05:00:00 UTC",1/1,0,LOG,00000,"duration: 688786.283 ms execute stmt_slow/stmt_slow: SELECT example_schema.slow_table.id FROM example_schema.slow_table",,,,,,,,,"example app","client backend",0
"2026-05-16 05:20:03.994 UTC","health_user","health_db",1002,"192.0.2.20:54322",00000002.000003ea,2,"idle","2026-05-16 05:00:00 UTC",2/2,0,LOG,00000,"statement: SELECT example_schema.health_check()",,,,,,,,,"example healthcheck","client backend",0