1
0
mirror of https://gitlab.com/depesz/explain.depesz.com.git synced 2024-11-30 09:06:42 +02:00
explain.depesz.com/scripts/find_interesting_plans
2022-02-06 17:00:12 +01:00

163 lines
4.9 KiB
Perl
Executable File

#!/usr/bin/env perl
# UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
use v5.14;
use strict;
use warnings;
use warnings qw( FATAL utf8 );
use utf8;
use open qw( :std :utf8 );
use Unicode::Normalize qw( NFC );
use Unicode::Collate;
use Encode qw( decode );
if ( grep /\P{ASCII}/ => @ARGV ) {
@ARGV = map { decode( 'UTF-8', $_ ) } @ARGV;
}
# If there is __DATA__,then uncomment next line:
# binmode( DATA, ':encoding(UTF-8)' );
# UTF8 boilerplace, per http://stackoverflow.com/questions/6162484/why-does-modern-perl-avoid-utf-8-by-default/
# Useful common code
use autodie;
use Carp qw( carp croak confess cluck );
use English qw( -no_match_vars );
use Data::Dumper qw( Dumper );
# give a full stack dump on any untrapped exceptions
local $SIG{ __DIE__ } = sub {
confess "Uncaught exception: @_" unless $^S;
};
# now promote run-time warnings into stackdumped exceptions
# *unless* we're in an try block, in which
# case just generate a clucking stackdump instead
local $SIG{ __WARN__ } = sub {
if ( $^S ) { cluck "Trapped warning: @_" }
else { confess "Deadly warning: @_" }
};
# Useful common code
use DBI;
use Parallel::ForkManager;
use List::Util qw( any );
use Sys::Info;
use Pg::Explain;
use File::Temp qw( tempdir );
my $partitions = get_list_of_partitions();
my $cpus = Sys::Info->new->device( 'CPU' )->count;
my $output = tempdir( 'interesting-explains.XXXXXX', TMPDIR => 1 );
printf "Processing %d partitions in %d workers, output goes to %s\n", scalar @{ $partitions }, $cpus, $output;
my $pm = Parallel::ForkManager->new( $cpus );
PARTITION:
for my $partno ( 0 .. $#{ $partitions } ) {
my $part = $partitions->[ $partno ];
$pm->start and next PARTITION;
printf "Starting partition #%d\n", 1 + $partno;
open my $out, '>', "${output}/${part}.txt";
my $dbh = get_dbh();
my $seen = 0;
my $errors = 0;
my $interesting = 0;
my $query = sprintf "select id, plan FROM %s WHERE is_public and NOT is_deleted and NOT is_anonymized and plan ~ 'actual time=[0-9]'", $part;
$dbh->do( "DECLARE csr CURSOR FOR $query" );
while ( 1 ) {
my $rows = $dbh->selectall_arrayref( 'FETCH 100 FROM csr', { 'Slice' => {} } );
last if 0 == scalar @{ $rows };
for my $row ( @{ $rows } ) {
$seen++;
my $explain;
eval {
$explain = Pg::Explain->new( 'source' => $row->{ 'plan' } );
$explain->parse_source();
};
if ( $EVAL_ERROR ) {
$errors++;
next;
}
next unless $explain->top_node;
my $is_interesting = 0;
for my $line ( @{ output_lines( $row->{'id'}, $explain ) } ) {
print $out join("\t", @{$line}) . "\n";
$is_interesting = 1;
}
$interesting++ if $is_interesting;
}
}
$dbh->rollback();
$dbh->disconnect();
close $out;
printf "Partition %d done. %d plans scanned, %d errored out, %d interesting saved.\n", 1 + $partno, $seen, $errors, $interesting;
$pm->finish;
}
$pm->wait_all_children;
printf "All done, output in %s\n", $output;
exit;
sub output_lines {
my ( $id, $plan ) = @_;
my $ret = [];
for my $node ( $plan->top_node, $plan->top_node->all_recursive_subnodes ) {
next unless $node->estimated_row_width;
next unless $node->total_rows;
next unless $node->total_rows_removed;
next unless $node->extra_info;
# At least 3 pages worth of data is returned
next unless $node->total_rows * $node->estimated_row_width > 3 * 8192;
# At least 90% of rows were removed
next unless $node->total_rows_removed > 9 * $node->total_rows;
my @filter_lines = grep { /^Filter:/ } @{ $node->extra_info };
# There are filter expressions
next if 0 == scalar @filter_lines;
push @{ $ret }, map { [ $id, $node->id, $node->type, $node->estimated_row_width, $node->total_rows, $node->total_rows_removed, $_ ] } @filter_lines;
}
return $ret;
}
sub get_dbh {
my $dsn = sprintf 'dbi:Pg:dbname=%s', $ENV{ 'PGDATABASE' } || 'depesz_explain';
$dsn .= sprintf ';host=%s', $ENV{ 'PGHOST' } if $ENV{ 'PGHOST' };
$dsn .= sprintf ';port=%s', $ENV{ 'PGPORT' } if $ENV{ 'PGPORT' };
return DBI->connect( $dsn, undef, undef, { 'AutoCommit' => 0, 'PrintError' => 1, 'RaiseError' => 1 } );
}
sub get_list_of_partitions {
my $dbh = get_dbh();
my $parts = $dbh->selectcol_arrayref( "
SELECT
c.oid::regclass
FROM
pg_catalog.pg_class c
JOIN pg_catalog.pg_inherits i ON c.oid = i.inhrelid
WHERE
i.inhparent = 'public.plans'::regclass
AND c.relkind = 'r'
ORDER BY
c.relpages DESC
" );
$dbh->rollback();
$dbh->disconnect();
return $parts;
}