Release 0.79

parent 64326fc0
Revision history for Pg-Explain
0.79 2019/06/12
- Added parsing of "Planning time", "Execution time", and "Trigger
time"
0.78 2019/03/29
- Fix parsing of json explain, when there is no trailing new line
character.
......
Build.PL
Changes
MANIFEST This list of files
META.json
META.yml
README
lib/Pg/Explain.pm
lib/Pg/Explain/From.pm
lib/Pg/Explain/FromJSON.pm
......@@ -8,10 +12,6 @@ lib/Pg/Explain/FromXML.pm
lib/Pg/Explain/FromYAML.pm
lib/Pg/Explain/Node.pm
lib/Pg/Explain/StringAnonymizer.pm
MANIFEST This list of files
META.json
META.yml
README
t/00-load.t
t/01-pod.t
t/02-pod-coverage.t
......@@ -129,6 +129,8 @@ t/32-parallel-index-scan-backward.t
t/33-parallel-index-scan.t
t/34-parallel-index-only-scan.t
t/35-sort-mode-and-buffers-from-json.t
t/36-extra-info.t
t/36-extra-info/plan
t/99-manifest.t
t/perlcriticrc
t/perltidyrc
......
......@@ -42,35 +42,35 @@
"provides" : {
"Pg::Explain" : {
"file" : "lib/Pg/Explain.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::From" : {
"file" : "lib/Pg/Explain/From.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::FromJSON" : {
"file" : "lib/Pg/Explain/FromJSON.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::FromText" : {
"file" : "lib/Pg/Explain/FromText.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::FromXML" : {
"file" : "lib/Pg/Explain/FromXML.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::FromYAML" : {
"file" : "lib/Pg/Explain/FromYAML.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::Node" : {
"file" : "lib/Pg/Explain/Node.pm",
"version" : "0.78"
"version" : "0.79"
},
"Pg::Explain::StringAnonymizer" : {
"file" : "lib/Pg/Explain/StringAnonymizer.pm",
"version" : "0.78"
"version" : "0.79"
}
},
"release_status" : "stable",
......@@ -79,6 +79,6 @@
"http://dev.perl.org/licenses/"
]
},
"version" : "0.78",
"version" : "0.79",
"x_serialization_backend" : "JSON::PP version 2.27300_01"
}
......@@ -19,28 +19,28 @@ name: Pg-Explain
provides:
Pg::Explain:
file: lib/Pg/Explain.pm
version: '0.78'
version: '0.79'
Pg::Explain::From:
file: lib/Pg/Explain/From.pm
version: '0.78'
version: '0.79'
Pg::Explain::FromJSON:
file: lib/Pg/Explain/FromJSON.pm
version: '0.78'
version: '0.79'
Pg::Explain::FromText:
file: lib/Pg/Explain/FromText.pm
version: '0.78'
version: '0.79'
Pg::Explain::FromXML:
file: lib/Pg/Explain/FromXML.pm
version: '0.78'
version: '0.79'
Pg::Explain::FromYAML:
file: lib/Pg/Explain/FromYAML.pm
version: '0.78'
version: '0.79'
Pg::Explain::Node:
file: lib/Pg/Explain/Node.pm
version: '0.78'
version: '0.79'
Pg::Explain::StringAnonymizer:
file: lib/Pg/Explain/StringAnonymizer.pm
version: '0.78'
version: '0.79'
requires:
Clone: '0'
Digest::SHA: '0'
......@@ -51,5 +51,5 @@ requires:
perl: '5.010'
resources:
license: http://dev.perl.org/licenses/
version: '0.78'
version: '0.79'
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
......@@ -11,11 +11,11 @@ Pg::Explain - Object approach at reading explain analyze output
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......
......@@ -9,11 +9,11 @@ Pg::Explain::From - Base class for parsers of non-text explain formats.
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromJSON - Parser for explains in JSON format
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......
......@@ -9,11 +9,11 @@ Pg::Explain::FromText - Parser for text based explains
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......@@ -157,6 +157,23 @@ sub parse_source {
next LINE;
}
elsif ( $line =~ m{ \A \s* (Planning|Execution) \s+ time: \s+ (\d+\.\d+) \s+ ms \s* \z }xmsi ) {
my ( $type, $time ) = ( $1, $2 );
next unless $top_node;
$top_node->planning_time( $2 ) if 'planning' eq lc( $type );
$top_node->execution_time( $2 ) if 'execution' eq lc( $type );
}
elsif ( $line =~ m{ \A \s* Trigger \s+ (.*) : \s+ time=(\d+\.\d+) \s+ calls=(\d+) \s* \z }xmsi ) {
my ( $name, $time, $calls ) = ( $1, $2, $3 );
next unless $top_node;
$top_node->add_trigger_time(
{
'name' => $name,
'time' => $time,
'calls' => $calls,
}
);
}
elsif ( $line =~ m{ \A (\s*) ( \S .* \S ) \s* \z }xms ) {
my ( $infoprefix, $info ) = ( $1, $2 );
my $maximal_depth = ( sort { $b <=> $a } grep { $_ < length $infoprefix } keys %element_at_depth )[ 0 ];
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromXML - Parser for explains in XML format
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......
......@@ -10,11 +10,11 @@ Pg::Explain::FromYAML - Parser for explains in YAML format
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......
......@@ -12,11 +12,11 @@ Pg::Explain::Node - Class representing single node from query plan
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......@@ -120,6 +120,30 @@ ArrayRef of strings, each contains textual information (leading and tailing spac
This is not always filled, as it depends heavily on node type and PostgreSQL version.
=head2 planning_time
Planning time, in milliseconds, only valid for top node, taken from "Planning time: ..." below textual explain.
=head2 execution_time
Execution time, in milliseconds, only valid for top node, taken from "Execution time: ..." below textual explain.
=head2 trigger_times
Array with information about trigger calls. Each element contains:
=over
=item * name
=item * time
=item * calls
=back
This information is extracted from "Trigger ..." lines below textual explain.
=head2 sub_nodes
ArrayRef of Pg::Explain::Node objects, which represent sub nodes.
......@@ -165,6 +189,9 @@ sub estimated_row_width { my $self = shift; $self->{ 'estimated_row_width' }
sub estimated_startup_cost { my $self = shift; $self->{ 'estimated_startup_cost' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'estimated_startup_cost' }; }
sub estimated_total_cost { my $self = shift; $self->{ 'estimated_total_cost' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'estimated_total_cost' }; }
sub extra_info { my $self = shift; $self->{ 'extra_info' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'extra_info' }; }
sub planning_time { my $self = shift; $self->{ 'planning_time' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'planning_time' }; }
sub execution_time { my $self = shift; $self->{ 'execution_time' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'execution_time' }; }
sub trigger_times { my $self = shift; $self->{ 'trigger_times' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'trigger_times' }; }
sub force_loops { my $self = shift; $self->{ 'force_loops' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'force_loops' }; }
sub initplans { my $self = shift; $self->{ 'initplans' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'initplans' }; }
sub never_executed { my $self = shift; $self->{ 'never_executed' } = $_[ 0 ] if 0 < scalar @_; return $self->{ 'never_executed' }; }
......@@ -273,6 +300,25 @@ sub add_extra_info {
return;
}
=head2 add_trigger_time
Adds new information about trigger time.
It will be available at $node->trigger_times (returns arrayref)
=cut
sub add_trigger_time {
my $self = shift;
if ( $self->trigger_times ) {
push @{ $self->trigger_times }, @_;
}
else {
$self->trigger_times( [ @_ ] );
}
return;
}
=head2 add_subplan
Adds new subplan node.
......@@ -438,6 +484,9 @@ sub get_struct {
$reply->{ 'type' } = $self->type if defined $self->type;
$reply->{ 'scan_on' } = clone( $self->scan_on ) if defined $self->scan_on;
$reply->{ 'extra_info' } = clone( $self->extra_info ) if defined $self->extra_info;
$reply->{ 'planning_time' } = clone( $self->planning_time ) if defined $self->planning_time;
$reply->{ 'execution_time' } = clone( $self->execution_time ) if defined $self->execution_time;
$reply->{ 'trigger_times' } = clone( $self->trigger_times ) if defined $self->trigger_times;
$reply->{ 'is_analyzed' } = $self->is_analyzed;
......@@ -635,6 +684,17 @@ sub as_text {
$textual .= $ip->as_text( $prefix_on_spaces . " " );
}
}
if ( $self->planning_time ) {
$textual .= $prefix_on_spaces . "Planning time: " . $self->planning_time . " ms\n";
}
if ( $self->trigger_times ) {
for my $t ( @{ $self->trigger_times } ) {
$textual .= $prefix_on_spaces . sprintf( "Trigger %s: time=%.3f calls=%d\n", $t->{ 'name' }, $t->{ 'time' }, $t->{ 'calls' } );
}
}
if ( $self->execution_time ) {
$textual .= $prefix_on_spaces . "Execution time: " . $self->execution_time . " ms\n";
}
return $textual;
}
......
......@@ -11,11 +11,11 @@ Pg::Explain::StringAnonymizer - Class to anonymize sets of strings
=head1 VERSION
Version 0.78
Version 0.79
=cut
our $VERSION = '0.78';
our $VERSION = '0.79';
=head1 SYNOPSIS
......
#!perl
use Test::More;
use Test::Deep;
use autodie;
use Pg::Explain;
plan 'tests' => 14;
my $plan_file = 't/36-extra-info/plan';
my $explain = Pg::Explain->new( 'source_file' => $plan_file );
isa_ok( $explain, 'Pg::Explain' );
isa_ok( $explain->top_node, 'Pg::Explain::Node' );
ok( defined $explain->top_node->planning_time, 'planning time defined' );
ok( $explain->top_node->planning_time == 0.057, 'planning time as expected' );
ok( defined $explain->top_node->execution_time, 'execution time defined' );
ok( $explain->top_node->execution_time == 68937.619, 'execution time as expected' );
ok( defined $explain->top_node->trigger_times, 'trigger times defined' );
ok( 5 == scalar @{ $explain->top_node->trigger_times }, 'correct count of triggers' );
ok( 'for constraint fk_df_usage_2_df_scenario' eq $explain->top_node->trigger_times->[ 0 ]->{ 'name' }, 'correct name of first trigger' );
ok( 51330.296 == $explain->top_node->trigger_times->[ 0 ]->{ 'time' }, 'correct time of first trigger' );
ok( 1 == $explain->top_node->trigger_times->[ 0 ]->{ 'calls' }, 'correct calls of first trigger' );
my $textual = $explain->as_text();
my $reparsed = Pg::Explain->new( 'source' => $textual );
isa_ok( $reparsed, 'Pg::Explain' );
isa_ok( $reparsed->top_node, 'Pg::Explain::Node' );
my $expected = $explain->top_node->get_struct();
my $got = $reparsed->top_node->get_struct();
cmp_deeply( $got, $expected, 'Structured match' );
exit;
Delete on df_scenario (cost=0.00..1.52 rows=1 width=6) (actual time=0.036..0.036 rows=0 loops=1)
-> Seq Scan on df_scenario (cost=0.00..1.52 rows=1 width=6) (actual time=0.014..0.018 rows=1 loops=1)
Filter: ((df_scenario_uid)::text = 'f0c383d9-f672-4eb9-917a-db9a802f9e06'::text)
Rows Removed by Filter: 38
Planning time: 0.057 ms
Trigger for constraint fk_df_usage_2_df_scenario: time=51330.296 calls=1
Trigger for constraint fk_df_usage_archive_2_df_scenario: time=17606.506 calls=1
Trigger for constraint fk_df_scenario_2_df_scenario_audit: time=0.387 calls=1
Trigger for constraint fk_df_scenario_usage_filter_2_df_scenario: time=0.207 calls=1
Trigger for constraint fk_df_rightsholder_discrepancy_2_df_scenario: time=0.164 calls=1
Execution time: 68937.619 ms
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment