Thread: "timer" script from SAMS book or equivalent?
Guys, In the book PostgreSQL (2nd ed) the author mentions a timer script he wrote to analyze various performance bits about PostgreSQL.I've looked everywhere and can't find it. Does anyone know where I can find a copy, or find an equivalent tool? Thanks! John
May be can help you \timing in psql El vie, 05-10-2007 a las 21:32 +0400, John Wells escribió: > Guys, > > In the book PostgreSQL (2nd ed) the author mentions a timer script he wrote to analyze various performance bits about PostgreSQL.I've looked everywhere and can't find it. Does anyone know where I can find a copy, or find an equivalent tool? > > Thanks! > John > > ---------------------------(end of broadcast)--------------------------- > TIP 4: Have you searched our list archives? > > http://archives.postgresql.org/
----- "Felipe de Jesús Molina Bravo" <felipe.molina@inegi.gob.mx> wrote: > May be can help you \timing in psql > > El vie, 05-10-2007 a las 21:32 +0400, John Wells escribió: > > Guys, > > > > In the book PostgreSQL (2nd ed) the author mentions a timer script > he wrote to analyze various performance bits about PostgreSQL. I've > looked everywhere and can't find it. Does anyone know where I can find > a copy, or find an equivalent tool? I'm afraid it's not even close in functionality. Here is sample results from the "timer" tool he mentions. This is the kindof data I'm after: $ timer "SELECT * FROM recalls" +-------------+--------------------------------+-------------------------------+ | | SEQUENTIAL I/O | INDEXED I/O | | |scans |tuples |heap_blks |cached|scans |tuples |idx_blks |cached| |-------------+------+-------+----------+------+------+-------+---------+------+ |pg_aggregate | 0 | 0 | 1 | 0 | 1 | 1 | 2 | 0 | |pg_am | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |pg_amop | 0 | 0 | 2 | 10 | 10 | 24 | 4 | 16 | |pg_amproc | 0 | 0 | 1 | 5 | 6 | 6 | 2 | 10 | |pg_attribute | 0 | 0 | 8 | 14 | 21 | 65 | 6 | 57 | |pg_cast | 0 | 0 | 2 | 6 | 60 | 8 | 2 | 118 | |pg_class | 4 | 740 | 5 | 32 | 18 | 17 | 7 | 34 | |pg_database | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |pg_index | 2 | 146 | 3 | 11 | 8 | 12 | 4 | 12 | |pg_namespace | 2 | 10 | 1 | 2 | 2 | 1 | 2 | 2 | |pg_opclass | 0 | 0 | 2 | 11 | 5 | 73 | 4 | 6 | |pg_operator | 0 | 0 | 4 | 6 | 10 | 10 | 4 | 26 | |pg_proc | 0 | 0 | 6 | 8 | 14 | 14 | 12 | 31 | |pg_rewrite | 0 | 0 | 1 | 1 | 2 | 2 | 2 | 2 | |pg_shadow | 0 | 0 | 1 | 2 | 3 | 3 | 4 | 2 | |pg_statistic | 0 | 0 | 3 | 5 | 33 | 8 | 2 | 64 | |pg_trigger | 0 | 0 | 1 | 1 | 2 | 2 | 2 | 2 | |pg_type | 0 | 0 | 2 | 5 | 7 | 7 | 2 | 12 | |recalls | 1 | 39241 | 4413 | 0 | 0 | 0 | 0 | 0 | +-------------+------+-------+----------+------+------+-------+---------+------+ |Totals | 11 | 40139 | 4458 | 119 | 202 | 253 | 61 | 394 | +-------------+------+-------+----------+------+------+-------+---------+------+ Thanks, John
----- "Owen Hartnett" <owen@clipboardinc.com> wrote: > At 11:57 PM +0400 10/5/07, John Wells wrote: > >----- "Felipe de Jesús Molina Bravo" <felipe.molina@inegi.gob.mx> > wrote: > >> May be can help you \timing in psql > >> > >> El vie, 05-10-2007 a las 21:32 +0400, John Wells escribió: > >> > Guys, > >> > > >> > In the book PostgreSQL (2nd ed) the author mentions a timer > script > >> he wrote to analyze various performance bits about PostgreSQL. > I've > >> looked everywhere and can't find it. Does anyone know where I can > find > >> a copy, or find an equivalent tool? > > > >I'm afraid it's not even close in functionality. Here is sample > >results from the "timer" tool he mentions. This is the kind of data > >I'm after: > > > > This was available on line at www.developers-library.com, but now it > looks like that has gone away. Sometimes an author keeps a web site > for code he uses to supplement a book, but I can't find one for him. > Here's the code: Thanks to all. Having problems building it myself. I plan to reimplement in ruby if possible. Will post results. Thanks, John
> In the book PostgreSQL (2nd ed) the author mentions a timer script he wrote to analyze various performance bits about PostgreSQL.I've looked everywhere and can't find it. Does anyone know where I can find a copy, or find an equivalent tool? > Hi John, sorry for the delay in getting back to you. You can find the timer program at http://www.conjectrix.com/pgbook/index.html. You want the sample data/code for chapter 4. -- Korry
Could someone explain to me how the connection is initialized below? I'm re-writing the script in Ruby...but this is a stumblingblock for me in the way the C++ libs work. Does the library initial conn automagically? How would one emulate thisin another language...would it be to run it as the postgres user and connection to template1? Thanks, John int main( int argc, char * argv[] ) { // how is this being initialized? connection conn; work tran1( conn, "getBegValues" ); const result & begTupleValues = getTupleValues( tran1, argc > 2 ? argv[2] : 0 ); const result & begBlockValues = getBlockValues( tran1, argc > 2 ? argv[2] : 0 ); const result & ignore = tran1.exec( argv[1] ); tran1.commit(); sleep( 1 ); work tran2( conn, "getEndValues" ); const result & endTupleValues = getTupleValues( tran2, argc > 2 ? argv[2] : 0 ); const result & endBlockValues = getBlockValues( tran2, argc > 2 ? argv[2] : 0 ); printDeltas( begTupleValues, endTupleValues, begBlockValues, endBlockValues ); }
My Ruby version is found below. Note it requires the postgres-pr ruby driver. Also note I didn't really ruby-ize it to much...forthe most part it's a one-to-one translation. One different thing to note...this script expects you to have postgres-pr installed via rubygems. You can modify the requirestatements (actually just comment out the rubygems line) if you're is installed in a non-rubygems way. Also note, this requires you to define your connection parameters in env variables, which works in my situation. PG_TIMER_DB - name of the database PG_TIMER_USER - name of database user PG_TIMER_PASS - password of database user PG_TIMER_URI - connection uri that postgres-pr understands. Examples: tcp://localhost:5432 unix:/tmp/.s.PGSQL.5432 Hope it helps someone else. ################################################### require 'rubygems' require 'postgres-pr/connection' $tupleQuery = "SELECT relname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch FROM pg_stat_all_tables" $blockQuery = "SELECT relname, heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit FROM pg_statio_all_tables" $use_jdbc = false def usage usstr = <<-EOL #{$0} <query> Example: #{$0} "select * from users" Note: the following environment variables MUST be set: PG_TIMER_DB - name of the database PG_TIMER_USER - name of database user PG_TIMER_PASS - password of database user PG_TIMER_URI - connection uri that postgres-pr understands. Examples: tcp://localhost:5432 unix:/tmp/.s.PGSQL.5432 EOL puts usstr exit() end $dbname = ENV['PG_TIMER_DB'] $dbuser = ENV['PG_TIMER_USER'] $dbpass = ENV['PG_TIMER_PASS'] $dburi = ENV['PG_TIMER_URI'] [$dbname, $dbuser, $dbpass, $dburi].each do |one| one.nil? && usage() end class PostgresPR::Connection::Result def get_field_at_row(field, row) idx = @fields.collect{|f|f.name}.index field @rows[row][idx] end end class PureDBUtil def initialize() @conn = PostgresPR::Connection.new($dbname, $dbuser, $dbpass, $dburi) end def start_tran @conn.query("BEGIN") end def commit @conn.query("COMMIT") end def exec(query) @conn.query(query) end end class JDBCDBUtil def initialize(dbname=nil) raise Exception, "not implemented" end end def getTupleValues(tran, table_name=nil) if !table_name.nil? return tran.exec($tupleQuery + " ORDER by relname") else return tran.exec($tupleQuery + " WHERE relname = '#{table_name}' ") end end def getBlockValues(tran, table_name) if !table_name.nil? return tran.exec($blockQuery + " ORDER by relname") else return tran.exec($blockQuery + " WHERE relname = '#{table_name}' ") end end def getDelta(n, beginning, ending, col) endVal = 0 begVal = 0 endVal = ending.get_field_at_row(col, n) begVal = beginning.get_field_at_row(col, n) return endVal.to_f - begVal.to_f; end def getColumnWidth(res, col) max = 0 res.rows.each do |one| if one[col].size > max max = one[col].size end end return max end def fill(len, c) c * len end def printDeltas(begTuples, endTuples, begBlocks, endBlocks) nameWidth = 15 str = "" str << '+' << fill( nameWidth, '-' ) << "+-----------------------------------+----------------------------------+" << "\n"; str << '|' << fill( nameWidth, ' ' ) << "| SEQUENTIAL I/O | INDEXED I/O |" << "\n"; str << '|' << fill( nameWidth, ' ' ) << "| scans | tuples |heap_blks |cached | scans | tuples | idx_blks |cached|" << "\n"; str << '|' << fill( nameWidth, '-' ) << "+-------+--------+-----------+------+-------+--------+----------+------+" << "\n"; totSeqScans = 0 totSeqTuples = 0 totHeapBlks = 0 totHeapHits = 0 totIdxScans = 0 totIdxTuples = 0 totIdxBlks = 0 totIdxHits = 0 tableCount = 0 0.upto begTuples.rows.size-1 do |row| seqScans = getDelta(row, begTuples, endTuples, "seq_scan") seqTuples = getDelta(row, begTuples, endTuples, "seq_tup_read") heapBlks = getDelta(row, begBlocks, endBlocks, "heap_blks_read") heapHits = getDelta(row, begBlocks, endBlocks, "heap_blks_hit") idxScans = getDelta(row, begTuples, endTuples, "idx_scan") idxTuples = getDelta(row, begTuples, endTuples, "idx_tup_fetch") idxBlks = getDelta(row, begBlocks, endBlocks, "idx_blks_read") idxHits = getDelta(row, begBlocks, endBlocks, "idx_blks_hit") if(( seqScans + seqTuples + heapBlks + heapHits + idxScans + idxTuples + idxBlks + idxHits ) > 0 ) str << '|' << begTuples.get_field_at_row("relname", row).ljust(nameWidth); str << '|' << seqScans.to_s.rjust(6) << ' ' str << '|' << seqTuples.to_s.rjust(7) << ' ' str << '|' << heapBlks.to_s.rjust(10) << ' ' str << '|' << heapHits.to_s.rjust(5) << ' ' str << '|' << idxScans.to_s.rjust(6) << ' ' str << '|' << idxTuples.to_s.rjust(7) << ' ' str << '|' << idxBlks.to_s.rjust(9) << ' ' str << '|' << idxHits.to_s.rjust(5) << ' ' str << '|' << "\n" tableCount += 1 totSeqScans += seqScans totSeqTuples += seqTuples totHeapBlks += heapBlks totHeapHits += heapHits totIdxScans += idxScans totIdxTuples += idxTuples totIdxBlks += idxBlks totIdxHits += idxHits end end str << '+' << fill( nameWidth, '-' ) << "+-------+--------+-----------+" << "------+-------+--------+----------+------+\n"; if( tableCount > 1 ) str << '|' << "Totals".ljust(nameWidth) str << '|' << totSeqScans.to_s.rjust(6) << ' '; str << '|' << totSeqTuples.to_s.rjust(7) << ' '; str << '|' << totHeapBlks.to_s.rjust(10) << ' '; str << '|' << totHeapHits.to_s.rjust(5) << ' '; str << '|' << totIdxScans.to_s.rjust(6) << ' '; str << '|' << totIdxTuples.to_s.rjust(7) << ' '; str << '|' << totIdxBlks.to_s.rjust(9) << ' '; str << '|' << totIdxHits.to_s.rjust(5) << ' '; str << '|' << "\n"; str << '+' << fill( nameWidth, '-' ) << "+-------+--------+-----------+" << "------+-------+--------+----------+------+\n"; end puts str end def main(args) arg = args[0] usage() if arg.nil? if $use_jdbc db_class = JDBCDBUtil else db_class = PureDBUtil end tran1 = db_class.new() tran1.start_tran() begTupleValues = getTupleValues(tran1, arg) begBlockValues = getBlockValues(tran1, arg) ignore = tran1.exec(args[0]) tran1.commit() sleep 1 tran2 = db_class.new() endTupleValues = getTupleValues(tran2, arg) endBlockValues = getBlockValues(tran2, arg) printDeltas(begTupleValues, endTupleValues, begBlockValues, endBlockValues) end main(ARGV)
Sorry...the first version was a bit rash and left out some features...particularly filtering by table. Here's the patch tocorrect: If anyone wants the corrected version, email me off list. Thanks, John # diff -w pg_timer_old.rb pg_timer.rb 18a19 > app = File.basename $0 20,21c21,25 < #{$0} <query> < Example: #{$0} "select * from users" --- > #{app} <query> [tablename] > or > #{app} /path/to/file/containing/query.sql [tablename] > > Example: #{app} "select * from users" "users" 54a59 > 78c83 < if !table_name.nil? --- > if table_name.nil? 86c91 < if !table_name.nil? --- > if table_name.nil? 194c199,211 < arg = args[0] --- > first = args[0] > query = nil > if !first.nil? and File.exists?(first) > File.open(first, "r") do |f| > query = f.read > end > else > query = first > end > > table = args[1] > > usage() if args.size < 1 196d212 < usage() if arg.nil? 201a218 > 204,206c221,223 < begTupleValues = getTupleValues(tran1, arg) < begBlockValues = getBlockValues(tran1, arg) < ignore = tran1.exec(args[0]) --- > begTupleValues = getTupleValues(tran1, table) > begBlockValues = getBlockValues(tran1, table) > ignore = tran1.exec(query) 212,213c229,232 < endTupleValues = getTupleValues(tran2, arg) < endBlockValues = getBlockValues(tran2, arg) --- > tran2.start_tran() > endTupleValues = getTupleValues(tran2, table) > endBlockValues = getBlockValues(tran2, table) > tran2.commit()
The version sent yesterday was creating a LOT of unnecessary objects when running queries with large result sets. I tweakedthe postpres-pr classes (via Ruby's very nice open class mechanism). The updated version is below...it took queriesrunning 10+ minutes down to around 20 seconds. Thanks, John --------------------- require 'rubygems' require 'postgres-pr/connection' require 'postgres-pr/message' $tupleQuery = "SELECT relname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch FROM pg_stat_all_tables" $blockQuery = "SELECT relname, heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit FROM pg_statio_all_tables" $use_jdbc = false def usage app = File.basename $0 usstr = <<-EOL #{app} <query> [tablename] or #{app} /path/to/file/containing/query.sql [tablename] Example: #{app} "select * from users" "users" Note: the following environment variables MUST be set: PG_TIMER_DB - name of the database PG_TIMER_USER - name of database user PG_TIMER_PASS - password of database user PG_TIMER_URI - connection uri that postgres-pr understands. Examples: tcp://localhost:5432 unix:/tmp/.s.PGSQL.5432 EOL puts usstr exit end class PostgresPR::Connection::Result def get_field_at_row(field, row) idx = @fields.collect{|f|f.name}.index field @rows[row][idx] end end class PostgresPR::Connection def query_no_results(sql) puts "Running query in background. Waiting..." @conn << PostgresPR::Query.dump(sql) loop do msg = PostgresPR::Message.read_without_buffer(@conn) case msg when PostgresPR::ReadyForQuery break end end end end class PostgresPR::Message def self.read_without_buffer(stream, startup=false) type = stream.readbytes(1).unpack('C').first unless startup length = stream.readbytes(4).unpack('N').first # FIXME: length should be signed, not unsigned if type==?Z buffer = Buffer.of_size(startup ? length : 1+length) buffer.write_byte(type) unless startup buffer.write_int32_network(length) buffer.copy_from_stream(stream, length-4) (startup ? StartupMessage : MsgTypeMap[type]).create(buffer) else stream.read(length - 4) return nil end end end class PureDBUtil def initialize() @conn = PostgresPR::Connection.new($dbname, $dbuser, $dbpass, $dburi) end def start_tran @conn.query("BEGIN") end def commit(results=true) if results @conn.query("COMMIT") else @conn.query_no_results("COMMIT") end end def exec(query, results=true) if results @conn.query(query) else @conn.query_no_results(query) end end end class JDBCDBUtil def initialize(dbname=nil) raise Exception, "not implemented" end end def getTupleValues(tran, table_name=nil) if table_name.nil? return tran.exec($tupleQuery + " ORDER by relname") else return tran.exec($tupleQuery + " WHERE relname = '#{table_name}' ") end end def getBlockValues(tran, table_name) if table_name.nil? return tran.exec($blockQuery + " ORDER by relname") else return tran.exec($blockQuery + " WHERE relname = '#{table_name}' ") end end def getDelta(n, beginning, ending, col) endVal = 0 begVal = 0 endVal = ending.get_field_at_row(col, n) begVal = beginning.get_field_at_row(col, n) return endVal.to_f - begVal.to_f; end def gcw(res, col) max = 0 0.upto res.rows.size-1 do |n| fld_size = res.get_field_at_row(col, n).size if fld_size > max max = fld_size end end return max end def fill(len, c) c * len end def printDeltas(begTuples, endTuples, begBlocks, endBlocks) nameWidth = gcw(begTuples, "relname") str = "" str << '+' << fill( nameWidth, '-' ) << "+-----------------------------------+----------------------------------+" << "\n"; str << '|' << fill( nameWidth, ' ' ) << "| SEQUENTIAL I/O | INDEXED I/O |" << "\n"; str << '|' << fill( nameWidth, ' ' ) << "| scans | tuples |heap_blks |cached| scans | tuples | idx_blks |cached|" << "\n"; str << '|' << fill( nameWidth, '-' ) << "+-------+--------+-----------+------+-------+--------+----------+------+" << "\n"; totSeqScans = 0 totSeqTuples = 0 totHeapBlks = 0 totHeapHits = 0 totIdxScans = 0 totIdxTuples = 0 totIdxBlks = 0 totIdxHits = 0 tableCount = 0 0.upto begTuples.rows.size-1 do |row| seqScans = getDelta(row, begTuples, endTuples, "seq_scan") seqTuples = getDelta(row, begTuples, endTuples, "seq_tup_read") heapBlks = getDelta(row, begBlocks, endBlocks, "heap_blks_read") heapHits = getDelta(row, begBlocks, endBlocks, "heap_blks_hit") idxScans = getDelta(row, begTuples, endTuples, "idx_scan") idxTuples = getDelta(row, begTuples, endTuples, "idx_tup_fetch") idxBlks = getDelta(row, begBlocks, endBlocks, "idx_blks_read") idxHits = getDelta(row, begBlocks, endBlocks, "idx_blks_hit") if(( seqScans + seqTuples + heapBlks + heapHits + idxScans + idxTuples + idxBlks + idxHits ) > 0 ) str << '|' << begTuples.get_field_at_row("relname", row).ljust(nameWidth); str << '|' << seqScans.to_s.rjust(6) << ' ' str << '|' << seqTuples.to_s.rjust(7) << ' ' str << '|' << heapBlks.to_s.rjust(10) << ' ' str << '|' << heapHits.to_s.rjust(5) << ' ' str << '|' << idxScans.to_s.rjust(6) << ' ' str << '|' << idxTuples.to_s.rjust(7) << ' ' str << '|' << idxBlks.to_s.rjust(9) << ' ' str << '|' << idxHits.to_s.rjust(5) << ' ' str << '|' << "\n" tableCount += 1 totSeqScans += seqScans totSeqTuples += seqTuples totHeapBlks += heapBlks totHeapHits += heapHits totIdxScans += idxScans totIdxTuples += idxTuples totIdxBlks += idxBlks totIdxHits += idxHits end end str << '+' << fill( nameWidth, '-' ) << "+-------+--------+-----------+" << "------+-------+--------+----------+------+\n"; if( tableCount > 1 ) str << '|' << "Totals".ljust(nameWidth) str << '|' << totSeqScans.to_s.rjust(6) << ' '; str << '|' << totSeqTuples.to_s.rjust(7) << ' '; str << '|' << totHeapBlks.to_s.rjust(10) << ' '; str << '|' << totHeapHits.to_s.rjust(5) << ' '; str << '|' << totIdxScans.to_s.rjust(6) << ' '; str << '|' << totIdxTuples.to_s.rjust(7) << ' '; str << '|' << totIdxBlks.to_s.rjust(9) << ' '; str << '|' << totIdxHits.to_s.rjust(5) << ' '; str << '|' << "\n"; str << '+' << fill( nameWidth, '-' ) << "+-------+--------+-----------+" << "------+-------+--------+----------+------+\n"; end puts str end def main(args) $dbname = ENV['PG_TIMER_DB'] $dbuser = ENV['PG_TIMER_USER'] $dbpass = ENV['PG_TIMER_PASS'] $dburi = ENV['PG_TIMER_URI'] [$dbname, $dbuser, $dbpass, $dburi].each do |one| one.nil? && usage() end first = args[0] query = nil if !first.nil? and File.exists?(first) File.open(first, "r") do |f| query = f.read end else query = first end table = args[1] usage() if args.size < 1 if $use_jdbc db_class = JDBCDBUtil else db_class = PureDBUtil end tran1 = db_class.new() tran1.start_tran() begTupleValues = getTupleValues(tran1, table) begBlockValues = getBlockValues(tran1, table) tran1.exec(query,false) tran1.commit(false) sleep 1 tran2 = db_class.new() tran2.start_tran() endTupleValues = getTupleValues(tran2, table) endBlockValues = getBlockValues(tran2, table) tran2.commit() printDeltas(begTupleValues, endTupleValues, begBlockValues, endBlockValues) end main(ARGV)