--- /dev/null
+CREATE TABLE ps_snaps(
+ snap BIGSERIAL PRIMARY KEY,
+ time TIMESTAMP WITH TIME ZONE DEFAULT NOW()
+);
+
+-- PostgreSQL Processes Stats
+CREATE TABLE ps_procstat(
+ snap BIGINT,
+ pid INTEGER,
+ comm VARCHAR,
+ fullcomm VARCHAR,
+ state CHAR,
+ ppid INTEGER,
+ pgrp INTEGER,
+ session INTEGER,
+ tty_nr INTEGER,
+ tpgid INTEGER,
+ flags INTEGER,
+ minflt BIGINT,
+ cminflt BIGINT,
+ majflt BIGINT,
+ cmajflt BIGINT,
+ utime BIGINT,
+ stime BIGINT,
+ cutime BIGINT,
+ cstime BIGINT,
+ priority BIGINT,
+ nice BIGINT,
+ num_threads BIGINT,
+ itrealvalue BIGINT,
+ starttime BIGINT,
+ vsize BIGINT,
+ rss BIGINT,
+ exit_signal INTEGER,
+ processor INTEGER,
+ rt_priority BIGINT,
+ policy BIGINT,
+ delayacct_blkio_ticks BIGINT,
+ uid INTEGER,
+ username VARCHAR,
+ rchar BIGINT,
+ wchar BIGINT,
+ syscr BIGINT,
+ syscw BIGINT,
+ reads BIGINT,
+ writes BIGINT,
+ cwrites BIGINT,
+ datid BIGINT,
+ datname NAME,
+ usesysid BIGINT,
+ usename NAME,
+ current_query TEXT,
+ waiting BOOLEAN,
+ query_start TIMESTAMP WITH TIME ZONE,
+ backend_start TIMESTAMP WITH TIME ZONE,
+ client_addr INET,
+ client_port INTEGER,
+ CONSTRAINT proc_snap
+ FOREIGN KEY (snap)
+ REFERENCES ps_snaps (snap)
+);
+
+-- PostgreSQL Database Stats
+CREATE TABLE ps_dbstat(
+ snap BIGINT,
+ datid BIGINT,
+ datname NAME,
+ numbackends INTEGER,
+ xact_commit BIGINT,
+ xact_rollback BIGINT,
+ blks_read BIGINT,
+ blks_hit BIGINT,
+ PRIMARY KEY (snap, datid),
+ CONSTRAINT db_snap
+ FOREIGN KEY (snap)
+ REFERENCES ps_snaps (snap)
+);
+
+-- PostgreSQL Table Stats
+CREATE TABLE ps_tablestat(
+ snap BIGINT,
+ relid BIGINT,
+ schemaname NAME,
+ relname NAME,
+ seq_scan BIGINT,
+ seq_tup_read BIGINT,
+ idx_scan BIGINT,
+ idx_tup_fetch BIGINT,
+ n_tup_ins BIGINT,
+ n_tup_upd BIGINT,
+ n_tup_del BIGINT,
+ last_vacuum TIMESTAMP WITH TIME ZONE,
+ last_autovacuum TIMESTAMP WITH TIME ZONE,
+ last_analyze TIMESTAMP WITH TIME ZONE,
+ last_autoanalyze TIMESTAMP WITH TIME ZONE,
+ PRIMARY KEY (snap, relid),
+ CONSTRAINT table_snap
+ FOREIGN KEY (snap)
+ REFERENCES ps_snaps (snap)
+);
+
+-- PostgreSQL Index Stats
+CREATE TABLE ps_indexstat(
+ snap BIGINT,
+ relid BIGINT,
+ indexrelid BIGINT,
+ schemaname NAME,
+ relname NAME,
+ indexrelname NAME,
+ idx_scan BIGINT,
+ idx_tup_read BIGINT,
+ idx_tup_fetch BIGINT,
+ PRIMARY KEY (snap, relid, indexrelid),
+ CONSTRAINT index_snap
+ FOREIGN KEY (snap)
+ REFERENCES ps_snaps (snap)
+);
+
+-- System Processor Stats
+CREATE TABLE ps_cpustat(
+ snap BIGINT PRIMARY KEY,
+ cpu_user BIGINT,
+ cpu_nice BIGINT,
+ cpu_system BIGINT,
+ cpu_idle BIGINT,
+ cpu_iowait BIGINT,
+ cpu_swap BIGINT,
+ CONSTRAINT cpu_snap
+ FOREIGN KEY (snap)
+ REFERENCES ps_snaps (snap)
+);
+
+-- System Memory Stats
+CREATE TABLE ps_memstat(
+ snap BIGINT PRIMARY KEY,
+ memused BIGINT,
+ memfree BIGINT,
+ memshared BIGINT,
+ membuffers BIGINT,
+ memcached BIGINT,
+ swapused BIGINT,
+ swapfree BIGINT,
+ swapcached BIGINT,
+ CONSTRAINT mem_snap
+ FOREIGN KEY (snap)
+ REFERENCES ps_snaps (snap)
+);
--- /dev/null
+CREATE OR REPLACE FUNCTION ps_snap_stats() RETURNS BIGINT AS $$
+DECLARE
+ snapid BIGINT;
+BEGIN
+ -- Create the snapshot id.
+ INSERT INTO ps_snaps
+ DEFAULT VALUES
+ RETURNING snap
+ INTO snapid;
+ RAISE DEBUG 'Creating snapshot: %', snapid;
+
+ -- Get system stats.
+ INSERT INTO ps_cpustat(snap, cpu_user, cpu_nice, cpu_system, cpu_idle,
+ cpu_iowait)
+ SELECT snapid, "user", nice, system, idle, iowait
+ FROM pg_cputime();
+
+ INSERT INTO ps_memstat(snap, memused, memfree, memshared, membuffers,
+ memcached, swapused, swapfree, swapcached)
+ SELECT snapid, memused, memfree, memshared, membuffers, memcached,
+ swapused, swapfree, swapcached
+ FROM pg_memusage();
+
+ -- Get database stats.
+ INSERT INTO ps_dbstat(snap, datid, datname, numbackends, xact_commit,
+ xact_rollback, blks_read, blks_hit)
+ SELECT snapid, datid, datname, numbackends, xact_commit,
+ xact_rollback, blks_read, blks_hit
+ FROM pg_catalog.pg_stat_database;
+
+ INSERT INTO ps_tablestat(snap, relid, schemaname, relname, seq_scan,
+ seq_tup_read, idx_scan, idx_tup_fetch, n_tup_ins, n_tup_upd,
+ n_tup_del, last_vacuum, last_autovacuum, last_analyze,
+ last_autoanalyze)
+ SELECT snapid, relid, schemaname, relname, seq_scan,
+ seq_tup_read, idx_scan, idx_tup_fetch, n_tup_ins, n_tup_upd,
+ n_tup_del, last_vacuum, last_autovacuum, last_analyze,
+ last_autoanalyze
+ FROM pg_catalog.pg_stat_all_tables;
+
+ INSERT INTO ps_indexstat(snap, relid, indexrelid, schemaname, relname,
+ indexrelname, idx_scan, idx_tup_read, idx_tup_fetch)
+ SELECT snapid, relid, indexrelid, schemaname, relname,
+ indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
+ FROM pg_catalog.pg_stat_all_indexes;
+
+ -- Get process stats.
+ INSERT INTO ps_procstat(snap, pid, comm, fullcomm, state, ppid, pgrp,
+ session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt,
+ utime, stime, cutime, cstime, priority, nice, num_threads,
+ itrealvalue, starttime, vsize, rss, exit_signal, processor,
+ rt_priority, policy, delayacct_blkio_ticks, uid, username,
+ syscr, syscw, reads, writes, cwrites)
+ SELECT snapid, procpid, comm, fullcomm, state, ppid, pgrp, session, tty_nr,
+ tpgid, flags, minflt, cminflt, majflt, cmajflt, utime, stime,
+ cutime, cstime, priority, nice, num_threads, itrealvalue,
+ starttime, vsize, rss, exit_signal, processor, rt_priority,
+ policy, delayacct_blkio_ticks, uid, username, syscr, syscw,
+ reads, writes, cwrites
+ FROM pg_stat_activity, pg_proctab()
+ WHERE procpid = pid;
+
+ -- Return the id of the snapshot just created.
+ RETURN snapid;
+END;
+$$ LANGUAGE plpgsql;
--- /dev/null
+DROP TABLE ps_indexstat;
+DROP TABLE ps_tablestat;
+DROP TABLE ps_dbstat;
+DROP TABLE ps_memstat;
+DROP TABLE ps_cpustat;
+DROP TABLE ps_procstat;
+DROP TABLE ps_snaps;
--- /dev/null
+#!/bin/bash
+
+if [ $# -ne 3 ]; then
+ echo "Usage: $0 <pid> <snapid1> <snapid2>"
+ exit 1
+fi
+
+PID=$1
+SNAP1=$2
+SNAP2=$3
+
+A=( `psql --no-align --tuples-only --field-separator ' ' --command "SELECT syscr, syscw, reads, writes, cwrites FROM ps_snaps a, ps_procstat b WHERE pid = ${PID} AND a.snap = b.snap AND a.snap = ${SNAP1}"` )
+
+R1=${A[0]}
+W1=${A[1]}
+RB1=${A[2]}
+WB1=${A[3]}
+CWB1=${A[4]}
+
+A=( `psql --no-align --tuples-only --field-separator ' ' --command "SELECT syscr, syscw, reads, writes, cwrites FROM ps_snaps a, ps_procstat b WHERE pid = ${PID} AND a.snap = b.snap AND a.snap = ${SNAP2}"` )
+
+R2=${A[0]}
+W2=${A[1]}
+RB2=${A[2]}
+WB2=${A[3]}
+CWB2=${A[4]}
+
+R=$(( ${R2} - ${R1} ))
+W=$(( ${W2} - ${W1} ))
+RB=$(( ${RB2} - ${RB1} ))
+WB=$(( ${WB2} - ${WB1} ))
+CWB=$(( ${CWB2} - ${CWB1} ))
+
+echo "Reads = ${R}"
+echo "Writes = ${W}"
+echo "Reads (Bytes) = ${RB}"
+echo "Writes (Bytes) = ${WB}"
+echo "Cancelled (Bytes) = ${CWB}"
--- /dev/null
+#!/bin/bash
+
+# FIXME: Hardcoding, best way to get this?
+HZ=1000
+
+if [ $# -ne 3 ]; then
+ echo "Usage: $0 <pid> <snapid1> <snapid2>"
+ exit 1
+fi
+
+PID=$1
+SNAP1=$2
+SNAP2=$3
+
+A=( `psql --no-align --tuples-only --field-separator ' ' --command "SELECT stime, utime, stime + utime AS total, extract(epoch FROM time) FROM ps_snaps a, ps_procstat b WHERE pid = ${PID} AND a.snap = b.snap AND a.snap = ${SNAP1}"` )
+
+STIME1=${A[0]}
+UTIME1=${A[1]}
+TOTAL1=${A[2]}
+TIME1=${A[3]}
+
+A=( `psql --no-align --tuples-only --field-separator ' ' --command "SELECT stime, utime, stime + utime AS total, extract(epoch FROM time) FROM ps_snaps a, ps_procstat b WHERE pid = ${PID} AND a.snap = b.snap AND a.snap = ${SNAP2}"` )
+
+STIME2=${A[0]}
+UTIME2=${A[1]}
+TOTAL2=${A[2]}
+TIME2=${A[3]}
+
+# Get the time difference in ticks.
+TIMEDIFF=`echo "scale = 2; (${TIME2} - ${TIME1}) * ${HZ}" | bc -l`
+
+U=`echo "scale = 2; (${TOTAL2} - ${TOTAL1}) / ${TIMEDIFF} * 100" | bc -l`
+
+echo "Processor Utilization = ${U} %"
--- /dev/null
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+unless ($ENV{PGDATABASE}) {
+ print "PGDATABASE must be set in your environment to determine which \n";
+ print "database to report statistics for.\n";
+ exit(1);
+}
+
+if (scalar @ARGV != 3) {
+ print "Usage: $0 <pid> <snap1> <snap2>\n";
+ exit(1);
+}
+
+my $pid = $ARGV[0];
+my $snap1 = $ARGV[1];
+my $snap2 = $ARGV[2];
+
+my $psql = "psql --tuples-only --no-align --command";
+my $sql;
+my $temp;
+my @a1;
+my @a2;
+
+# Get the snapshot timestamps.
+$temp = "
+SELECT time
+FROM ps_snaps
+WHERE snap = %d
+";
+
+$sql = sprintf($temp, $snap1);
+my $start_time = `$psql "$sql"`;
+chomp $start_time;
+
+$sql = sprintf($temp, $snap2);
+my $end_time = `$psql "$sql"`;
+chomp $end_time;
+
+# Get database stats.
+
+$temp = "
+SELECT numbackends, xact_commit, xact_rollback, blks_read, blks_hit
+FROM ps_dbstat
+WHERE datname = '$ENV{PGDATABASE}'
+ AND snap = %d
+";
+
+$sql = sprintf($temp, $snap1);
+@a1 = split /\|/, `$psql "$sql"`;
+
+$sql = sprintf($temp, $snap2);
+@a2 = split /\|/, `$psql "$sql"`;
+
+my $xact_commit = $a2[0] - $a1[0];
+my $xact_rollback = $a2[1] - $a1[1];
+my $blks_read = $a2[3] - $a1[3];
+my $blks_hit = $a2[4] - $a1[4];
+
+print "Database : $ENV{PGDATABASE}\n";
+print "Snapshot Start : $start_time\n";
+print "Snapshot End : $end_time\n";
+print "\n";
+print "-------------------\n";
+print "Database Statistics\n";
+print "-------------------\n";
+print "Commits : $xact_commit\n";
+print "Rollbacks : $xact_rollback\n";
+print "Blocks Read : $blks_read\n";
+print "Blocks Hit : $blks_hit\n";
+print "\n";
+
+# FIXME: This simple logic breakds if tables are added or dropped between
+# snapshots.
+
+$temp = "
+SELECT schemaname, relname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch,
+ n_tup_ins, n_tup_upd, n_tup_del
+FROM ps_tablestat
+WHERE snap = %d
+ORDER BY schemaname, relname
+";
+
+$sql = sprintf($temp, $snap1);
+@a1 = split /\n/, `$psql "$sql"`;
+
+# Get the vaccum dates of the second snapshot.
+$temp = "
+SELECT schemaname, relname, seq_scan, seq_tup_read, idx_scan, idx_tup_fetch,
+ n_tup_ins, n_tup_upd, n_tup_del, last_vacuum, last_autovacuum,
+ last_analyze, last_autoanalyze
+FROM ps_tablestat
+WHERE snap = %d
+ORDER BY schemaname, relname
+";
+$sql = sprintf($temp, $snap2);
+@a2 = split /\n/, `$psql "$sql"`;
+
+unless (scalar @a1 == scalar @a2) {
+ print "Tables were added or dropped between snapshot $snap1 and $snap2\n";
+ print "This script can't handle that, aborting.\n";
+ exit(1);
+}
+
+my $length = 0;
+
+print "================\n";
+print "Table Statistics\n";
+print "================\n";
+
+# Run through all the names once just to see what the longest one is to
+# determine how to format the output.
+my @c = ();
+for (my $i = 0; $i < scalar @a1; $i++) {
+ my @b1 = split /\|/, $a1[$i];
+ my @b2 = split /\|/, $a2[$i];
+
+ # Some columns are NULL, set to 0.
+ for (my $i = 0; $i < 9; $i++) {
+ $b1[$i] = 0 unless ($b1[$i]);
+ $b2[$i] = 0 unless ($b2[$i]);
+ }
+ for (my $i = 9; $i < 13; $i++) {
+ $b2[$i] = 'N/A' unless ($b2[$i]);
+ }
+ my $name = "$b1[0].$b1[1]";
+ $length = length $name if (length $name > $length);
+ push @c, ([$name, $b2[2] - $b1[2], $b2[3] - $b1[3], $b2[4] - $b1[4],
+ $b2[5] - $b1[5], $b2[6] - $b1[6], $b2[7] - $b1[7],
+ $b2[8] - $b1[8], "$b2[9]", "$b2[10]", "$b2[11]", "$b2[12]"]);
+}
+
+my @header = ("Schema.Relation", "Seq Scan", "Seq Tup Read",
+ "Idx Scan", "Idx Tup Fetch", "N Tup Ins", "N Tup Upd", "N Tup Del",
+ "Last Vacuum", "Last Autovacuum", "Last Analyze", "Last Autoanalyze");
+my @l = ();
+my @d = ();
+foreach (@header) {
+ push @l, length $_;
+ my $dashes = '';
+ for (my $i = 0; $i < length $_; $i++) {
+ $dashes .= '-';
+ }
+ push @d, $dashes;
+}
+# Re-adjust the lengths for timestamps.
+for (my $i = 8; $i < 12; $i++) {
+ $l[$i] = 29;
+}
+# Re-adjust the dashes for the Schema.Relation to the longest one.
+$d[0] = '';
+for (my $i = 0; $i < $length; $i++) {
+ $d[0] .= '-';
+}
+my $header_format = "%-" . $length . "s %" . $l[1] . "s %" . $l[2] . "s %" .
+ $l[3] . "s %" . $l[4] . "s %" . $l[5] . "s %" . $l[6] . "s %" .
+ $l[7] . "s %" . $l[8] . "s %" . $l[9] . "s %" . $l[10] . "s %" .
+ $l[11] . "s\n";
+my $format = "%-" . $length . "s %" . $l[1] . "d %" . $l[2] . "d %" .
+ $l[3] . "d %" . $l[4] . "d %" . $l[5] . "d %" . $l[6] . "d %" .
+ $l[7] . "d %" . $l[8] . "s %" . $l[9] . "s %" . $l[10] . "s %" .
+ $l[11] . "s\n";
+
+# Now start displaying data.
+printf $header_format, @d;
+printf $header_format, @header;
+printf $header_format, @d;
+for (my $i = 0; $i < scalar @c; $i++) {
+ printf $format, @{$c[$i]};
+}
+print"\n";
+
+# Display index stats.
+
+# FIXME: This simple logic breakds if indexes are added or dropped between
+# snapshots.
+
+$temp = "
+SELECT schemaname, relname, indexrelname, idx_scan, idx_tup_read, idx_tup_fetch
+FROM ps_indexstat
+WHERE snap = %d
+ORDER BY schemaname, relname, indexrelname
+";
+
+$sql = sprintf($temp, $snap1);
+@a1 = split /\n/, `$psql "$sql"`;
+
+$sql = sprintf($temp, $snap2);
+@a2 = split /\n/, `$psql "$sql"`;
+
+print "================\n";
+print "Index Statistics\n";
+print "================\n";
+
+# Run through all the names once just to see what the longest one is to
+# determine how to format the output.
+@c = ();
+for (my $i = 0; $i < scalar @a1; $i++) {
+ my @b1 = split /\|/, $a1[$i];
+ my @b2 = split /\|/, $a2[$i];
+
+ my $name = "$b1[0].$b1[1].$b1[2]";
+ $length = length $name if (length $name > $length);
+ push @c, ([$name, $b2[3] - $b1[3], $b2[4] - $b1[4], $b2[5] - $b1[5]]);
+}
+@header = ("Schema.Relation.Index", "Idx Scan", "Idx Tup Read",
+ "Idx Tup Fetch");
+@l = ();
+@d = ();
+foreach (@header) {
+ push @l, length $_;
+ my $dashes = '';
+ for (my $i = 0; $i < length $_; $i++) {
+ $dashes .= '-';
+ }
+ push @d, $dashes;
+}
+# Re-adjust the dashes for the Schema.Relation.Index to the longest one.
+$d[0] = '';
+for (my $i = 0; $i < $length; $i++) {
+ $d[0] .= '-';
+}
+$header_format = "%-" . $length . "s %" . $l[1] . "s %" . $l[2] . "s %" .
+ $l[3] . "s\n";
+$format = "%-" . $length . "s %" . $l[1] . "d %" . $l[2] . "d %" . $l[3] .
+ "d\n";
+
+# Now start displaying data.
+printf $header_format, @d;
+printf $header_format, @header;
+printf $header_format, @d;
+for (my $i = 0; $i < scalar @c; $i++) {
+ printf $format, @{$c[$i]};
+}
--- /dev/null
+BEGIN;
+SELECT ps_snap_stats();
+COMMIT;