#!/usr/bin/env perl # Copyright (C) 2022 MariaDB Foundation # Use is subject to license terms # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA # This is a test that runs queries to meassure if the MariaDB cost calculations # are reasonable. # # The following test are run: # - Full table scan of a table # - Range scan of the table # - Index scan of the table # # The output can be used to finetune the optimizer cost variables. # # The table in question is a similar to the 'lineitem' table used by DBT3 # it has 16 field and could be regarded as a 'average kind of table'. # Number of fields and record length places a small role when comparing # index scan and table scan ##################### Standard benchmark inits ############################## use DBI; use Getopt::Long; use Benchmark ':hireswallclock'; package main; $opt_rows=1000000; $opt_test_runs= 2; # Run each test 2 times and take the average $opt_verbose=""; $opt_host=""; $opt_db="test"; $opt_user="test"; $opt_password=""; $opt_socket=undef; $opt_skip_drop= undef; $opt_skip_create= undef; $opt_init_query= undef; $opt_analyze= undef; $opt_where_check= undef; $opt_engine=undef; $opt_comment=undef; $opt_table_suffix=undef; $opt_table_name= undef; $opt_grof= undef; $opt_all_tests=undef; $opt_ratios= undef; $opt_mysql= undef; $has_force_index=1; @arguments= @ARGV; GetOptions("host=s","user=s","password=s", "rows=i","test-runs=i","socket=s", "db=s", "table-name=s", "skip-drop","skip-create", "init-query=s","engine=s","comment=s", "gprof", "one-test=s", "mysql", "all-tests", "ratios", "where-check", "analyze", "verbose") || die "Aborted"; $Mysql::db_errstr=undef; # Ignore warnings from these my ($base_table, $table, $dbh, $where_cost, $real_where_cost, $perf_ratio); if (!$opt_mysql) { @engines= ("aria","innodb","myisam","heap"); } else { @engines= ("innodb","myisam","heap"); } # Special handling for some engines $no_force= 0; if (defined($opt_engine)) { if (lc($engine) eq "archive") { $has_force_index= 0; # Skip tests with force index } } if (defined($opt_gprof) || defined($opt_one_test)) { die "one_test must be defined when --gprof is used" if (!defined($opt_one_test)); die "engine must be defined when --gprof or --one-test is used" if (!defined($opt_engine)); die "function '$opt_one_test' does not exist\n" if (!defined(&{$opt_one_test})); } # We add engine_name to the table name later $opt_table_name="check_costs" if (!defined($opt_table_name)); $base_table="$opt_db.$opt_table_name"; #### #### Start timeing and start test #### $|= 1; # Autoflush if ($opt_verbose) { $opt_analyze= 1; } #### #### Create the table #### my %attrib; $attrib{'PrintError'}=0; if (defined($opt_socket)) { $attrib{'mariadb_socket'}=$opt_socket; } $dbh = DBI->connect("DBI:MariaDB:$opt_db:$opt_host", $opt_user, $opt_password,\%attrib) || die $DBI::errstr; print_mariadb_version(); print "Server options: $opt_comment\n" if (defined($opt_comment)); print "Running tests with $opt_rows rows\n"; print "Program arguments:\n"; for ($i= 0 ; $i <= $#arguments; $i++) { my $arg=$arguments[$i]; if ($arg =~ / /) { if ($arg =~ /([^ =]*)=(.*)/) { print "$1=\"$2\" "; } else { print "\"$arg\"" . " "; } } else { print $arguments[$i] . " "; } } print "\n\n"; @test_names= ("table scan no where", "table scan simple where", "table scan where no match", "table scan complex where", "table scan", "index scan", "index scan 4 parts", "range scan", "eq_ref_index_join", "eq_ref_cluster_join", "eq_ref_join", "eq_ref_btree"); $where_tests=3; # Number of where test to be compared with test[0] if ($opt_mysql) { create_seq_table(); } if ($opt_engine || defined($opt_one_test)) { test_engine(0, $opt_engine); } else { my $i; undef($opt_skip_create); for ($i= 0 ; $i <= $#engines; $i++) { test_engine($i, $engines[$i]); if ($i > 0 && $opt_ratios) { print "\n"; my $j; print "Ratios $engines[$i] / $engines[0]\n"; for ($j= $where_tests+1 ; $j <= $#test_names ; $j++) { if ($res[$i][$j]) { my $cmp_cost= $res[0][$j]->{'cost'} - $res[0][$j]->{'where_cost'}; my $cmp_time= $res[0][$j]->{'time'}; my $cur_cost= $res[$i][$j]->{'cost'} - $res[$i][$j]->{'where_cost'}; my $cur_time= $res[$i][$j]->{'time'}; printf "%14.14s cost: %6.4f time: %6.4f cost_multiplier: %6.4f\n", $test_names[$j], $cur_cost / $cmp_cost, $cur_time / $cmp_time, ($cmp_cost * ($cur_time / $cmp_time))/$cur_cost; } 000000 } } # if ($i + 1 <= $#engines) { print "-------------------------\n\n"; } } print_totals(); } $dbh->do("drop table if exists $table") if (!defined($opt_skip_drop)); $dbh->disconnect; $dbh=0; # Close handler exit(0); sub test_engine() { my ($i, $engine)= @_; my ($cur_rows); setup_engine($engine); setup($opt_init_query); $table= $base_table . "_$engine"; if (!defined($opt_skip_create) || !check_if_table_exist($table)) { my $index_type=""; # We should use btree index with heap to ge range scans $index_type= "using btree" if (lc($engine) eq "heap"); print "Creating table $table of type $engine\n"; $dbh->do("drop table if exists $table"); $dbh->do("create table $table ( `l_orderkey` int(11) NOT NULL, `l_partkey` int(11) DEFAULT NULL, `l_suppkey` int(11) DEFAULT NULL, `l_linenumber` int(11) NOT NULL, `l_extra` int(11) NOT NULL, `l_quantity` double DEFAULT NULL, `l_extendedprice` double DEFAULT NULL, `l_discount` double DEFAULT NULL, `l_tax` double DEFAULT NULL, `l_returnflag` char(1) DEFAULT NULL, `l_linestatus` char(1) DEFAULT NULL, `l_shipDATE` date DEFAULT NULL, `l_commitDATE` date DEFAULT NULL, `l_receiptDATE` date DEFAULT NULL, `l_shipinstruct` char(25) DEFAULT NULL, `l_shipmode` char(10) DEFAULT NULL, `l_comment` varchar(44) DEFAULT NULL, PRIMARY KEY (`l_orderkey`), UNIQUE (`l_linenumber`), UNIQUE (`l_extra`) $index_type, KEY `l_suppkey` $index_type (l_suppkey, l_partkey), KEY `long_suppkey` $index_type (l_partkey, l_suppkey, l_linenumber, l_extra) ) ENGINE= $engine") or die "Got error on CREATE TABLE: $DBI::errstr"; } $cur_rows= get_row_count($table); if ($cur_rows == 0 || !defined($opt_skip_create)) { $dbh->do("insert into $table select seq, seq/10, seq, seq, seq, seq, seq, mod(seq,10)*10, 0, 'a','b', date_add('2000-01-01', interval seq/500 day), date_add('2000-01-10', interval seq/500 day), date_add('2000-01-20', interval seq/500 day), left(md5(seq),25), if(seq & 1,'mail','ship'), repeat('a',mod(seq,40)) from seq_1_to_$opt_rows") or die "Got error on INSERT: $DBI::errstr"; $sth= $dbh->do("analyze table $table") or die "Got error on 'analyze table: " . $dbh->errstr . "\n"; } else { $opt_rows= $cur_rows; die "Table $table is empty. Please run without --skip-create" if ($opt_rows == 0); print "Reusing old table $table, which has $opt_rows rows\n"; } if (!$opt_mysql) { $where_cost=get_variable("optimizer_where_cost"); if (defined($where_cost)) { # Calculate cost of where once. Must be done after table is created $real_where_cost= get_where_cost(); $perf_ratio= $real_where_cost/$where_cost; printf "Performance ratio compared to base computer: %6.4f\n", $perf_ratio; } print "\n"; } else { $where_cost=0.1; # mysql 'm_row_evaluate_cost' } if (defined($opt_one_test)) { if (defined($opt_gprof)) { # Argument is the name of the test function test_with_gprof($opt_one_test, 10); return; } $opt_one_test->(); return; } if ($opt_where_check) { $res[$i][0]= table_scan_without_where(0); $res[$i][1]= table_scan_with_where(1); $res[$i][2]= table_scan_with_where_no_match(2); $res[$i][3]= table_scan_with_complex_where(3); } $res[$i][4]= table_scan_without_where_analyze(4); $res[$i][5]= index_scan(5); $res[$i][6]= index_scan_4_parts(6) if ($opt_all_tests); $res[$i][7]= range_scan(7); $res[$i][8]= eq_ref_index_join(8); $res[$i][9]= eq_ref_clustered_join(9); $res[$i][10]= eq_ref_join(10); $res[$i][11]= eq_ref_join_btree(11); if ($opt_where_check) { printf "Variable optimizer_where_cost: cur: %6.4f real: %6.4f prop: %6.4f\n", $where_cost, $real_where_cost, $perf_ratio; print "Ratio of WHERE costs compared to scan without a WHERE\n"; for ($j= 1 ; $j <= $where_tests ; $j++) { print_where_costs($i,$j,0); } print "\n"; } print "Cost/time ratio for different scans types\n"; for ($j= $where_tests+1 ; $j <= $#test_names ; $j++) { if ($res[$i][$j]) { print_costs($test_names[$j], $res[$i][$j]); } } } sub print_costs($;$) { my ($name, $cur_res)= @_; # Cost without where clause my $cur_cost= $cur_res->{'cost'} - $cur_res->{'where_cost'}; my $cur_time= $cur_res->{'time'}; printf "%-20.20s cost: %9.4f time: %9.4f cost/time: %8.4f\n", $name, $cur_cost, $cur_time, $cur_cost/$cur_time; } sub print_where_costs() { my ($index, $cmp, $base)= @_; my $cmp_time= $res[$index][$cmp]->{'time'}; my $base_time= $res[$index][$base]->{'time'}; printf "%-30.30s time: %6.4f\n", $test_names[$cmp], $cmp_time / $base_time; } # Used to setup things like optimizer_switch or optimizer_cache_hit_ratio sub setup() { my ($query)= @_; my ($sth,$query); $sth= $dbh->do("flush tables") || die "Got error on 'flush tables': " . $dbh->errstr . "\n"; if (defined($query)) { $sth= $dbh->do("$query") || die "Got error on '$query': " . $dbh->errstr . "\n"; } # Set variables that may interfer with timings $query= "set \@\@optimizer_switch='index_condition_pushdown=off'"; $sth= $dbh->do($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; } sub setup_engine() { my ($engine)= @_; my ($sth,$query); if (!$opt_mysql) { # Set variables that may interfere with timings $query= "set global $engine.optimizer_disk_read_ratio=0"; $sth= $dbh->do($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; } } sub create_seq_table { my $name= "seq_1_to_$opt_rows"; my $i; print "Creating $name\n"; $dbh->do("drop table if exists $name") || die "Error on drop: " . $dbh->errstr ."\n"; $dbh->do("create table $name (seq int(11) not null) engine=heap") || die "Error on create: " . $dbh->errstr ."\n"; for ($i= 1 ; $i < $opt_rows ; $i+=10) { $dbh->do("insert into $name values ($i),($i+1),($i+2),($i+3),($i+4),($i+5),($i+6),($i+7),($i+8),($i+9)") || die "Error on insert"; } } ############################################################################## # Query functions ############################################################################## # Calculate the cost of the WHERE clause sub table_scan_without_where() { my ($query_id)= @_; return run_query($test_names[$query_id], "table_scan", "ALL", $opt_rows, "select sum(l_quantity) from $table"); } sub table_scan_with_where() { my ($query_id)= @_; return run_query($test_names[$query_id], "table_scan", "ALL", $opt_rows, "select sum(l_quantity) from $table where l_commitDate >= '2000-01-01' and l_tax >= 0.0"); } sub table_scan_with_where_no_match() { my ($query_id)= @_; return run_query($test_names[$query_id], "table_scan", "ALL", $opt_rows, "select sum(l_quantity) from $table where l_commitDate >= '2000-01-01' and l_tax > 0.0 /* NO MATCH */"); } sub table_scan_with_complex_where() { my ($query_id)= @_; return run_query($test_names[$query_id], "table_scan", "ALL", $opt_rows, "select sum(l_quantity) from $table where l_commitDate >= '2000-01-01' and l_quantity*l_extendedprice-l_discount+l_tax > 0.0"); } # Calculate the time spent for table accesses (done with analyze statment) # Table scan sub table_scan_without_where_analyze() { my ($query_id)= @_; return run_query_with_analyze($test_names[$query_id], "table_scan", "ALL", $opt_rows, "select sum(l_quantity) from $table"); } # Index scan with 2 key parts sub index_scan() { my ($query_id)= @_; return 0 if (!$has_force_index); my ($query_id)= @_; return run_query_with_analyze($test_names[$query_id], "index_scan", "index", $opt_rows, "select count(*) from $table force index (l_suppkey) where l_suppkey >= 0 and l_partkey >=0"); } # Index scan with 2 key parts # This is to check how the number of key parts affects the timeings sub index_scan_4_parts() { my ($query_id)= @_; return 0 if (!$has_force_index); return run_query_with_analyze($test_names[$query_id], "index_scan_4_parts", "index", $opt_rows, "select count(*) from $table force index (long_suppkey) where l_linenumber >= 0 and l_extra >0"); } sub range_scan() { my ($query_id)= @_; return 0 if (!$has_force_index); return run_query_with_analyze($test_names[$query_id], "range_scan", "range", $opt_rows, "select sum(l_orderkey) from $table force index(l_suppkey) where l_suppkey >= 0 and l_partkey >=0 and l_discount>=0.0"); } sub eq_ref_index_join() { my ($query_id)= @_; return run_query_with_analyze($test_names[$query_id], "eq_ref_index_join", "eq_ref", 1, "select straight_join count(*) from seq_1_to_$opt_rows,$table where seq=l_linenumber"); } sub eq_ref_clustered_join() { my ($query_id)= @_; return run_query_with_analyze($test_names[$query_id], "eq_ref_cluster_join", "eq_ref", 1, "select straight_join count(*) from seq_1_to_$opt_rows,$table where seq=l_orderkey"); } sub eq_ref_join() { my ($query_id)= @_; return run_query_with_analyze($test_names[$query_id], "eq_ref_join", "eq_ref", 1, "select straight_join count(*) from seq_1_to_$opt_rows,$table where seq=l_linenumber and l_partkey >= 0"); } sub eq_ref_join_btree() { my ($query_id)= @_; return run_query_with_analyze($test_names[$query_id], "eq_ref_btree", "eq_ref", 1, "select straight_join count(*) from seq_1_to_$opt_rows,$table where seq=l_extra and l_partkey >= 0"); } # Calculate the cost of a basic where clause # This can be used to find out the speed of the current computer compared # to the reference computer on which the costs where calibrated. sub get_where_cost() { my ($loop); $loop=10000000; # Return time in microseconds for one where (= optimizer_where_cost) return query_time("select benchmark($loop, l_commitDate >= '2000-01-01' and l_tax >= 0.0) from $table limit 1")/$loop; } # Run a query to be able to calculate the costs of filter sub cost_of_filtering() { my ($query, $cost1, $cost2); do_query("set \@\@max_rowid_filter_size=10000000," . "optimizer_switch='rowid_filter=on',". "\@\@optimizer_scan_setup_cost=1000000"); do_query("set \@old_cost=\@\@aria.OPTIMIZER_ROW_LOOKUP_COST"); do_query("set global aria.OPTIMIZER_ROW_LOOKUP_COST=1"); do_query("flush tables"); $cost1= run_query_with_analyze("range", "range", "range", 500000, "select count(l_discount) from check_costs_aria as t1 where t1.l_orderkey between 1 and 500000"); $cost2= run_query_with_analyze("range-all", "range-all", "range|filter", 500000, "select count(l_discount) from check_costs_aria as t1 where t1.l_orderkey between 1 and 500000 and l_linenumber between 1 and 500000"); $cost3= run_query_with_analyze("range-none","range-none", "range|filter", 500000, "select count(l_discount) from check_costs_aria as t1 where t1.l_orderkey between 1 and 500000 and l_linenumber between 500000 and 1000000"); do_query("set global aria.OPTIMIZER_ROW_LOOKUP_COST=\@old_cost"); do_query("flush tables"); print_costs("range", $cost1); print_costs("filter-all", $cost2); print_costs("filter-none", $cost3); } sub gprof_cost_of_filtering() { $cost2= run_query_with_analyze("gprof","range-all", "range|filter", 500000, "select count(l_discount) from check_costs_aria as t1 where t1.l_orderkey between 1 and 500000 and l_linenumber between 1 and 500000"); } ############################################################################### # Help functions for running the queries ############################################################################### # Run query and return time for query in microseconds sub query_time() { my ($query)= @_; my ($start_time,$end_time,$time,$ms,$sth,$row); $start_time= new Benchmark; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n"; $end_time=new Benchmark; $row= $sth->fetchrow_arrayref(); $sth=0; $time= timestr(timediff($end_time, $start_time),"nop"); $time =~ /([\d.]*)/; return $1*1000000.0; } # # Run a query and compare the clock time # sub run_query() { my ($full_name, $name, $type, $expected_rows, $query)= @_; my ($start_time,$end_time,$sth,@row,%res,$i,$optimizer_rows); my ($extra, $last_type, $adjust_cost, $ms); $adjust_cost=1.0; print "Timing full query: $full_name\n$query\n"; $sth= $dbh->prepare("explain $query") || die "Got error on 'explain $query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on 'explain $query': " . $dbh->errstr . "\n"; print "explain:\n"; while ($row= $sth->fetchrow_arrayref()) { print $row->[0]; for ($i= 1 ; $i < @$row; $i++) { print " " . $row->[$i] if (defined($row->[$i])); } print "\n"; $extra= $row->[@$row-1]; $last_type= $row->[3]; $optimizer_rows= $row->[8]; } if ($last_type ne $type && ($type ne "index" || !($extra =~ /Using index/))) { print "Warning: Wrong scan type: '$last_type', expected '$type'\n"; } if ($expected_rows >= 0 && (abs($optimizer_rows - $expected_rows)/$expected_rows) > 0.1) { printf "Warning: Expected $expected_rows instead of $optimizer_rows from EXPLAIN. Adjusting costs\n"; $adjust_cost= $expected_rows / $optimizer_rows; } # Do one query to fill the cache $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n"; $end_time=new Benchmark; $row= $sth->fetchrow_arrayref(); $sth=0; # Run query for real $start_time= new Benchmark; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n"; $end_time=new Benchmark; $row= $sth->fetchrow_arrayref(); $sth=0; $time= timestr(timediff($end_time, $start_time),"nop"); $time =~ /([\d.]*)/; $ms= $1*1000.0; $query= "show status like 'last_query_cost'"; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n";; $row= $sth->fetchrow_arrayref(); $sth=0; $cost= $row->[1] * $adjust_cost; printf "%10s time: %10.10s ms cost: %6.4f", $name, $ms, $cost; if ($adjust_cost != 1.0) { printf " (was %6.4f)", $row->[1]; } print "\n\n"; $res{'cost'}= $cost; $res{'time'}= $ms; return \%res; } # # Run a query and compare the table access time from analyze statement # The cost works for queries with one or two tables! # sub run_query_with_analyze() { my ($full_name,$name, $type, $expected_rows, $query)= @_; my ($start_time,$end_time,$sth,@row,%res,$i,$j); my ($optimizer_rows, $optimizer_rows_first); my ($adjust_cost, $ms, $second_ms, $analyze, $local_where_cost); my ($extra, $last_type, $tot_ms, $found_two_tables); $found_two_tables= 0; $adjust_cost=1.0; if (!$opt_mysql) { $local_where_cost= $where_cost/1000 * $opt_rows; } else { $local_where_cost= $where_cost * $opt_rows; } $optimizer_rows_first= undef; print "Timing table access for query: $full_name\n$query\n"; $sth= $dbh->prepare("explain $query") || die "Got error on 'explain $query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on 'explain $query': " . $dbh->errstr . "\n"; print "explain:\n"; if (!$opt_mysql) { $type_pos= 3; $row_pos= 8; } else { $type_pos= 4; $row_pos= 9; } $j= 0; while ($row= $sth->fetchrow_arrayref()) { $j++; print $row->[0]; for ($i= 1 ; $i < @$row; $i++) { print " " . $row->[$i] if (defined($row->[$i])); # print " X" if (!defined($row->[$i])); } print "\n"; $extra= $row->[@$row-1]; $last_type= $row->[$type_pos]; if (!defined($optimizer_rows_first)) { $optimizer_rows_first= $row->[$row_pos]; } $optimizer_rows= $row->[$row_pos]; } $found_two_tables= 1 if ($j > 1); if ($last_type ne $type && ($type ne "index" || !($extra =~ /Using index/))) { print "Warning: Wrong scan type: '$last_type', expected '$type'\n"; } if ($expected_rows >= 0 && (abs($optimizer_rows - $expected_rows)/$expected_rows) > 0.1) { printf "Warning: Expected $expected_rows instead of $optimizer_rows from EXPLAIN. Adjusting costs\n"; $adjust_cost= $expected_rows / $optimizer_rows; } # Do one query to fill the cache if (!defined($opt_grof)) { $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n"; $row= $sth->fetchrow_arrayref(); $sth=0; } # Run the query through analyze statement $tot_ms=0; if (!$opt_mysql) { for ($i=0 ; $i < $opt_test_runs ; $i++) { my ($j); $sth= $dbh->prepare("analyze format=json $query" ) || die "Got error on 'analzye $query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n"; $row= $sth->fetchrow_arrayref(); $analyze= $row->[0]; $sth=0; # Fetch the timings $j=0; while ($analyze =~ /r_table_time_ms": ([0-9.]*)/g) { $tot_ms= $tot_ms+ $1; $j++; } if ($j > 2) { die "Found too many tables, program needs to be extended!" } # Add cost of filtering while ($analyze =~ /r_filling_time_ms": ([0-9.]*)/g) { $tot_ms= $tot_ms+ $1; } } } else { my $local_table= substr($table,index($table,".")+1); for ($i=0 ; $i < $opt_test_runs ; $i++) { my ($j); $sth= $dbh->prepare("explain analyze $query" ) || die "Got error on 'analzye $query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n"; $row= $sth->fetchrow_arrayref(); $analyze= $row->[0]; $sth=0; } # Fetch the timings $j=0; if ($analyze =~ / $local_table .*actual time=([0-9.]*) .*loops=([0-9]*)/g) { my $times= $1; my $loops= $2; $times =~ /\.\.([0-9.]*)/; $times= $1; $times="0.005" if ($times == 0); #print "time: $times \$1: $1 loops: $loops\n"; $tot_ms= $tot_ms+ $times*$loops; $j++; } if ($j > 1) { die "Found too many tables, program needs to be extended!" } } if ($found_two_tables) { # Add the cost of the where for the two tables. The last table # is assumed to have $expected_rows while the first (driving table) # may have less rows. Take that into account when calculalting the # total where cost. $local_where_cost= ($local_where_cost + $local_where_cost * ($optimizer_rows_first/$opt_rows)); } $ms= $tot_ms/$opt_test_runs; if ($opt_analyze) { print "\nanalyze:\n" . $analyze . "\n\n"; } if (!defined($opt_grof)) { # Get last query cost $query= "show status like 'last_query_cost'"; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n";; $row= $sth->fetchrow_arrayref(); $sth=0; $cost= $row->[1] * $adjust_cost; printf "%10s time: %10.10s ms cost-where: %6.4f cost: %6.4f", $name, $ms, $cost - $local_where_cost, $cost; if ($adjust_cost != 1.0) { printf " (cost was %6.4f)", $row->[1]; } } else { printf "%10s time: %10.10s ms", $name, $ms; $cost= 0; $local_where_cost= 0; } print "\n\n"; $res{'cost'}= $cost; $res{'where_cost'}= $local_where_cost; $res{'time'}= $ms; return \%res; } sub do_query() { my ($query)= @_; $dbh->do($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; } sub print_totals() { my ($i, $j); print "Totals per test\n"; for ($j= $where_tests+1 ; $j <= $#test_names; $j++) { print "$test_names[$j]:\n"; for ($i= $0 ; $i <= $#engines ; $i++) { if ($res[$i][$j]) { my $cost= $res[$i][$j]->{'cost'} - $res[$i][$j]->{'where_cost'}; my $ms= $res[$i][$j]->{'time'}; printf "%-8s %10.4f ms cost: %10.4f cost/time: %8.4f\n", $engines[$i], $ms, $cost, $cost/$ms; } } } } # This function can be used to test things with gprof sub test_with_gprof() { my ($function_ref, $loops)= @_; my ($sum, $i, $cost); printf "Running test $function_ref $loops time\n"; $sum= 0; $loops=10; for ($i=0 ; $i < $loops ; $i++) { $cost= $function_ref->(); $sum+= $cost->{'time'}; } print "Average: " . ($sum/$loops) . "\n"; print "Shuting down server\n"; $dbh->do("shutdown") || die "Got error .."; } ############################################################################## # Get various simple data from MariaDB ############################################################################## sub print_mariadb_version() { my ($query, $sth, $row); $query= "select VERSION()"; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n";; $row= $sth->fetchrow_arrayref(); print "Server: $row->[0]"; $query= "show variables like 'VERSION_SOURCE_REVISION'"; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n";; $row= $sth->fetchrow_arrayref(); print " Commit: $row->[1]\n"; } sub get_row_count() { my ($table)= @_; my ($query, $sth, $row); $query= "select count(*) from $table"; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; if (!$sth->execute) { if (!($dbh->errstr =~ /doesn.*exist/)) { die "Got error on '$query': " . $dbh->errstr . "\n"; } return 0; } $row= $sth->fetchrow_arrayref(); return $row->[0]; } sub get_variable() { my ($name)= @_; my ($query, $sth, $row); $query= "select @@" . $name; if (!($sth= $dbh->prepare($query))) { die "Got error on '$query': " . $dbh->errstr . "\n"; } $sth->execute || die "Got error on '$query': " . $dbh->errstr . "\n";; $row= $sth->fetchrow_arrayref(); return $row->[0]; } sub check_if_table_exist() { my ($name)= @_; my ($query,$sth); $query= "select 1 from $name limit 1"; $sth= $dbh->prepare($query) || die "Got error on '$query': " . $dbh->errstr . "\n"; print $sth->fetchrow_arrayref(); if (!$sth->execute || !defined($sth->fetchrow_arrayref())) { return 0; # Table does not exists } return 1; }