diff --git a/pgbadger b/pgbadger index eaab6070..b818d9d6 100755 --- a/pgbadger +++ b/pgbadger @@ -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 @@ -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}; } @@ -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}) { diff --git a/t/07_csv_statement_duration.t b/t/07_csv_statement_duration.t new file mode 100644 index 00000000..5f7ca315 --- /dev/null +++ b/t/07_csv_statement_duration.t @@ -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; diff --git a/t/fixtures/csv_statement_without_duration.log b/t/fixtures/csv_statement_without_duration.log new file mode 100644 index 00000000..6dc4533c --- /dev/null +++ b/t/fixtures/csv_statement_without_duration.log @@ -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