pax_global_header00006660000000000000000000000064133220645570014521gustar00rootroot0000000000000052 comment=e010c273d61ccccc99652aa20985aef997fa3314 pgstats-REL1_0_1/000077500000000000000000000000001332206455700137125ustar00rootroot00000000000000pgstats-REL1_0_1/.gitignore000066400000000000000000000000271332206455700157010ustar00rootroot00000000000000/pgstat /pgcsvstat *.o pgstats-REL1_0_1/BUGS000066400000000000000000000000001332206455700143630ustar00rootroot00000000000000pgstats-REL1_0_1/License000066400000000000000000000020601332206455700152150ustar00rootroot00000000000000This software, pgcsvstat, pgstat, and pgdisplay, is released under the terms of the PostgreSQL License. Copyright (c) 2011-2018, Guillaume Lelarge Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL Guillaume Lelarge BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF Guillaume Lelarge HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Guillaume Lelarge SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND Guillaume Lelarge HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. pgstats-REL1_0_1/Makefile000066400000000000000000000007211332206455700153520ustar00rootroot00000000000000PGFILEDESC = "Statistics utilities" PGAPPICON = win32 PROGRAMS = pgcsvstat pgstat pgdisplay PG_CPPFLAGS = -I$(libpq_srcdir) PG_LIBS = $(libpq_pgport) PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) all: $(PROGRAMS) %: %.o $(WIN32RES) $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) -o $@$(X) pgcsvstat: pgcsvstat.o pgdisplay: pgdisplay.o pgstat: pgstat.o clean: rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS)) pgstats-REL1_0_1/pgcsvstat.c000066400000000000000000000542531332206455700161050ustar00rootroot00000000000000/* * pgcsvstat, a PostgreSQL app to gather statistical informations * from a PostgreSQL database. * * This software is released under the PostgreSQL Licence. * * Guillaume Lelarge, guillaume@lelarge.info, 2011-2018. * * pgstats/pgcsvstat.c */ /* * Headers */ #include "postgres_fe.h" #include #include #ifdef HAVE_GETOPT_H #include #endif extern char *optarg; #include "libpq-fe.h" /* * Defines */ #define PGCSVSTAT_VERSION "1.0.1" /* these are the opts structures for command line params */ struct options { bool quiet; bool nodb; char *directory; char *dbname; char *hostname; char *port; char *username; int major; int minor; }; /* global variables */ struct options *opts; PGconn *conn; /* function prototypes */ static void help(const char *progname); void get_opts(int, char **); void *myalloc(size_t size); char *mystrdup(const char *str); PGconn *sql_conn(void); int sql_exec(const char *sql, const char *filename, bool quiet); void sql_exec_dump_pgstatactivity(void); void sql_exec_dump_pgstatarchiver(void); void sql_exec_dump_pgstatbgwriter(void); void sql_exec_dump_pgstatdatabase(void); void sql_exec_dump_pgstatdatabaseconflicts(void); void sql_exec_dump_pgstatreplication(void); void sql_exec_dump_pgstatalltables(void); void sql_exec_dump_pgstatallindexes(void); void sql_exec_dump_pgstatioalltables(void); void sql_exec_dump_pgstatioallindexes(void); void sql_exec_dump_pgstatioallsequences(void); void sql_exec_dump_pgstatuserfunctions(void); void sql_exec_dump_pgclass_size(void); void sql_exec_dump_pgstatstatements(void); void sql_exec_dump_xlog_stat(void); void fetch_version(void); bool check_superuser(void); bool backend_minimum_version(int major, int minor); bool backend_has_pgstatstatements(void); /* function to parse command line options and check for some usage errors. */ void get_opts(int argc, char **argv) { int c; const char *progname; progname = get_progname(argv[0]); /* set the defaults */ opts->quiet = false; opts->nodb = false; opts->directory = NULL; opts->dbname = NULL; opts->hostname = NULL; opts->port = NULL; opts->username = NULL; if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { help(progname); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { puts("pgcsvstats " PGCSVSTAT_VERSION " (compiled with PostgreSQL " PG_VERSION ")"); exit(0); } } /* get opts */ while ((c = getopt(argc, argv, "h:p:U:d:D:q")) != -1) { switch (c) { /* specify the database */ case 'd': opts->dbname = mystrdup(optarg); break; /* specify the directory */ case 'D': opts->directory = mystrdup(optarg); break; /* don't show headers */ case 'q': opts->quiet = true; break; /* host to connect to */ case 'h': opts->hostname = mystrdup(optarg); break; /* port to connect to on remote host */ case 'p': opts->port = mystrdup(optarg); break; /* username */ case 'U': opts->username = mystrdup(optarg); break; default: fprintf(stderr, "Try \"%s --help\" for more information.\n", progname); exit(1); } } } static void help(const char *progname) { printf("%s gathers statistics from a PostgreSQL database.\n\n" "Usage:\n" " %s [OPTIONS]...\n" "\nGeneral options:\n" " -d DBNAME database to connect to\n" " -D DIRECTORY directory for stats files (defaults to current)\n" " -q quiet\n" " --help show this help, then exit\n" " --version output version information, then exit\n" "\nConnection options:\n" " -h HOSTNAME database server host or socket directory\n" " -p PORT database server port number\n" " -U USER connect as specified database user\n" "\nThe default action is to create CSV files for each report.\n\n" "Report bugs to .\n", progname, progname); } void * myalloc(size_t size) { void *ptr = malloc(size); if (!ptr) { fprintf(stderr, "out of memory"); exit(1); } return ptr; } char * mystrdup(const char *str) { char *result = strdup(str); if (!result) { fprintf(stderr, "out of memory"); exit(1); } return result; } /* establish connection with database. */ PGconn * sql_conn() { PGconn *my_conn; char *password = NULL; bool new_pass; /* * Start the connection. Loop until we have a password if requested by * backend. */ do { new_pass = false; my_conn = PQsetdbLogin(opts->hostname, opts->port, NULL, /* options */ NULL, /* tty */ opts->dbname, opts->username, password); if (!my_conn) { fprintf(stderr, "%s: could not connect to database %s\n", "pgstats", opts->dbname); exit(1); } #if PG_VERSION_NUM >= 80200 if (PQstatus(my_conn) == CONNECTION_BAD && PQconnectionNeedsPassword(my_conn) && password == NULL) { PQfinish(my_conn); #if PG_VERSION_NUM < 100000 password = simple_prompt("Password: ", 100, false); #else simple_prompt("Password: ", password, 100, false); #endif new_pass = true; } #endif } while (new_pass); if (password) free(password); /* check to see that the backend connection was successfully made */ if (PQstatus(my_conn) == CONNECTION_BAD) { fprintf(stderr, "%s: could not connect to database %s: %s", "pgstats", opts->dbname, PQerrorMessage(my_conn)); PQfinish(my_conn); exit(1); } /* return the conn if good */ return my_conn; } /* * Actual code to make call to the database and print the output data. */ int sql_exec(const char *todo, const char* filename, bool quiet) { PGresult *res; FILE *fdcsv; struct stat st; int nfields; int nrows; int i, j; int size; /* open the csv file */ fdcsv = fopen(filename, "a"); if (!fdcsv) { fprintf(stderr, "pgstats: fopen failed: %d\n", errno); fprintf(stderr, "pgstats: filename was: %s\n", filename); PQfinish(conn); exit(-1); } /* get size of file */ stat(filename, &st); size = st.st_size; /* make the call */ res = PQexec(conn, todo); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { fprintf(stderr, "pgstats: query failed: %s\n", PQerrorMessage(conn)); fprintf(stderr, "pgstats: query was: %s\n", todo); PQclear(res); PQfinish(conn); exit(-1); } /* get the number of fields */ nrows = PQntuples(res); nfields = PQnfields(res); /* print a header */ if (!quiet && size == 0) { for (j = 0; j < nfields; j++) { fprintf(fdcsv, "%s", PQfname(res, j)); if (j < nfields - 1) fprintf(fdcsv, ";"); } fprintf(fdcsv, "\n"); } /* for each row, dump the information */ for (i = 0; i < nrows; i++) { for (j = 0; j < nfields; j++) { fprintf(fdcsv, "%s", PQgetvalue(res, i, j)); if (j < nfields - 1) fprintf(fdcsv, ";"); } fprintf(fdcsv, "\n"); } /* cleanup */ PQclear(res); /* close the csv file */ fclose(fdcsv); return 0; } /* * Dump all activities. */ void sql_exec_dump_pgstatactivity() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), datid, datname, %s, " "usesysid, usename, %s%s%s%s%s" "date_trunc('seconds', query_start) AS query_start, " "%s%s%s%s%s " "FROM pg_stat_activity " "ORDER BY %s", backend_minimum_version(9, 2) ? "pid" : "procpid", backend_minimum_version(9, 0) ? "application_name, " : "", backend_minimum_version(8, 1) ? "client_addr, " : "", backend_minimum_version(9, 1) ? "client_hostname, " : "", backend_minimum_version(8, 1) ? "client_port, date_trunc('seconds', backend_start) AS backend_start, " : "", backend_minimum_version(8, 3) ? "date_trunc('seconds', xact_start) AS xact_start, " : "", backend_minimum_version(9, 2) ? "state_change, " : "", backend_minimum_version(8, 2) ? "waiting, " : "", backend_minimum_version(9, 4) ? "backend_xid, " : "", backend_minimum_version(9, 4) ? "backend_xmin, " : "", backend_minimum_version(9, 2) ? "query" : "current_query", backend_minimum_version(9, 2) ? "pid" : "procpid"); snprintf(filename, sizeof(filename), "%s/pg_stat_activity.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all bgwriter stats. */ void sql_exec_dump_pgstatbgwriter() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), checkpoints_timed, " "checkpoints_req, %sbuffers_checkpoint, buffers_clean, " "maxwritten_clean, buffers_backend, %sbuffers_alloc%s " "FROM pg_stat_bgwriter ", backend_minimum_version(9, 2) ? "checkpoint_write_time, checkpoint_sync_time, " : "", backend_minimum_version(9, 1) ? "buffers_backend_fsync, " : "", backend_minimum_version(9, 1) ? ", date_trunc('seconds', stats_reset) AS stats_reset " : ""); snprintf(filename, sizeof(filename), "%s/pg_stat_bgwriter.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all archiver stats. */ void sql_exec_dump_pgstatarchiver() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), archived_count, " "last_archived_wal, date_trunc('seconds', last_archived_time) AS last_archived_time, " "failed_count, " "last_failed_wal, date_trunc('seconds', last_failed_time) AS last_failed_time, " "date_trunc('seconds', stats_reset) AS stats_reset " "FROM pg_stat_archiver "); snprintf(filename, sizeof(filename), "%s/pg_stat_archiver.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all databases stats. */ void sql_exec_dump_pgstatdatabase() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), datid, datname, " "numbackends, xact_commit, xact_rollback, blks_read, blks_hit" "%s%s%s " "FROM pg_stat_database " "ORDER BY datname", backend_minimum_version(8, 3) ? ", tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted" : "", backend_minimum_version(9, 1) ? ", conflicts, date_trunc('seconds', stats_reset) AS stats_reset" : "", backend_minimum_version(9, 2) ? ", temp_files, temp_bytes, deadlocks, blk_read_time, blk_write_time" : ""); snprintf(filename, sizeof(filename), "%s/pg_stat_database.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all databases conflicts stats. */ void sql_exec_dump_pgstatdatabaseconflicts() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), * " "FROM pg_stat_database_conflicts " "ORDER BY datname"); snprintf(filename, sizeof(filename), "%s/pg_stat_database_conflicts.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all replication stats. */ void sql_exec_dump_pgstatreplication() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), %s, usesysid, usename, " "application_name, client_addr, client_hostname, client_port, " "date_trunc('seconds', backend_start) AS backend_start, %sstate, " "pg_current_xlog_location() AS master_location, " "sent_location, write_location, flush_location, replay_location, " "sync_priority, " "sync_state " "FROM pg_stat_replication " "ORDER BY application_name", backend_minimum_version(9, 2) ? "pid" : "procpid", backend_minimum_version(9, 4) ? "backend_xmin, " : ""); snprintf(filename, sizeof(filename), "%s/pg_stat_replication.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all tables stats. */ void sql_exec_dump_pgstatalltables() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), relid, schemaname, relname, " "seq_scan, seq_tup_read, idx_scan, idx_tup_fetch, n_tup_ins, " "n_tup_upd, n_tup_del" "%s" "%s" "%s" "%s" " FROM pg_stat_all_tables " "WHERE schemaname <> 'information_schema' " "ORDER BY schemaname, relname", backend_minimum_version(8, 3) ? ", n_tup_hot_upd, n_live_tup, n_dead_tup" : "", backend_minimum_version(9, 4) ? ", n_mod_since_analyze" : "", backend_minimum_version(8, 2) ? ", date_trunc('seconds', last_vacuum) AS last_vacuum, date_trunc('seconds', last_autovacuum) AS last_autovacuum, date_trunc('seconds',last_analyze) AS last_analyze, date_trunc('seconds',last_autoanalyze) AS last_autoanalyze" : "", backend_minimum_version(9, 1) ? ", vacuum_count, autovacuum_count, analyze_count, autoanalyze_count" : ""); snprintf(filename, sizeof(filename), "%s/pg_stat_all_tables.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all indexes stats. */ void sql_exec_dump_pgstatallindexes() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), * " "FROM pg_stat_all_indexes " "WHERE schemaname <> 'information_schema' " "ORDER BY schemaname, relname"); snprintf(filename, sizeof(filename), "%s/pg_stat_all_indexes.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all tables IO stats. */ void sql_exec_dump_pgstatioalltables() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), * " "FROM pg_statio_all_tables " "WHERE schemaname <> 'information_schema' " "ORDER BY schemaname, relname"); snprintf(filename, sizeof(filename), "%s/pg_statio_all_tables.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all indexes IO stats. */ void sql_exec_dump_pgstatioallindexes() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), * " "FROM pg_statio_all_indexes " "WHERE schemaname <> 'information_schema' " "ORDER BY schemaname, relname"); snprintf(filename, sizeof(filename), "%s/pg_statio_all_indexes.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all sequences IO stats. */ void sql_exec_dump_pgstatioallsequences() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), * " "FROM pg_statio_all_sequences " "WHERE schemaname <> 'information_schema' " "ORDER BY schemaname, relname"); snprintf(filename, sizeof(filename), "%s/pg_statio_all_sequences.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all functions stats. */ void sql_exec_dump_pgstatuserfunctions() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), * " "FROM pg_stat_user_functions " "WHERE schemaname <> 'information_schema' " "ORDER BY schemaname, funcname"); snprintf(filename, sizeof(filename), "%s/pg_stat_user_functions.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all size class stats. */ void sql_exec_dump_pgclass_size() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), n.nspname, c.relname, c.relkind, c.reltuples, c.relpages%s " "FROM pg_class c, pg_namespace n " "WHERE n.oid=c.relnamespace AND n.nspname <> 'information_schema' " "ORDER BY n.nspname, c.relname", backend_minimum_version(8, 1) ? ", pg_relation_size(c.oid)" : ""); snprintf(filename, sizeof(filename), "%s/pg_class_size.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all statements stats. */ void sql_exec_dump_pgstatstatements() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), r.rolname, d.datname, " "regexp_replace(query, E'\n', ' ', 'g') as query, calls, total_time, rows, " "shared_blks_hit, shared_blks_read, shared_blks_written, " "local_blks_hit, local_blks_read, local_blks_written, " "temp_blks_read, temp_blks_written " "FROM pg_stat_statements q, pg_database d, pg_roles r " "WHERE q.userid=r.oid and q.dbid=d.oid " "ORDER BY r.rolname, d.datname"); snprintf(filename, sizeof(filename), "%s/pg_stat_statements.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Dump all xlog stats. */ void sql_exec_dump_xlog_stat() { char todo[1024]; char filename[1024]; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT date_trunc('seconds', now()), pg_xlogfile_name(pg_current_xlog_location())=pg_ls_dir AS current, pg_ls_dir AS filename, " "(SELECT modification FROM pg_stat_file('pg_xlog/'||pg_ls_dir)) AS modification_timestamp " "FROM pg_ls_dir('pg_xlog') " "WHERE pg_ls_dir ~ E'^[0-9A-F]{24}' " "ORDER BY pg_ls_dir"); snprintf(filename, sizeof(filename), "%s/pg_xlog_stat.csv", opts->directory); sql_exec(todo, filename, opts->quiet); } /* * Fetch PostgreSQL major and minor numbers */ void fetch_version() { char todo[1024]; PGresult *res; /* get the oid and database name from the system pg_database table */ snprintf(todo, sizeof(todo), "SELECT version()"); /* make the call */ res = PQexec(conn, todo); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { fprintf(stderr, "pgstats: query failed: %s\n", PQerrorMessage(conn)); fprintf(stderr, "pgstats: query was: %s\n", todo); PQclear(res); PQfinish(conn); exit(-1); } /* get the only row, and parse it to get major and minor numbers */ sscanf(PQgetvalue(res, 0, 0), "%*s %d.%d", &(opts->major), &(opts->minor)); /* print version */ if (!opts->quiet) printf("Detected release: %d.%d\n", opts->major, opts->minor); /* cleanup */ PQclear(res); } /* * Check if user has the superuser attribute */ bool check_superuser() { PGresult *res; char sql[1024]; bool is_superuser = false; /* get the oid and database name from the system pg_database table */ snprintf(sql, sizeof(sql), "SELECT rolsuper FROM pg_roles WHERE rolname=current_user "); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { fprintf(stderr, "pgstats: query failed: %s\n", PQerrorMessage(conn)); fprintf(stderr, "pgstats: query was: %s\n", sql); PQclear(res); PQfinish(conn); exit(-1); } /* get the information */ is_superuser = strncmp(PQgetvalue(res, 0, 0), "t", 1) == 0; /* cleanup */ PQclear(res); return is_superuser; } /* * Compare given major and minor numbers to the one of the connected server */ bool backend_minimum_version(int major, int minor) { return opts->major > major || (opts->major == major && opts->minor >= minor); } /* * Check if backend has the pg_stat_statements view */ bool backend_has_pgstatstatements() { PGresult *res; char sql[1024]; bool has_pgstatstatements = false; /* get the oid and database name from the system pg_database table */ snprintf(sql, sizeof(sql), "SELECT 1 " "FROM pg_proc p, pg_namespace n " "WHERE p.proname='pg_stat_statements' " " AND p.pronamespace=n.oid"); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { fprintf(stderr, "pgstats: query failed: %s\n", PQerrorMessage(conn)); fprintf(stderr, "pgstats: query was: %s\n", sql); PQclear(res); PQfinish(conn); exit(-1); } /* get the information */ has_pgstatstatements = PQntuples(res)>0; /* cleanup */ PQclear(res); return has_pgstatstatements; } int main(int argc, char **argv) { bool is_superuser = false; opts = (struct options *) myalloc(sizeof(struct options)); /* parse the opts */ get_opts(argc, argv); if (opts->dbname == NULL) { opts->dbname = "postgres"; opts->nodb = true; } if (opts->directory == NULL) { opts->directory = "./"; } /* connect to the database */ conn = sql_conn(); /* get version */ fetch_version(); /* check superuser attribute */ is_superuser = check_superuser(); /* grabe cluster stats info */ sql_exec_dump_pgstatactivity(); if (backend_minimum_version(8, 3)) sql_exec_dump_pgstatbgwriter(); sql_exec_dump_pgstatdatabase(); if (backend_minimum_version(9, 1)) { sql_exec_dump_pgstatdatabaseconflicts(); sql_exec_dump_pgstatreplication(); } if (backend_minimum_version(9, 4)) sql_exec_dump_pgstatarchiver(); /* grabe database stats info */ sql_exec_dump_pgstatalltables(); sql_exec_dump_pgstatallindexes(); sql_exec_dump_pgstatioalltables(); sql_exec_dump_pgstatioallindexes(); sql_exec_dump_pgstatioallsequences(); if (backend_minimum_version(8, 4)) sql_exec_dump_pgstatuserfunctions(); /* grabe other informations */ sql_exec_dump_pgclass_size(); if (backend_has_pgstatstatements()) sql_exec_dump_pgstatstatements(); if (backend_minimum_version(8, 2) && is_superuser) sql_exec_dump_xlog_stat(); PQfinish(conn); return 0; } pgstats-REL1_0_1/pgdisplay.c000066400000000000000000000301621332206455700160540ustar00rootroot00000000000000/* * pgdisplay, a PostgreSQL app to display a table * in an informative way. * * This software is released under the PostgreSQL Licence. * * Guillaume Lelarge, guillaume@lelarge.info, 2015-2018. * * pgstat/pgdisplay.c */ /* * Headers */ #include "postgres_fe.h" #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include "libpq-fe.h" #include "libpq/pqsignal.h" /* * Defines */ #define PGDISPLAY_VERSION "0.0.1" #define PGSTAT_DEFAULT_STRING_SIZE 1024 #define couleur(param) printf("\033[48;2;255;%d;%dm",param,param) #define nocouleur() printf("\033[0m") /* these are the options structure for command line parameters */ struct options { /* misc */ bool verbose; char *table; int groups; int blocksize; /* connection parameters */ char *dbname; char *hostname; char *port; char *username; /* version number */ int major; int minor; }; /* * Global variables */ PGconn *conn; struct options *opts; extern char *optarg; /* * Function prototypes */ static void help(const char *progname); void get_opts(int, char **); #ifndef FE_MEMUTILS_H void *pg_malloc(size_t size); char *pg_strdup(const char *in); #endif PGconn *sql_conn(void); void display_fsm(char *table); void fetch_version(void); void fetch_blocksize(void); bool backend_minimum_version(int major, int minor); void allocate_struct(void); static void quit_properly(SIGNAL_ARGS); /* * Print help message */ static void help(const char *progname) { printf("%s displays table in an informative way.\n\n" "Usage:\n" " %s [OPTIONS] [delay [count]]\n" "\nGeneral options:\n" " -G GROUPS # of groups of blocks\n" " -t TABLE table to display\n" " -v verbose\n" " -?|--help show this help, then exit\n" " -V|--version output version information, then exit\n" "\nConnection options:\n" " -h HOSTNAME database server host or socket directory\n" " -p PORT database server port number\n" " -U USER connect as specified database user\n" " -d DBNAME database to connect to\n" "Report bugs to .\n", progname, progname); } /* * Parse command line options and check for some usage errors */ void get_opts(int argc, char **argv) { int c; const char *progname; progname = get_progname(argv[0]); /* set the defaults */ opts->verbose = false; opts->groups = 20; opts->blocksize = 0; opts->table = NULL; opts->dbname = NULL; opts->hostname = NULL; opts->port = NULL; opts->username = NULL; if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { help(progname); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { puts("pgdisplay " PGDISPLAY_VERSION " (compiled with PostgreSQL " PG_VERSION ")"); exit(0); } } /* get opts */ while ((c = getopt(argc, argv, "h:p:U:d:t:G:v")) != -1) { switch (c) { /* specify the # of groups */ case 'G': opts->groups = atoi(optarg); break; /* specify the table */ case 't': opts->table = pg_strdup(optarg); break; /* don't show headers */ case 'v': opts->verbose = true; break; /* specify the database */ case 'd': opts->dbname = pg_strdup(optarg); break; /* host to connect to */ case 'h': opts->hostname = pg_strdup(optarg); break; /* port to connect to on remote host */ case 'p': opts->port = pg_strdup(optarg); break; /* username */ case 'U': opts->username = pg_strdup(optarg); break; default: errx(1, "Try \"%s --help\" for more information.\n", progname); } } if (opts->table == NULL) { fprintf(stderr, "missing table name\n"); exit(EXIT_FAILURE); } if (opts->dbname == NULL) { /* * We want to use dbname for possible error reports later, * and in case someone has set and is using PGDATABASE * in its environment preserve that name for later usage */ if (!getenv("PGDATABASE")) opts->dbname = "postgres"; else opts->dbname = getenv("PGDATABASE"); } } #ifndef FE_MEMUTILS_H /* * "Safe" wrapper around malloc(). */ void * pg_malloc(size_t size) { void *tmp; /* Avoid unportable behavior of malloc(0) */ if (size == 0) size = 1; tmp = malloc(size); if (!tmp) { fprintf(stderr, "out of memory\n"); exit(EXIT_FAILURE); } return tmp; } /* * "Safe" wrapper around strdup(). */ char * pg_strdup(const char *in) { char *tmp; if (!in) { fprintf(stderr, "cannot duplicate null pointer (internal error)\n"); exit(EXIT_FAILURE); } tmp = strdup(in); if (!tmp) { fprintf(stderr, "out of memory\n"); exit(EXIT_FAILURE); } return tmp; } #endif /* * Establish the PostgreSQL connection */ PGconn * sql_conn() { PGconn *my_conn; char *password = NULL; bool new_pass; #if PG_VERSION_NUM >= 90300 const char **keywords; const char **values; #else int size; char *dns; #endif /* * Start the connection. Loop until we have a password if requested by * backend. */ do { #if PG_VERSION_NUM >= 90300 /* * We don't need to check if the database name is actually a complete * connection string, PQconnectdbParams being smart enough to check * this itself. */ #define PARAMS_ARRAY_SIZE 8 keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); keywords[0] = "host"; values[0] = opts->hostname, keywords[1] = "port"; values[1] = opts->port; keywords[2] = "user"; values[2] = opts->username; keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; values[4] = opts->dbname; keywords[5] = "fallback_application_name"; values[5] = "pgstat"; keywords[7] = NULL; values[7] = NULL; my_conn = PQconnectdbParams(keywords, values, true); #else /* 34 is the length of the fallback application name setting */ size = 34; if (opts->hostname) size += strlen(opts->hostname) + 6; if (opts->port) size += strlen(opts->port) + 6; if (opts->username) size += strlen(opts->username) + 6; if (opts->dbname) size += strlen(opts->dbname) + 8; dns = pg_malloc(size); /* * Checking the presence of a = sign is our way to check that the * database name is actually a connection string. In such a case, we * keep this string as the connection string, and add other parameters * if they are supplied. */ sprintf(dns, "%s", "fallback_application_name='pgstat' "); if (strchr(opts->dbname, '=') != NULL) sprintf(dns, "%s%s", dns, opts->dbname); else if (opts->dbname) sprintf(dns, "%sdbname=%s ", dns, opts->dbname); if (opts->hostname) sprintf(dns, "%shost=%s ", dns, opts->hostname); if (opts->port) sprintf(dns, "%sport=%s ", dns, opts->port); if (opts->username) sprintf(dns, "%suser=%s ", dns, opts->username); if (opts->verbose) printf("Connection string: %s\n", dns); my_conn = PQconnectdb(dns); #endif new_pass = false; if (!my_conn) { errx(1, "could not connect to database %s\n", opts->dbname); } #if PG_VERSION_NUM >= 80200 if (PQstatus(my_conn) == CONNECTION_BAD && PQconnectionNeedsPassword(my_conn) && password == NULL) { PQfinish(my_conn); #if PG_VERSION_NUM < 100000 password = simple_prompt("Password: ", 100, false); #else simple_prompt("Password: ", password, 100, false); #endif new_pass = true; } #endif } while (new_pass); if (password) free(password); /* check to see that the backend connection was successfully made */ if (PQstatus(my_conn) == CONNECTION_BAD) { errx(1, "could not connect to database %s: %s", opts->dbname, PQerrorMessage(my_conn)); PQfinish(my_conn); } /* return the conn if good */ return my_conn; } /* * Dump all archiver stats. */ void display_fsm(char *table) { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row; int color; int totalspace, freespace; int groupby, blocksize; int n; blocksize = 8192; /* grab the stats (this is the only stats on one line) */ /* snprintf(sql, sizeof(sql), "with fsm as (select blkno/443 as blockrange, sum(avail) as available, 8192*443 as total from pg_freespace('%s') group by 1)" "select blockrange, available, total, 100*available/total as ratio, 180*available/total as color from fsm order by 1", table); */ snprintf(sql, sizeof(sql), "select avail from pg_freespace('%s') order by blkno", table); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* initialize some vars */ totalspace = nrows*blocksize; if (nrows <= opts->groups) groupby = 1; else groupby = nrows/opts->groups; freespace = 0; n = 0; printf("Nombre de blocs: %d\n", nrows); printf("Taille de la table: %d\n", totalspace); printf("... groupe de %d\n", groupby); printf("\n\n"); /* for each row, dump the information */ for (row = 0; row < nrows; row++) { /* getting new values */ freespace += atol(PQgetvalue(res, row, 0)); if (++n >= groupby) { //printf("Espace libre[%d] : %d (sur %d)\n", n, freespace, groupby*blocksize); /* printing the diff... * note that the first line will be the current value, rather than the diff */ color = 180*freespace/(8192*groupby); if (color<0) color = 0; couleur(color); printf(" "); nocouleur(); freespace = 0; n = 0; } } printf("\n\n"); /* cleanup */ PQclear(res); } /* * Fetch block size. */ void fetch_blocksize() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; /* get the cluster version */ snprintf(sql, sizeof(sql), "SELECT current_setting('block_size')"); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the only row, and parse it to get major and minor numbers */ opts->blocksize = atoi(PQgetvalue(res, 0, 0)); /* print version */ if (opts->verbose) printf("Detected block size: %d\n", opts->blocksize); /* cleanup */ PQclear(res); } /* * Fetch PostgreSQL major and minor numbers */ void fetch_version() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; /* get the cluster version */ snprintf(sql, sizeof(sql), "SELECT version()"); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the only row, and parse it to get major and minor numbers */ sscanf(PQgetvalue(res, 0, 0), "%*s %d.%d", &(opts->major), &(opts->minor)); /* print version */ if (opts->verbose) printf("Detected release: %d.%d\n", opts->major, opts->minor); /* cleanup */ PQclear(res); } /* * Compare given major and minor numbers to the one of the connected server */ bool backend_minimum_version(int major, int minor) { return opts->major > major || (opts->major == major && opts->minor >= minor); } /* * Close the PostgreSQL connection, and quit */ static void quit_properly(SIGNAL_ARGS) { PQfinish(conn); exit(1); } /* * Main function */ int main(int argc, char **argv) { /* * If the user stops the program (control-Z) and then resumes it, * print out the header again. */ pqsignal(SIGINT, quit_properly); /* Allocate the options struct */ opts = (struct options *) pg_malloc(sizeof(struct options)); /* Parse the options */ get_opts(argc, argv); /* Connect to the database */ conn = sql_conn(); // check last vacuum timestamp // fetch blocks count fetch_blocksize(); display_fsm(opts->table); PQfinish(conn); return 0; } pgstats-REL1_0_1/pgstat.c000066400000000000000000002130071332206455700153630ustar00rootroot00000000000000/* * pgstat, a PostgreSQL app to gather statistical informations * from a PostgreSQL database, and act like a vmstat tool. * * This software is released under the PostgreSQL Licence. * * Guillaume Lelarge, guillaume@lelarge.info, 2014-2018. * * pgstat/pgstat.c */ /* * Headers */ #include "postgres_fe.h" #include #include #include #include #include #ifdef HAVE_GETOPT_H #include #endif #include "libpq-fe.h" #include "libpq/pqsignal.h" /* * Defines */ #define PGSTAT_VERSION "1.0.1" #define PGSTAT_DEFAULT_LINES 20 #define PGSTAT_DEFAULT_STRING_SIZE 1024 /* * Structs and enums */ typedef enum { NONE = 0, ARCHIVER, BGWRITER, CONNECTION, DATABASE, TABLE, TABLEIO, INDEX, FUNCTION, STATEMENT, XLOG, TEMPFILE, PBPOOLS, PBSTATS } stat_t; /* these are the options structure for command line parameters */ struct options { /* misc */ bool verbose; bool dontredisplayheader; stat_t stat; char *filter; bool human_readable; /* connection parameters */ char *dbname; char *hostname; char *port; char *username; /* version number */ int major; int minor; /* pg_stat_statements namespace */ char *namespace; /* frequency */ int interval; int count; }; /* pg_stat_archiver struct */ struct pgstatarchiver { long archived_count; /* we don't put these columns here because it makes no sense to get a diff between the new and the old values ? last_archived_wal; ? last_archived_time; */ long failed_count; /* we don't put these columns here because it makes no sense to get a diff between the new and the old values ? last_failed_wal; ? last_failed_time; ? stats_reset; */ }; /* pg_stat_bgwriter struct */ struct pgstatbgwriter { long checkpoints_timed; long checkpoints_req; long checkpoint_write_time; long checkpoint_sync_time; long buffers_checkpoint; long buffers_clean; long maxwritten_clean; long buffers_backend; long buffers_backend_fsync; long buffers_alloc; }; /* pg_stat_database struct */ struct pgstatdatabase { /* we don't put numbackends here because it makes no sense to get a diff between the new and the old values long numbackends; */ long xact_commit; long xact_rollback; long blks_read; long blks_hit; long tup_returned; long tup_fetched; long tup_inserted; long tup_updated; long tup_deleted; long conflicts; long temp_files; long temp_bytes; long deadlocks; long blk_read_time; long blk_write_time; }; /* pg_stat_all_tables struct */ struct pgstattable { long seq_scan; long seq_tup_read; long idx_scan; long idx_tup_fetch; long n_tup_ins; long n_tup_upd; long n_tup_del; long n_tup_hot_upd; long n_live_tup; long n_dead_tup; long n_mod_since_analyze; /* we don't put the timestamps here because it makes no sense to get a diff between the new and the old values ? last_vacuum; ? last_autovacuum; ? last_analyze; ? last_autoanalyze; */ long vacuum_count; long autovacuum_count; long analyze_count; long autoanalyze_count; }; /* pg_statio_all_tables struct */ struct pgstattableio { long heap_blks_read; long heap_blks_hit; long idx_blks_read; long idx_blks_hit; long toast_blks_read; long toast_blks_hit; long tidx_blks_read; long tidx_blks_hit; }; /* pg_stat_all_indexes struct */ struct pgstatindex { long idx_scan; long idx_tup_read; long idx_tup_fetch; }; /* pg_stat_user_functions struct */ struct pgstatfunction { long calls; float total_time; float self_time; }; /* pg_stat_statements struct */ struct pgstatstatement { /* long userid; long dbid; long queryid; text query; */ long calls; float total_time; long rows; long shared_blks_hit; long shared_blks_read; long shared_blks_dirtied; long shared_blks_written; long local_blks_hit; long local_blks_read; long local_blks_dirtied; long local_blks_written; long temp_blks_read; long temp_blks_written; float blk_read_time; float blk_write_time; }; /* xlogstats struct */ struct xlogstats { char *location; long locationdiff; }; /* pgBouncer stats struct */ struct pgbouncerstats { long total_request; long total_received; long total_sent; long total_query_time; /* not used yet float avg_req; float avg_recv; float avg_sent; float avg_query; */ }; /* * Global variables */ PGconn *conn; struct options *opts; extern char *optarg; struct pgstatarchiver *previous_pgstatarchiver; struct pgstatbgwriter *previous_pgstatbgwriter; struct pgstatdatabase *previous_pgstatdatabase; struct pgstattable *previous_pgstattable; struct pgstattableio *previous_pgstattableio; struct pgstatindex *previous_pgstatindex; struct pgstatfunction *previous_pgstatfunction; struct pgstatstatement *previous_pgstatstatement; struct xlogstats *previous_xlogstats; struct pgbouncerstats *previous_pgbouncerstats; int hdrcnt = 0; volatile sig_atomic_t wresized; static int winlines = PGSTAT_DEFAULT_LINES; /* * Function prototypes */ static void help(const char *progname); void get_opts(int, char **); #ifndef FE_MEMUTILS_H void *pg_malloc(size_t size); char *pg_strdup(const char *in); #endif PGconn *sql_conn(void); void print_pgstatarchiver(void); void print_pgstatbgwriter(void); void print_pgstatconnection(void); void print_pgstatdatabase(void); void print_pgstattable(void); void print_pgstattableio(void); void print_pgstatindex(void); void print_pgstatfunction(void); void print_pgstatstatement(void); void print_xlogstats(void); void print_tempfilestats(void); void print_pgbouncerpools(void); void print_pgbouncerstats(void); void fetch_version(void); void fetch_pgstatstatements_namespace(void); bool backend_minimum_version(int major, int minor); void print_header(void); void print_line(void); void allocate_struct(void); static void needhdr(int dummy); static void needresize(int); void doresize(void); static void quit_properly(SIGNAL_ARGS); /* * Print help message */ static void help(const char *progname) { printf("%s gathers statistics from a PostgreSQL database.\n\n" "Usage:\n" " %s [OPTIONS] [delay [count]]\n" "\nGeneral options:\n" " -f FILTER include only this object\n" " -H display human-readable values\n" " -n do not redisplay header\n" " -s STAT stats to collect\n" " -v verbose\n" " -?|--help show this help, then exit\n" " -V|--version output version information, then exit\n" "\nConnection options:\n" " -h HOSTNAME database server host or socket directory\n" " -p PORT database server port number\n" " -U USER connect as specified database user\n" " -d DBNAME database to connect to\n" "\nThe default stat is pg_stat_bgwriter, but you can change it with the -s command line option,\n" "and one of its value (STAT):\n" " * archiver for pg_stat_archiver\n" " * bgwriter for pg_stat_bgwriter\n" " * connection (only for > 9.1)\n" " * database for pg_stat_database\n" " * table for pg_stat_all_tables\n" " * tableio for pg_statio_all_tables\n" " * index for pg_stat_all_indexes\n" " * function for pg_stat_user_function\n" " * statement for pg_stat_statements (needs the extension)\n" " * xlog for xlog writes (only for > 9.2)\n" " * tempfile for temporary file usage\n" " * pbpools for pgBouncer pools statistics\n" " * pbstats for pgBouncer statistics\n\n" "Report bugs to .\n", progname, progname); } /* * Parse command line options and check for some usage errors */ void get_opts(int argc, char **argv) { int c; const char *progname; progname = get_progname(argv[0]); /* set the defaults */ opts->verbose = false; opts->dontredisplayheader = false; opts->stat = NONE; opts->filter = NULL; opts->human_readable = false; opts->dbname = NULL; opts->hostname = NULL; opts->port = NULL; opts->username = NULL; opts->namespace = NULL; opts->interval = 1; opts->count = -1; if (argc > 1) { if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0) { help(progname); exit(0); } if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) { puts("pgstats " PGSTAT_VERSION " (compiled with PostgreSQL " PG_VERSION ")"); exit(0); } } /* get opts */ while ((c = getopt(argc, argv, "h:Hp:U:d:f:ns:v")) != -1) { switch (c) { /* specify the database */ case 'd': opts->dbname = pg_strdup(optarg); break; /* specify the filter */ case 'f': opts->filter = pg_strdup(optarg); break; /* do not redisplay the header */ case 'n': opts->dontredisplayheader = true; break; /* don't show headers */ case 'v': opts->verbose = true; break; /* specify the stat */ case 's': if (opts->stat != NONE) { errx(1, "You can only use once the -s command line switch.\n"); } if (!strcmp(optarg, "archiver")) { opts->stat = ARCHIVER; } else if (!strcmp(optarg, "bgwriter")) { opts->stat = BGWRITER; } else if (!strcmp(optarg, "connection")) { opts->stat = CONNECTION; } else if (!strcmp(optarg, "database")) { opts->stat = DATABASE; } else if (!strcmp(optarg, "table")) { opts->stat = TABLE; } else if (!strcmp(optarg, "tableio")) { opts->stat = TABLEIO; } else if (!strcmp(optarg, "index")) { opts->stat = INDEX; } else if (!strcmp(optarg, "function")) { opts->stat = FUNCTION; } else if (!strcmp(optarg, "statement")) { opts->stat = STATEMENT; } else if (!strcmp(optarg, "xlog")) { opts->stat = XLOG; } else if (!strcmp(optarg, "tempfile")) { opts->stat = TEMPFILE; } else if (!strcmp(optarg, "pbpools")) { opts->stat = PBPOOLS; } else if (!strcmp(optarg, "pbstats")) { opts->stat = PBSTATS; } else { errx(1, "Unknown service \"%s\".\nTry \"%s --help\" for more information.\n", optarg, progname); } break; /* host to connect to */ case 'h': opts->hostname = pg_strdup(optarg); break; /* display human-readable values */ case 'H': opts->human_readable = true; break; /* port to connect to on remote host */ case 'p': opts->port = pg_strdup(optarg); break; /* username */ case 'U': opts->username = pg_strdup(optarg); break; default: errx(1, "Try \"%s --help\" for more information.\n", progname); } } if (optind < argc) { opts->interval = atoi(argv[optind]); if (opts->interval == 0) { errx(1, "Invalid delay.\nTry \"%s --help\" for more information.\n", progname); } optind++; } if (optind < argc) { opts->count = atoi(argv[optind]); if (opts -> count == 0) { errx(1, "Invalid count.\nTry \"%s --help\" for more information.\n", progname); } } if (opts->stat == PBPOOLS || opts->stat == PBSTATS) { /* * Set (or override) database name. * It should always be pgbouncer */ opts->dbname = pg_strdup("pgbouncer"); } if (opts->dbname == NULL) { /* * We want to use dbname for possible error reports later, * and in case someone has set and is using PGDATABASE * in its environment preserve that name for later usage */ if (!getenv("PGDATABASE")) opts->dbname = "postgres"; else opts->dbname = getenv("PGDATABASE"); } } #ifndef FE_MEMUTILS_H /* * "Safe" wrapper around malloc(). */ void * pg_malloc(size_t size) { void *tmp; /* Avoid unportable behavior of malloc(0) */ if (size == 0) size = 1; tmp = malloc(size); if (!tmp) { fprintf(stderr, "out of memory\n"); exit(EXIT_FAILURE); } return tmp; } /* * "Safe" wrapper around strdup(). */ char * pg_strdup(const char *in) { char *tmp; if (!in) { fprintf(stderr, "cannot duplicate null pointer (internal error)\n"); exit(EXIT_FAILURE); } tmp = strdup(in); if (!tmp) { fprintf(stderr, "out of memory\n"); exit(EXIT_FAILURE); } return tmp; } #endif /* * Establish the PostgreSQL connection */ PGconn * sql_conn() { PGconn *my_conn; char *password = NULL; bool new_pass; #if PG_VERSION_NUM >= 90300 const char **keywords; const char **values; #else int size; char *dns; #endif /* * Start the connection. Loop until we have a password if requested by * backend. */ do { #if PG_VERSION_NUM >= 90300 /* * We don't need to check if the database name is actually a complete * connection string, PQconnectdbParams being smart enough to check * this itself. */ #define PARAMS_ARRAY_SIZE 8 keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); keywords[0] = "host"; values[0] = opts->hostname, keywords[1] = "port"; values[1] = opts->port; keywords[2] = "user"; values[2] = opts->username; keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; values[4] = opts->dbname; keywords[5] = "fallback_application_name"; values[5] = "pgstat"; keywords[7] = NULL; values[7] = NULL; my_conn = PQconnectdbParams(keywords, values, true); #else /* 34 is the length of the fallback application name setting */ size = 34; if (opts->hostname) size += strlen(opts->hostname) + 6; if (opts->port) size += strlen(opts->port) + 6; if (opts->username) size += strlen(opts->username) + 6; if (opts->dbname) size += strlen(opts->dbname) + 8; dns = pg_malloc(size); /* * Checking the presence of a = sign is our way to check that the * database name is actually a connection string. In such a case, we * keep this string as the connection string, and add other parameters * if they are supplied. */ sprintf(dns, "%s", "fallback_application_name='pgstat' "); if (strchr(opts->dbname, '=') != NULL) sprintf(dns, "%s%s", dns, opts->dbname); else if (opts->dbname) sprintf(dns, "%sdbname=%s ", dns, opts->dbname); if (opts->hostname) sprintf(dns, "%shost=%s ", dns, opts->hostname); if (opts->port) sprintf(dns, "%sport=%s ", dns, opts->port); if (opts->username) sprintf(dns, "%suser=%s ", dns, opts->username); if (opts->verbose) printf("Connection string: %s\n", dns); my_conn = PQconnectdb(dns); #endif new_pass = false; if (!my_conn) { errx(1, "could not connect to database %s\n", opts->dbname); } #if PG_VERSION_NUM >= 80200 if (PQstatus(my_conn) == CONNECTION_BAD && PQconnectionNeedsPassword(my_conn) && password == NULL) { PQfinish(my_conn); #if PG_VERSION_NUM < 100000 password = simple_prompt("Password: ", 100, false); #else simple_prompt("Password: ", password, 100, false); #endif new_pass = true; } #endif } while (new_pass); if (password) free(password); /* check to see that the backend connection was successfully made */ if (PQstatus(my_conn) == CONNECTION_BAD) { errx(1, "could not connect to database %s: %s", opts->dbname, PQerrorMessage(my_conn)); PQfinish(my_conn); } /* return the conn if good */ return my_conn; } /* * Dump all archiver stats. */ void print_pgstatarchiver() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row, column; long archived_count; long failed_count; /* grab the stats (this is the only stats on one line) */ snprintf(sql, sizeof(sql), "SELECT archived_count, failed_count " "FROM pg_stat_archiver "); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ archived_count = atol(PQgetvalue(res, row, column++)); failed_count = atol(PQgetvalue(res, row, column++)); /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6ld\n", archived_count - previous_pgstatarchiver->archived_count, failed_count - previous_pgstatarchiver->failed_count ); /* setting the new old value */ previous_pgstatarchiver->archived_count = archived_count; previous_pgstatarchiver->failed_count = failed_count; } /* cleanup */ PQclear(res); } /* * Dump all bgwriter stats. */ void print_pgstatbgwriter() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row, column; long checkpoints_timed = 0; long checkpoints_req = 0; long checkpoint_write_time = 0; long checkpoint_sync_time = 0; long buffers_checkpoint = 0; long buffers_clean = 0; long maxwritten_clean = 0; long buffers_backend = 0; long buffers_backend_fsync = 0; long buffers_alloc = 0; /* grab the stats (this is the only stats on one line) */ snprintf(sql, sizeof(sql), "SELECT checkpoints_timed, checkpoints_req, %sbuffers_checkpoint, buffers_clean, " "maxwritten_clean, buffers_backend, %sbuffers_alloc " "FROM pg_stat_bgwriter ", backend_minimum_version(9, 2) ? "checkpoint_write_time, checkpoint_sync_time, " : "", backend_minimum_version(9, 1) ? "buffers_backend_fsync, " : ""); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ checkpoints_timed = atol(PQgetvalue(res, row, column++)); checkpoints_req = atol(PQgetvalue(res, row, column++)); if (backend_minimum_version(9, 2)) { checkpoint_write_time = atol(PQgetvalue(res, row, column++)); checkpoint_sync_time = atol(PQgetvalue(res, row, column++)); } buffers_checkpoint = atol(PQgetvalue(res, row, column++)); buffers_clean = atol(PQgetvalue(res, row, column++)); maxwritten_clean = atol(PQgetvalue(res, row, column++)); buffers_backend = atol(PQgetvalue(res, row, column++)); if (backend_minimum_version(9, 1)) { buffers_backend_fsync = atol(PQgetvalue(res, row, column++)); } buffers_alloc = atol(PQgetvalue(res, row, column++)); /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %4ld %2ld\n", checkpoints_timed - previous_pgstatbgwriter->checkpoints_timed, checkpoints_req - previous_pgstatbgwriter->checkpoints_req, checkpoint_write_time - previous_pgstatbgwriter->checkpoint_write_time, checkpoint_sync_time - previous_pgstatbgwriter->checkpoint_sync_time, buffers_checkpoint - previous_pgstatbgwriter->buffers_checkpoint, buffers_clean - previous_pgstatbgwriter->buffers_clean, buffers_backend - previous_pgstatbgwriter->buffers_backend, buffers_alloc - previous_pgstatbgwriter->buffers_alloc, maxwritten_clean - previous_pgstatbgwriter->maxwritten_clean, buffers_backend_fsync - previous_pgstatbgwriter->buffers_backend_fsync ); /* setting the new old value */ previous_pgstatbgwriter->checkpoints_timed = checkpoints_timed; previous_pgstatbgwriter->checkpoints_req = checkpoints_req; previous_pgstatbgwriter->checkpoint_write_time = checkpoint_write_time; previous_pgstatbgwriter->checkpoint_sync_time = checkpoint_sync_time; previous_pgstatbgwriter->buffers_checkpoint = buffers_checkpoint; previous_pgstatbgwriter->buffers_clean = buffers_clean; previous_pgstatbgwriter->maxwritten_clean = maxwritten_clean; previous_pgstatbgwriter->buffers_backend = buffers_backend; previous_pgstatbgwriter->buffers_backend_fsync = buffers_backend_fsync; previous_pgstatbgwriter->buffers_alloc = buffers_alloc; } /* cleanup */ PQclear(res); } /* * Dump all connection stats. */ void print_pgstatconnection() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row, column; long total = 0; long active = 0; long lockwaiting = 0; long idleintransaction = 0; long idle = 0; if (backend_minimum_version(9, 6)) { snprintf(sql, sizeof(sql), "SELECT count(*) AS total, " " sum(CASE WHEN state='active' and wait_event IS NULL THEN 1 ELSE 0 end) AS active, " " sum(CASE WHEN wait_event IS NOT NULL THEN 1 ELSE 0 end) AS lockwaiting, " " sum(CASE WHEN state='idle in transaction' THEN 1 ELSE 0 end) AS idleintransaction, " " sum(CASE WHEN state='idle' THEN 1 ELSE 0 end) AS idle " "FROM pg_stat_activity"); } else { snprintf(sql, sizeof(sql), "SELECT count(*) AS total, " " sum(CASE WHEN state='active' and not waiting THEN 1 ELSE 0 end) AS active, " " sum(CASE WHEN waiting THEN 1 ELSE 0 end) AS lockwaiting, " " sum(CASE WHEN state='idle in transaction' THEN 1 ELSE 0 end) AS idleintransaction, " " sum(CASE WHEN state='idle' THEN 1 ELSE 0 end) AS idle " "FROM pg_stat_activity"); } res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; total = atol(PQgetvalue(res, row, column++)); active = atol(PQgetvalue(res, row, column++)); lockwaiting = atol(PQgetvalue(res, row, column++)); idleintransaction = atol(PQgetvalue(res, row, column++)); idle = atol(PQgetvalue(res, row, column++)); /* printing the actual values for once */ (void)printf(" %4ld %4ld %4ld %4ld %4ld \n", total, active, lockwaiting, idleintransaction, idle); } /* cleanup */ PQclear(res); } /* * Dump all bgwriter stats. */ void print_pgstatdatabase() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; const char *paramValues[1]; int nrows; int row, column; long numbackends = 0; long xact_commit = 0; long xact_rollback = 0; long blks_read = 0; long blks_hit = 0; long tup_returned = 0; long tup_fetched = 0; long tup_inserted = 0; long tup_updated = 0; long tup_deleted = 0; long conflicts = 0; long temp_files = 0; long temp_bytes = 0; long deadlocks = 0; long blk_read_time = 0; long blk_write_time = 0; /* * With a filter, we assume we'll get only one row. * Without, we sum all the fields to get one row. */ if (opts->filter == NULL) { snprintf(sql, sizeof(sql), "SELECT sum(numbackends), sum(xact_commit), sum(xact_rollback), sum(blks_read), sum(blks_hit)" "%s%s%s " "FROM pg_stat_database ", backend_minimum_version(8, 3) ? ", sum(tup_returned), sum(tup_fetched), sum(tup_inserted), sum(tup_updated), sum(tup_deleted)" : "", backend_minimum_version(9, 1) ? ", sum(conflicts)" : "", backend_minimum_version(9, 2) ? ", sum(temp_files), sum(temp_bytes), sum(deadlocks), sum(blk_read_time), sum(blk_write_time)" : ""); res = PQexec(conn, sql); } else { snprintf(sql, sizeof(sql), "SELECT numbackends, xact_commit, xact_rollback, blks_read, blks_hit" "%s%s%s " "FROM pg_stat_database " "WHERE datname=$1", backend_minimum_version(8, 3) ? ", tup_returned, tup_fetched, tup_inserted, tup_updated, tup_deleted" : "", backend_minimum_version(9, 1) ? ", conflicts" : "", backend_minimum_version(9, 2) ? ", temp_files, temp_bytes, deadlocks, blk_read_time, blk_write_time" : ""); paramValues[0] = pg_strdup(opts->filter); res = PQexecParams(conn, sql, 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 0); /* ask for text results */ } /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ numbackends = atol(PQgetvalue(res, row, column++)); xact_commit = atol(PQgetvalue(res, row, column++)); xact_rollback = atol(PQgetvalue(res, row, column++)); blks_read = atol(PQgetvalue(res, row, column++)); blks_hit = atol(PQgetvalue(res, row, column++)); if (backend_minimum_version(8, 3)) { tup_returned = atol(PQgetvalue(res, row, column++)); tup_fetched = atol(PQgetvalue(res, row, column++)); tup_inserted = atol(PQgetvalue(res, row, column++)); tup_updated = atol(PQgetvalue(res, row, column++)); tup_deleted = atol(PQgetvalue(res, row, column++)); } if (backend_minimum_version(9, 1)) { conflicts = atol(PQgetvalue(res, row, column++)); } if (backend_minimum_version(9, 2)) { temp_files = atol(PQgetvalue(res, row, column++)); temp_bytes = atol(PQgetvalue(res, row, column++)); deadlocks = atol(PQgetvalue(res, row, column++)); blk_read_time = atol(PQgetvalue(res, row, column++)); blk_write_time = atol(PQgetvalue(res, row, column++)); } /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %4ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %9ld %9ld %9ld\n", numbackends, xact_commit - previous_pgstatdatabase->xact_commit, xact_rollback - previous_pgstatdatabase->xact_rollback, blks_read - previous_pgstatdatabase->blks_read, blks_hit - previous_pgstatdatabase->blks_hit, blk_read_time - previous_pgstatdatabase->blk_read_time, blk_write_time - previous_pgstatdatabase->blk_write_time, tup_returned - previous_pgstatdatabase->tup_returned, tup_fetched - previous_pgstatdatabase->tup_fetched, tup_inserted - previous_pgstatdatabase->tup_inserted, tup_updated - previous_pgstatdatabase->tup_updated, tup_deleted - previous_pgstatdatabase->tup_deleted, temp_files - previous_pgstatdatabase->temp_files, temp_bytes - previous_pgstatdatabase->temp_bytes, conflicts - previous_pgstatdatabase->conflicts, deadlocks - previous_pgstatdatabase->deadlocks ); /* setting the new old value */ previous_pgstatdatabase->xact_commit = xact_commit; previous_pgstatdatabase->xact_rollback = xact_rollback; previous_pgstatdatabase->blks_read = blks_read; previous_pgstatdatabase->blks_hit = blks_hit; previous_pgstatdatabase->tup_returned = tup_returned; previous_pgstatdatabase->tup_fetched = tup_fetched; previous_pgstatdatabase->tup_inserted = tup_inserted; previous_pgstatdatabase->tup_updated = tup_updated; previous_pgstatdatabase->tup_deleted = tup_deleted; previous_pgstatdatabase->conflicts = conflicts; previous_pgstatdatabase->temp_files = temp_files; previous_pgstatdatabase->temp_bytes = temp_bytes; previous_pgstatdatabase->deadlocks = deadlocks; previous_pgstatdatabase->blk_read_time = blk_read_time; previous_pgstatdatabase->blk_write_time = blk_write_time; } /* cleanup */ PQclear(res); } /* * Dump all table stats. */ void print_pgstattable() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; const char *paramValues[1]; int nrows; int row, column; long seq_scan = 0; long seq_tup_read = 0; long idx_scan = 0; long idx_tup_fetch = 0; long n_tup_ins = 0; long n_tup_upd = 0; long n_tup_del = 0; long n_tup_hot_upd = 0; long n_live_tup = 0; long n_dead_tup = 0; long n_mod_since_analyze = 0; long vacuum_count = 0; long autovacuum_count = 0; long analyze_count = 0; long autoanalyze_count = 0; /* * With a filter, we assume we'll get only one row. * Without, we sum all the fields to get one row. */ if (opts->filter == NULL) { snprintf(sql, sizeof(sql), "SELECT sum(seq_scan), sum(seq_tup_read), sum(idx_scan), sum(idx_tup_fetch), sum(n_tup_ins), " "sum(n_tup_upd), sum(n_tup_del)" "%s" "%s" "%s" " FROM pg_stat_all_tables " "WHERE schemaname <> 'information_schema' ", backend_minimum_version(8, 3) ? ", sum(n_tup_hot_upd), sum(n_live_tup), sum(n_dead_tup)" : "", backend_minimum_version(9, 4) ? ", sum(n_mod_since_analyze)" : "", backend_minimum_version(9, 1) ? ", sum(vacuum_count), sum(autovacuum_count), sum(analyze_count), sum(autoanalyze_count)" : ""); res = PQexec(conn, sql); } else { snprintf(sql, sizeof(sql), "SELECT sum(seq_scan), sum(seq_tup_read), sum(idx_scan), sum(idx_tup_fetch), sum(n_tup_ins), " "sum(n_tup_upd), sum(n_tup_del)" "%s" "%s" "%s" " FROM pg_stat_all_tables " "WHERE schemaname <> 'information_schema' " " AND relname = $1", backend_minimum_version(8, 3) ? ", sum(n_tup_hot_upd), sum(n_live_tup), sum(n_dead_tup)" : "", backend_minimum_version(9, 4) ? ", sum(n_mod_since_analyze)" : "", backend_minimum_version(9, 1) ? ", sum(vacuum_count), sum(autovacuum_count), sum(analyze_count), sum(autoanalyze_count)" : ""); paramValues[0] = pg_strdup(opts->filter); res = PQexecParams(conn, sql, 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 0); /* ask for text results */ } /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ seq_scan = atol(PQgetvalue(res, row, column++)); seq_tup_read = atol(PQgetvalue(res, row, column++)); idx_scan = atol(PQgetvalue(res, row, column++)); idx_tup_fetch = atol(PQgetvalue(res, row, column++)); n_tup_ins = atol(PQgetvalue(res, row, column++)); n_tup_upd = atol(PQgetvalue(res, row, column++)); n_tup_del = atol(PQgetvalue(res, row, column++)); if (backend_minimum_version(8, 3)) { n_tup_hot_upd = atol(PQgetvalue(res, row, column++)); n_live_tup = atol(PQgetvalue(res, row, column++)); n_dead_tup = atol(PQgetvalue(res, row, column++)); } if (backend_minimum_version(9, 4)) { n_mod_since_analyze = atol(PQgetvalue(res, row, column++)); } if (backend_minimum_version(9, 1)) { vacuum_count = atol(PQgetvalue(res, row, column++)); autovacuum_count = atol(PQgetvalue(res, row, column++)); analyze_count = atol(PQgetvalue(res, row, column++)); autoanalyze_count = atol(PQgetvalue(res, row, column++)); } /* printing the diff... note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld\n", seq_scan - previous_pgstattable->seq_scan, seq_tup_read - previous_pgstattable->seq_tup_read, idx_scan - previous_pgstattable->idx_scan, idx_tup_fetch - previous_pgstattable->idx_tup_fetch, n_tup_ins - previous_pgstattable->n_tup_ins, n_tup_upd - previous_pgstattable->n_tup_upd, n_tup_del - previous_pgstattable->n_tup_del, n_tup_hot_upd - previous_pgstattable->n_tup_hot_upd, n_live_tup - previous_pgstattable->n_live_tup, n_dead_tup - previous_pgstattable->n_dead_tup, n_mod_since_analyze - previous_pgstattable->n_mod_since_analyze, vacuum_count - previous_pgstattable->vacuum_count, autovacuum_count - previous_pgstattable->autovacuum_count, analyze_count - previous_pgstattable->analyze_count, autoanalyze_count - previous_pgstattable->autoanalyze_count ); /* setting the new old value */ previous_pgstattable->seq_scan = seq_scan; previous_pgstattable->seq_tup_read = seq_tup_read; previous_pgstattable->idx_scan = idx_scan; previous_pgstattable->idx_tup_fetch = idx_tup_fetch; previous_pgstattable->n_tup_ins = n_tup_ins; previous_pgstattable->n_tup_upd = n_tup_upd; previous_pgstattable->n_tup_del = n_tup_del; previous_pgstattable->n_tup_hot_upd = n_tup_hot_upd; previous_pgstattable->n_live_tup = n_live_tup; previous_pgstattable->n_dead_tup = n_dead_tup; previous_pgstattable->n_mod_since_analyze = n_mod_since_analyze; previous_pgstattable->vacuum_count = vacuum_count; previous_pgstattable->autovacuum_count = autovacuum_count; previous_pgstattable->analyze_count = analyze_count; previous_pgstattable->autoanalyze_count = autoanalyze_count; } /* cleanup */ PQclear(res); } /* * Dump all table IO stats. */ void print_pgstattableio() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; const char *paramValues[1]; int nrows; int row, column; long heap_blks_read = 0; long heap_blks_hit = 0; long idx_blks_read = 0; long idx_blks_hit = 0; long toast_blks_read = 0; long toast_blks_hit = 0; long tidx_blks_read = 0; long tidx_blks_hit = 0; /* * With a filter, we assume we'll get only one row. * Without, we sum all the fields to get one row. */ if (opts->filter == NULL) { snprintf(sql, sizeof(sql), "SELECT sum(heap_blks_read), sum(heap_blks_hit), sum(idx_blks_read), sum(idx_blks_hit), " "sum(toast_blks_read), sum(toast_blks_hit), sum(tidx_blks_read), sum(tidx_blks_hit) " "FROM pg_statio_all_tables " "WHERE schemaname <> 'information_schema' "); res = PQexec(conn, sql); } else { snprintf(sql, sizeof(sql), "SELECT heap_blks_read, heap_blks_hit, idx_blks_read, idx_blks_hit, " "toast_blks_read, toast_blks_hit, tidx_blks_read, tidx_blks_hit " "FROM pg_statio_all_tables " "WHERE schemaname <> 'information_schema' " " AND relname = $1"); paramValues[0] = pg_strdup(opts->filter); res = PQexecParams(conn, sql, 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 0); /* ask for text results */ } /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ heap_blks_read = atol(PQgetvalue(res, row, column++)); heap_blks_hit = atol(PQgetvalue(res, row, column++)); idx_blks_read = atol(PQgetvalue(res, row, column++)); idx_blks_hit = atol(PQgetvalue(res, row, column++)); toast_blks_read = atol(PQgetvalue(res, row, column++)); toast_blks_hit = atol(PQgetvalue(res, row, column++)); tidx_blks_read = atol(PQgetvalue(res, row, column++)); tidx_blks_hit = atol(PQgetvalue(res, row, column++)); /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6ld %7ld %7ld %7ld %7ld %9ld %9ld\n", heap_blks_read - previous_pgstattableio->heap_blks_read, heap_blks_hit - previous_pgstattableio->heap_blks_hit, idx_blks_read - previous_pgstattableio->idx_blks_read, idx_blks_hit - previous_pgstattableio->idx_blks_hit, toast_blks_read - previous_pgstattableio->toast_blks_read, toast_blks_hit - previous_pgstattableio->toast_blks_hit, tidx_blks_read - previous_pgstattableio->tidx_blks_read, tidx_blks_hit - previous_pgstattableio->tidx_blks_hit ); /* setting the new old value */ previous_pgstattableio->heap_blks_read = heap_blks_read; previous_pgstattableio->heap_blks_hit = heap_blks_hit; previous_pgstattableio->idx_blks_read = idx_blks_read; previous_pgstattableio->idx_blks_hit = idx_blks_hit; previous_pgstattableio->toast_blks_read = toast_blks_read; previous_pgstattableio->toast_blks_hit = toast_blks_hit; previous_pgstattableio->tidx_blks_read = tidx_blks_read; previous_pgstattableio->tidx_blks_hit = tidx_blks_hit; } /* cleanup */ PQclear(res); } /* * Dump all index stats. */ void print_pgstatindex() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; const char *paramValues[1]; int nrows; int row, column; long idx_scan = 0; long idx_tup_read = 0; long idx_tup_fetch = 0; /* * With a filter, we assume we'll get only one row. * Without, we sum all the fields to get one row. */ if (opts->filter == NULL) { snprintf(sql, sizeof(sql), "SELECT sum(idx_scan), sum(idx_tup_read), sum(idx_tup_fetch) " " FROM pg_stat_all_indexes " "WHERE schemaname <> 'information_schema' "); res = PQexec(conn, sql); } else { snprintf(sql, sizeof(sql), "SELECT idx_scan, idx_tup_read, idx_tup_fetch " " FROM pg_stat_all_indexes " "WHERE schemaname <> 'information_schema' " " AND indexrelname = $1"); paramValues[0] = pg_strdup(opts->filter); res = PQexecParams(conn, sql, 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 0); /* ask for text results */ } /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ idx_scan = atol(PQgetvalue(res, row, column++)); idx_tup_read = atof(PQgetvalue(res, row, column++)); idx_tup_fetch = atof(PQgetvalue(res, row, column++)); /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %8ld %7ld %7ld\n", idx_scan - previous_pgstatindex->idx_scan, idx_tup_read - previous_pgstatindex->idx_tup_read, idx_tup_fetch - previous_pgstatindex->idx_tup_fetch ); /* setting the new old value */ previous_pgstatindex->idx_scan = idx_scan; previous_pgstatindex->idx_tup_read = idx_tup_read; previous_pgstatindex->idx_tup_fetch = idx_tup_fetch; } /* cleanup */ PQclear(res); } /* * Dump all function stats. */ void print_pgstatfunction() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; const char *paramValues[1]; int nrows; int row, column; long calls = 0; float total_time = 0; float self_time = 0; /* * With a filter, we assume we'll get only one row. * Without, we sum all the fields to get one row. */ if (opts->filter == NULL) { snprintf(sql, sizeof(sql), "SELECT sum(calls), sum(total_time), sum(self_time) " " FROM pg_stat_user_functions " "WHERE schemaname <> 'information_schema' "); res = PQexec(conn, sql); } else { snprintf(sql, sizeof(sql), "SELECT calls, total_time, self_time " " FROM pg_stat_user_functions " "WHERE schemaname <> 'information_schema' " " AND funcname = $1"); paramValues[0] = pg_strdup(opts->filter); res = PQexecParams(conn, sql, 1, /* one param */ NULL, /* let the backend deduce param type */ paramValues, NULL, /* don't need param lengths since text */ NULL, /* default to all text params */ 0); /* ask for text results */ } /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ calls = atol(PQgetvalue(res, row, column++)); total_time = atof(PQgetvalue(res, row, column++)); self_time = atof(PQgetvalue(res, row, column++)); /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %9ld %5f %5f\n", calls - previous_pgstatfunction->calls, total_time - previous_pgstatfunction->total_time, self_time - previous_pgstatfunction->self_time ); /* setting the new old value */ previous_pgstatfunction->calls = calls; previous_pgstatfunction->total_time = total_time; previous_pgstatfunction->self_time = self_time; } /* cleanup */ PQclear(res); } /* * Dump all statement stats. */ void print_pgstatstatement() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row, column; long calls = 0; float total_time = 0; long rows = 0; long shared_blks_hit = 0; long shared_blks_read = 0; long shared_blks_dirtied = 0; long shared_blks_written = 0; long local_blks_hit = 0; long local_blks_read = 0; long local_blks_dirtied = 0; long local_blks_written = 0; long temp_blks_read = 0; long temp_blks_written = 0; float blk_read_time = 0; float blk_write_time = 0; snprintf(sql, sizeof(sql), "SELECT sum(calls), sum(total_time), sum(rows)," " sum(shared_blks_hit), sum(shared_blks_read), sum(shared_blks_dirtied), sum(shared_blks_written)," " sum(local_blks_hit), sum(local_blks_read), sum(local_blks_dirtied), sum(local_blks_written)," " sum(temp_blks_read), sum(temp_blks_written)," " sum(blk_read_time), sum(blk_write_time)" " FROM %s.pg_stat_statements ", opts->namespace); res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { column = 0; /* getting new values */ calls = atol(PQgetvalue(res, row, column++)); total_time = atof(PQgetvalue(res, row, column++)); rows = atol(PQgetvalue(res, row, column++)); shared_blks_hit = atol(PQgetvalue(res, row, column++)); shared_blks_read = atol(PQgetvalue(res, row, column++)); shared_blks_dirtied = atol(PQgetvalue(res, row, column++)); shared_blks_written = atol(PQgetvalue(res, row, column++)); local_blks_hit = atol(PQgetvalue(res, row, column++)); local_blks_read = atol(PQgetvalue(res, row, column++)); local_blks_dirtied = atol(PQgetvalue(res, row, column++)); local_blks_written = atol(PQgetvalue(res, row, column++)); temp_blks_read = atol(PQgetvalue(res, row, column++)); temp_blks_written = atol(PQgetvalue(res, row, column++)); blk_read_time = atof(PQgetvalue(res, row, column++)); blk_write_time = atof(PQgetvalue(res, row, column++)); /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6.2f %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6.2f %6.2f\n", calls - previous_pgstatstatement->calls, total_time - previous_pgstatstatement->total_time, rows - previous_pgstatstatement->rows, shared_blks_hit - previous_pgstatstatement->shared_blks_hit, shared_blks_read - previous_pgstatstatement->shared_blks_read, shared_blks_dirtied - previous_pgstatstatement->shared_blks_dirtied, shared_blks_written - previous_pgstatstatement->shared_blks_written, local_blks_hit - previous_pgstatstatement->local_blks_hit, local_blks_read - previous_pgstatstatement->local_blks_read, local_blks_dirtied - previous_pgstatstatement->local_blks_dirtied, local_blks_written - previous_pgstatstatement->local_blks_written, temp_blks_read - previous_pgstatstatement->temp_blks_read, temp_blks_written - previous_pgstatstatement->temp_blks_written, blk_read_time - previous_pgstatstatement->blk_read_time, blk_write_time - previous_pgstatstatement->blk_write_time ); /* setting the new old value */ previous_pgstatstatement->calls = calls; previous_pgstatstatement->total_time = total_time; previous_pgstatstatement->rows = rows; previous_pgstatstatement->shared_blks_hit = shared_blks_hit; previous_pgstatstatement->shared_blks_read = shared_blks_read; previous_pgstatstatement->shared_blks_dirtied = shared_blks_dirtied; previous_pgstatstatement->shared_blks_written = shared_blks_written; previous_pgstatstatement->local_blks_hit = local_blks_hit; previous_pgstatstatement->local_blks_read = local_blks_read; previous_pgstatstatement->local_blks_dirtied = local_blks_dirtied; previous_pgstatstatement->local_blks_written = local_blks_written; previous_pgstatstatement->temp_blks_read = temp_blks_read; previous_pgstatstatement->temp_blks_written = temp_blks_written; previous_pgstatstatement->blk_read_time = blk_read_time; previous_pgstatstatement->blk_write_time = blk_write_time; }; /* cleanup */ PQclear(res); } /* * Dump all xlog writes stats. */ void print_xlogstats() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; char *xlogfilename; char *currentlocation; char *prettylocation; long locationdiff; char h_locationdiff[PGSTAT_DEFAULT_STRING_SIZE]; if (backend_minimum_version(10, 0)) { snprintf(sql, sizeof(sql), "SELECT " " pg_walfile_name(pg_current_wal_lsn()), " " pg_current_wal_lsn(), " " pg_wal_lsn_diff(pg_current_wal_lsn(), '0/0'), " " pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), '%s'))", previous_xlogstats->location); } else { snprintf(sql, sizeof(sql), "SELECT " " pg_xlogfile_name(pg_current_xlog_location()), " " pg_current_xlog_location(), " " pg_xlog_location_diff(pg_current_xlog_location(), '0/0'), " " pg_size_pretty(pg_xlog_location_diff(pg_current_xlog_location(), '%s'))", previous_xlogstats->location); } res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } xlogfilename = pg_strdup(PQgetvalue(res, 0, 0)); currentlocation = pg_strdup(PQgetvalue(res, 0, 1)); locationdiff = atol(PQgetvalue(res, 0, 2)); prettylocation = pg_strdup(PQgetvalue(res, 0, 3)); /* get the human-readable diff if asked */ if (opts->human_readable) { snprintf(h_locationdiff, sizeof(h_locationdiff), "%s", prettylocation); } else { snprintf(h_locationdiff, sizeof(h_locationdiff), "%12ld", locationdiff - previous_xlogstats->locationdiff); } /* printing the actual values for once */ (void)printf(" %s %s %s\n", xlogfilename, currentlocation, h_locationdiff); /* setting the new old value */ previous_xlogstats->location = pg_strdup(currentlocation); previous_xlogstats->locationdiff = locationdiff; /* cleanup */ PQclear(res); } /* * Dump all temporary files stats. */ void print_tempfilestats() { char sql[2*PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; long size = 0; long count = 0; int nrows; int row, column; if (backend_minimum_version(9, 3)) { snprintf(sql, sizeof(sql), "SELECT unnest(regexp_matches(agg.tmpfile, 'pgsql_tmp([0-9]*)')) AS pid, " " SUM((pg_stat_file(agg.dir||'/'||agg.tmpfile)).size), " " count(*) " "FROM " " (SELECT ls.oid, ls.spcname, " " ls.dir||'/'||ls.sub AS dir, CASE gs.i WHEN 1 THEN '' ELSE pglsdir END AS tmpfile " " FROM " " (SELECT sr.oid, sr.spcname, " " 'pg_tblspc/'||sr.oid||'/'||sr.spc_root AS dir, " " pg_ls_dir('pg_tblspc/'||sr.oid||'/'||sr.spc_root) AS sub " " FROM (SELECT spc.oid, spc.spcname, " " pg_ls_dir('pg_tblspc/'||spc.oid) AS spc_root, " " trim(trailing E'\n ' FROM pg_read_file('PG_VERSION')) as v " " FROM (SELECT oid, spcname FROM pg_tablespace WHERE spcname !~ '^pg_') AS spc " " ) sr " " WHERE sr.spc_root ~ ('^PG_'||sr.v) " " UNION ALL " " SELECT 0, 'pg_default', " " 'base' AS dir, " " 'pgsql_tmp' AS sub " " FROM pg_ls_dir('base') AS l " " WHERE l='pgsql_tmp' " " ) AS ls, " " (SELECT generate_series(1,2) AS i) AS gs, " " LATERAL pg_ls_dir(dir||'/'||ls.sub) pglsdir " " WHERE ls.sub = 'pgsql_tmp') agg " "GROUP BY 1"); } else { snprintf(sql, sizeof(sql), "SELECT unnest(regexp_matches(agg.tmpfile, 'pgsql_tmp([0-9]*)')) AS pid, " " SUM((pg_stat_file(agg.dir||'/'||agg.tmpfile)).size), " " count(*) " "FROM " " (SELECT ls.oid, ls.spcname, " " ls.dir||'/'||ls.sub AS dir, CASE gs.i WHEN 1 THEN '' ELSE pg_ls_dir(dir||'/'||ls.sub) END AS tmpfile " " FROM " " (SELECT sr.oid, sr.spcname, " " 'pg_tblspc/'||sr.oid||'/'||sr.spc_root AS dir, " " pg_ls_dir('pg_tblspc/'||sr.oid||'/'||sr.spc_root) AS sub " " FROM (SELECT spc.oid, spc.spcname, " " pg_ls_dir('pg_tblspc/'||spc.oid) AS spc_root, " " trim(trailing E'\n ' FROM pg_read_file('PG_VERSION')) as v " " FROM (SELECT oid, spcname FROM pg_tablespace WHERE spcname !~ '^pg_') AS spc " " ) sr " " WHERE sr.spc_root ~ ('^PG_'||sr.v) " " UNION ALL " " SELECT 0, 'pg_default', " " 'base' AS dir, " " 'pgsql_tmp' AS sub " " FROM pg_ls_dir('base') AS l " " WHERE l='pgsql_tmp' " " ) AS ls, " " (SELECT generate_series(1,2) AS i) AS gs " " WHERE ls.sub = 'pgsql_tmp') agg " "GROUP BY 1"); } res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ for (row = 0; row < nrows; row++) { column = 1; /* getting new values */ size += atol(PQgetvalue(res, row, column++)); count += atol(PQgetvalue(res, row, column++)); } /* printing the diff... */ (void)printf(" %9ld %9ld\n", size, count); /* cleanup */ PQclear(res); } /* * Dump all pgBouncer pools stats. */ void print_pgbouncerpools() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row, column; long cl_active = 0; long cl_waiting = 0; long sv_active = 0; long sv_idle = 0; long sv_used = 0; long sv_tested = 0; long sv_login = 0; long maxwait = 0; /* * We cannot use a filter now, we need to get all rows. */ snprintf(sql, sizeof(sql), "SHOW pools"); res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { /* we don't use the first two columns */ column = 2; /* getting new values */ cl_active += atol(PQgetvalue(res, row, column++)); cl_waiting += atol(PQgetvalue(res, row, column++)); sv_active += atol(PQgetvalue(res, row, column++)); sv_idle += atol(PQgetvalue(res, row, column++)); sv_used += atol(PQgetvalue(res, row, column++)); sv_tested += atol(PQgetvalue(res, row, column++)); sv_login += atol(PQgetvalue(res, row, column++)); maxwait += atol(PQgetvalue(res, row, column++)); } /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6ld %6ld %6ld %6ld %6ld %6ld %6ld\n", cl_active, cl_waiting, sv_active, sv_idle, sv_used, sv_tested, sv_login, maxwait ); /* cleanup */ PQclear(res); } /* * Dump all pgBouncer stats. */ void print_pgbouncerstats() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; int nrows; int row, column; long total_request = 0; long total_received = 0; long total_sent = 0; long total_query_time = 0; /* * We cannot use a filter now, we need to get all rows. */ snprintf(sql, sizeof(sql), "SHOW stats"); res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the number of fields */ nrows = PQntuples(res); /* for each row, dump the information */ /* this is stupid, a simple if would do the trick, but it will help for other cases */ for (row = 0; row < nrows; row++) { /* we don't use the first column */ column = 1; /* getting new values */ total_request += atol(PQgetvalue(res, row, column++)); total_received += atol(PQgetvalue(res, row, column++)); total_sent += atol(PQgetvalue(res, row, column++)); total_query_time += atol(PQgetvalue(res, row, column++)); } /* printing the diff... * note that the first line will be the current value, rather than the diff */ (void)printf(" %6ld %6ld %6ld %6ld\n", total_request - previous_pgbouncerstats->total_request, total_received - previous_pgbouncerstats->total_received, total_sent - previous_pgbouncerstats->total_sent, total_query_time - previous_pgbouncerstats->total_query_time ); /* setting the new old value */ previous_pgbouncerstats->total_request = total_request; previous_pgbouncerstats->total_received = total_received; previous_pgbouncerstats->total_sent = total_sent; previous_pgbouncerstats->total_query_time = total_query_time; /* cleanup */ PQclear(res); } /* * Fetch PostgreSQL major and minor numbers */ void fetch_version() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; /* get the cluster version */ snprintf(sql, sizeof(sql), "SELECT version()"); /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } /* get the only row, and parse it to get major and minor numbers */ sscanf(PQgetvalue(res, 0, 0), "%*s %d.%d", &(opts->major), &(opts->minor)); /* print version */ if (opts->verbose) printf("Detected release: %d.%d\n", opts->major, opts->minor); /* cleanup */ PQclear(res); } /* * Fetch pg_stat_statement namespace */ void fetch_pgstatstatements_namespace() { char sql[PGSTAT_DEFAULT_STRING_SIZE]; PGresult *res; /* get the pg_stat_statement installation schema */ if (backend_minimum_version(9, 1)) { snprintf(sql, sizeof(sql), "SELECT nspname FROM pg_extension e " "JOIN pg_namespace n ON e.extnamespace=n.oid " "WHERE extname='pg_stat_statements'"); } else { snprintf(sql, sizeof(sql), "SELECT nspname FROM pg_proc p " "JOIN pg_namespace n ON p.pronamespace=n.oid " "WHERE proname='pg_stat_statements'"); } /* make the call */ res = PQexec(conn, sql); /* check and deal with errors */ if (!res || PQresultStatus(res) > 2) { warnx("pgstats: query failed: %s", PQerrorMessage(conn)); PQclear(res); PQfinish(conn); errx(1, "pgstats: query was: %s", sql); } if (PQntuples(res) > 0) { /* get the only row, and parse it to get major and minor numbers */ opts->namespace = pg_strdup(PQgetvalue(res, 0, 0)); /* print version */ if (opts->verbose) printf("pg_stat_statements namespace: %s\n", opts->namespace); } /* cleanup */ PQclear(res); } /* * Compare given major and minor numbers to the one of the connected server */ bool backend_minimum_version(int major, int minor) { return opts->major > major || (opts->major == major && opts->minor >= minor); } /* * Print the right header according to the stats mode */ void print_header(void) { switch(opts->stat) { case NONE: /* That shouldn't happen */ break; case ARCHIVER: (void)printf("---- WAL counts ----\n"); (void)printf(" archived failed \n"); break; case BGWRITER: (void)printf("------------ checkpoints ------------- ------------- buffers ------------- ---------- misc ----------\n"); (void)printf(" timed requested write_time sync_time checkpoint clean backend alloc maxwritten backend_fsync\n"); break; case CONNECTION: (void)printf(" - total - active - lockwaiting - idle in transaction - idle -\n"); break; case DATABASE: (void)printf("- backends - ------ xacts ------ -------------- blocks -------------- -------------- tuples -------------- ------ temp ------ ------- misc --------\n"); (void)printf(" commit rollback read hit read_time write_time ret fet ins upd del files bytes conflicts deadlocks\n"); break; case TABLE: (void)printf("-- sequential -- ------ index ------ ----------------- tuples -------------------------- -------------- maintenance --------------\n"); (void)printf(" scan tuples scan tuples ins upd del hotupd live dead analyze vacuum autovacuum analyze autoanalyze\n"); break; case TABLEIO: (void)printf("--- heap table --- --- toast table --- --- heap indexes --- --- toast indexes ---\n"); (void)printf(" read hit read hit read hit read hit \n"); break; case INDEX: (void)printf("-- scan -- ----- tuples -----\n"); (void)printf(" read fetch\n"); break; case FUNCTION: (void)printf("-- count -- ------ time ------\n"); (void)printf(" total self\n"); break; case STATEMENT: (void)printf("--------- misc ---------- ----------- shared ----------- ----------- local ----------- ----- temp ----- -------- time --------\n"); (void)printf(" calls time rows hit read dirty written hit read dirty written read written read written\n"); break; case XLOG: (void)printf("-------- filename -------- -- location -- ---- bytes ----\n"); break; case TEMPFILE: (void)printf("--- size --- --- count ---\n"); break; case PBPOOLS: (void)printf("---- client ----- ---------------- server ---------------- -- misc --\n"); (void)printf(" active waiting active idle used tested login maxwait\n"); break; case PBSTATS: (void)printf("---------------- total -----------------\n"); (void)printf(" request received sent query time\n"); break; } if (wresized != 0) doresize(); if (opts->dontredisplayheader) hdrcnt = 0; else hdrcnt = winlines; } /* * Call the right function according to the stats mode */ void print_line(void) { switch(opts->stat) { case NONE: /* That shouldn't happen */ break; case ARCHIVER: print_pgstatarchiver(); break; case BGWRITER: print_pgstatbgwriter(); break; case CONNECTION: print_pgstatconnection(); break; case DATABASE: print_pgstatdatabase(); break; case TABLE: print_pgstattable(); break; case TABLEIO: print_pgstattableio(); break; case INDEX: print_pgstatindex(); break; case FUNCTION: print_pgstatfunction(); break; case STATEMENT: print_pgstatstatement(); break; case XLOG: print_xlogstats(); break; case TEMPFILE: print_tempfilestats(); break; case PBPOOLS: print_pgbouncerpools(); break; case PBSTATS: print_pgbouncerstats(); break; } } /* * Allocate and initialize the right statistics struct according to the stats mode */ void allocate_struct(void) { switch (opts->stat) { case NONE: /* That shouldn't happen */ break; case ARCHIVER: previous_pgstatarchiver = (struct pgstatarchiver *) pg_malloc(sizeof(struct pgstatarchiver)); previous_pgstatarchiver->archived_count = 0; previous_pgstatarchiver->failed_count = 0; break; case BGWRITER: previous_pgstatbgwriter = (struct pgstatbgwriter *) pg_malloc(sizeof(struct pgstatbgwriter)); previous_pgstatbgwriter->checkpoints_timed = 0; previous_pgstatbgwriter->checkpoints_req = 0; previous_pgstatbgwriter->checkpoint_write_time = 0; previous_pgstatbgwriter->checkpoint_sync_time = 0; previous_pgstatbgwriter->buffers_checkpoint = 0; previous_pgstatbgwriter->buffers_clean = 0; previous_pgstatbgwriter->maxwritten_clean = 0; previous_pgstatbgwriter->buffers_backend = 0; previous_pgstatbgwriter->buffers_backend_fsync = 0; previous_pgstatbgwriter->buffers_alloc = 0; break; case CONNECTION: // nothing to do break; case DATABASE: previous_pgstatdatabase = (struct pgstatdatabase *) pg_malloc(sizeof(struct pgstatdatabase)); previous_pgstatdatabase->xact_commit = 0; previous_pgstatdatabase->xact_rollback = 0; previous_pgstatdatabase->blks_read = 0; previous_pgstatdatabase->blks_hit = 0; previous_pgstatdatabase->tup_returned = 0; previous_pgstatdatabase->tup_fetched = 0; previous_pgstatdatabase->tup_inserted = 0; previous_pgstatdatabase->tup_updated = 0; previous_pgstatdatabase->tup_deleted = 0; previous_pgstatdatabase->conflicts = 0; previous_pgstatdatabase->temp_files = 0; previous_pgstatdatabase->temp_bytes = 0; previous_pgstatdatabase->deadlocks = 0; previous_pgstatdatabase->blk_read_time = 0; previous_pgstatdatabase->blk_write_time = 0; break; case TABLE: previous_pgstattable = (struct pgstattable *) pg_malloc(sizeof(struct pgstattable)); previous_pgstattable->seq_scan = 0; previous_pgstattable->seq_tup_read = 0; previous_pgstattable->idx_scan = 0; previous_pgstattable->idx_tup_fetch = 0; previous_pgstattable->n_tup_ins = 0; previous_pgstattable->n_tup_upd = 0; previous_pgstattable->n_tup_del = 0; previous_pgstattable->n_tup_hot_upd = 0; previous_pgstattable->n_live_tup = 0; previous_pgstattable->n_dead_tup = 0; previous_pgstattable->vacuum_count = 0; previous_pgstattable->autovacuum_count = 0; previous_pgstattable->analyze_count = 0; previous_pgstattable->autoanalyze_count = 0; break; case TABLEIO: previous_pgstattableio = (struct pgstattableio *) pg_malloc(sizeof(struct pgstattableio)); previous_pgstattableio->heap_blks_read = 0; previous_pgstattableio->heap_blks_hit = 0; previous_pgstattableio->idx_blks_read = 0; previous_pgstattableio->idx_blks_hit = 0; previous_pgstattableio->toast_blks_read = 0; previous_pgstattableio->toast_blks_hit = 0; previous_pgstattableio->tidx_blks_read = 0; previous_pgstattableio->tidx_blks_hit = 0; break; case INDEX: previous_pgstatindex = (struct pgstatindex *) pg_malloc(sizeof(struct pgstatindex)); previous_pgstatindex->idx_scan = 0; previous_pgstatindex->idx_tup_read = 0; previous_pgstatindex->idx_tup_fetch = 0; break; case FUNCTION: previous_pgstatfunction = (struct pgstatfunction *) pg_malloc(sizeof(struct pgstatfunction)); previous_pgstatfunction->calls = 0; previous_pgstatfunction->total_time = 0; previous_pgstatfunction->self_time = 0; break; case STATEMENT: previous_pgstatstatement = (struct pgstatstatement *) pg_malloc(sizeof(struct pgstatstatement)); previous_pgstatstatement->calls = 0; previous_pgstatstatement->total_time = 0; previous_pgstatstatement->rows = 0; previous_pgstatstatement->shared_blks_hit = 0; previous_pgstatstatement->shared_blks_read = 0; previous_pgstatstatement->shared_blks_dirtied = 0; previous_pgstatstatement->shared_blks_written = 0; previous_pgstatstatement->local_blks_hit = 0; previous_pgstatstatement->local_blks_read = 0; previous_pgstatstatement->local_blks_dirtied = 0; previous_pgstatstatement->local_blks_written = 0; previous_pgstatstatement->temp_blks_read = 0; previous_pgstatstatement->temp_blks_written = 0; previous_pgstatstatement->blk_read_time = 0; previous_pgstatstatement->blk_write_time = 0; break; case XLOG: previous_xlogstats = (struct xlogstats *) pg_malloc(sizeof(struct xlogstats)); previous_xlogstats->location = pg_strdup("0/0"); previous_xlogstats->locationdiff = 0; break; case TEMPFILE: // no initialization worth doing... break; case PBPOOLS: // no initialization worth doing... break; case PBSTATS: previous_pgbouncerstats = (struct pgbouncerstats *) pg_malloc(sizeof(struct pgbouncerstats)); previous_pgbouncerstats->total_request = 0; previous_pgbouncerstats->total_received = 0; previous_pgbouncerstats->total_sent = 0; previous_pgbouncerstats->total_query_time = 0; break; } } /* * Force a header to be prepended to the next output. */ static void needhdr(int dummy) { hdrcnt = 1; } /* * When the terminal is resized, force an update of the maximum number of rows * printed between each header repetition. Then force a new header to be * prepended to the next output. */ void needresize(int signo) { wresized = 1; } /* * Update the global `winlines' count of terminal rows. */ void doresize(void) { int status; struct winsize w; for (;;) { status = ioctl(fileno(stdout), TIOCGWINSZ, &w); if (status == -1 && errno == EINTR) continue; else if (status == -1) errx(1, "ioctl"); if (w.ws_row > 3) winlines = w.ws_row - 3; else winlines = PGSTAT_DEFAULT_LINES; break; } /* * Inhibit doresize() calls until we are rescheduled by SIGWINCH. */ wresized = 0; hdrcnt = 1; } /* * Close the PostgreSQL connection, and quit */ static void quit_properly(SIGNAL_ARGS) { PQfinish(conn); exit(1); } /* * Main function */ int main(int argc, char **argv) { /* * If the user stops the program (control-Z) and then resumes it, * print out the header again. */ pqsignal(SIGCONT, needhdr); pqsignal(SIGINT, quit_properly); /* * If our standard output is a tty, then install a SIGWINCH handler * and set wresized so that our first iteration through the main * pgstat loop will peek at the terminal's current rows to find out * how many lines can fit in a screenful of output. */ if (isatty(fileno(stdout)) != 0) { wresized = 1; (void)signal(SIGWINCH, needresize); } else { wresized = 0; winlines = PGSTAT_DEFAULT_LINES; } /* Allocate the options struct */ opts = (struct options *) pg_malloc(sizeof(struct options)); /* Parse the options */ get_opts(argc, argv); /* Connect to the database */ conn = sql_conn(); /* Get PostgreSQL version * (if we are not connected to the pseudo pgBouncer database */ if (opts->stat != PBPOOLS && opts->stat != PBSTATS) { fetch_version(); } /* Without the -s option, defaults to the bgwriter statistics */ if (opts->stat == NONE) { opts->stat = BGWRITER; } /* Check if the release number matches the statistics */ if (opts->stat == ARCHIVER && !backend_minimum_version(9, 4)) { errx(1, "You need at least 9.4 for this statistic."); } if ((opts->stat == CONNECTION || opts->stat == XLOG) && !backend_minimum_version(9, 2)) { errx(1, "You need at least 9.2 for this statistic."); } if (opts->stat == STATEMENT) { fetch_pgstatstatements_namespace(); if (!opts->namespace) { errx(1, "Cannot find the pg_stat_statements extension."); } } /* Allocate and initialize statistics struct */ allocate_struct(); /* Grab cluster stats info */ for (hdrcnt = 1;;) { if (!--hdrcnt) print_header(); print_line(); (void)fflush(stdout); if (--opts->count == 0) break; (void)usleep(opts->interval * 1000000); } PQfinish(conn); return 0; }