From: Bruce Momjian Date: Thu, 12 Jun 2003 01:36:44 +0000 (+0000) Subject: This patch updates pg_autovacuum in several ways: X-Git-Tag: REL7_4_BETA1~439 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9243664dd473a1b0aec6f658622d6d9db40ea4d2;p=postgresql This patch updates pg_autovacuum in several ways: * A few bug fixes * fixes solaris compile and crash issue * decouple vacuum analyze and analyze thresholds * detach from tty (dameonize) * improved logging layout * more conservative default configuration * improved, expanded and updated README please apply and 1st convenience, or before code freeze which ever comes first :-) At this point I think I have brought pg_autovacuum and its client side design as far as I think it should go. It works, keeping file sizes in check, helps performance and give the administrator a fair amount flexibility in configuring it. Next up is to do the FSM based design that is integrated into the back end. p.s. Thanks to Christopher Browne for his help. Matthew T. O'Connor --- diff --git a/contrib/pg_autovacuum/README.pg_autovacuum b/contrib/pg_autovacuum/README.pg_autovacuum index 2f79ba543e..245783ecf4 100644 --- a/contrib/pg_autovacuum/README.pg_autovacuum +++ b/contrib/pg_autovacuum/README.pg_autovacuum @@ -1,80 +1,156 @@ pg_autovacuum README +-------------------- -pg_autovacuum is a libpq client program that monitors all the databases of a -postgresql server. It uses the stats collector to monitor insert, update and -delete activity. When an individual table exceeds it's insert or delete -threshold (more detail on thresholds below) then that table is vacuumed or -analyzed. This allows postgresql to keep the fsm and table statistics up to -date without having to schedule periodic vacuums with cron regardless of need. +pg_autovacuum is a libpq client program that monitors all the +databases associated with a postgresql server. It uses the stats +collector to monitor insert, update and delete activity. -The primary benefit of pg_autovacuum is that the FSM and table statistic information -are updated as needed. When a table is actively changed pg_autovacuum performs the -necessary vacuums and analyzes, when a table is inactive, no cycles are wasted -performing vacuums and analyzes that are not needed. +When a table exceeds its insert or delete threshold (more detail +on thresholds below) then that table will be vacuumed or analyzed. + +This allows postgresql to keep the fsm and table statistics up to +date, and eliminates the need to schedule periodic vacuums. + +The primary benefit of pg_autovacuum is that the FSM and table +statistic information are updated as needed. When a table is actively +changing, pg_autovacuum will perform the necessary vacuums and +analyzes, whereas if a table remains static, no cycles will be wasted +performing unnecessary vacuums/analyzes. + +A secondary benefit of pg_autovacuum is that it ensures that a +database wide vacuum is performed prior to xid wraparound. This is an +important, if rare, problem, as failing to do so can result in major +data loss. + + +KNOWN ISSUES: +------------- +pg_autovacuum has been tested under Redhat Linux (by me) and Solaris (by +Christopher B. Browne) and all known bugs have been resolved. Please report +any problems to the hackers list. + +pg_autovacuum does not get started automatically by either the postmaster or +by pg_ctl. Along the sames lines, when the postmaster exits no one tells +pg_autovacuum. The result is that at the start of the next loop, +pg_autovacuum fails to connect to the server and exits. Any time it fails +to connect pg_autovacuum exits. + +pg_autovacuum requires that the stats system be enabled and reporting row +level stats. The overhead of the stats system has been shown to be +significant under certain workloads. For instance a tight loop of queries +performing "select 1" was nearly 30% slower with stats enabled. However, +in practice with more realistic workloads, the stats system overhead is +usually nominal. -A secondary benefit of pg_autovacuum is that it guarantees that a database wide -vacuum is performed prior to xid wraparound. This is important as failing to do -so can result in major data loss. INSTALL: -To use pg_autovacuum, uncompress the tar.gz into the contrib directory and modify the -contrib/Makefile to include the pg_autovacuum directory. pg_autovacuum will then be made as -part of the standard postgresql install. +-------- + +As of postgresql v7.4 pg_autovacuum is included in the main source tree +under contrib. Therefore you just make && make install (similar to most other +contrib modules) and it will be installed for you. + +If you are using an earlier version of postgresql just uncompress the tar.gz +into the contrib directory and modify the contrib/Makefile to include the pg_autovacuum +directory. pg_autovacuum will then be made as part of the standard +postgresql install. make sure that the folowing are set in postgresql.conf -stats_start_collector = true -stats_row_level = true -start up the postmaster -then, just execute the pg_autovacuum executable. + stats_start_collector = true + stats_row_level = true + +start up the postmaster, then execute the pg_autovacuum executable. Command line arguments: +----------------------- + pg_autovacuum has the following optional arguments: + -d debug: 0 silent, 1 basic info, 2 more debug info, etc... +-D dameonize: Detach from tty and run in background. -s sleep base value: see "Sleeping" below. -S sleep scaling factor: see "Sleeping" below. --t tuple base threshold: see Vacuuming. --T tuple scaling factor: see Vacuuming. --U username: Username pg_autovacuum will use to connect with, if not specified the - current username is used +-v vacuum base threshold: see Vacuum and Analyze. +-V vacuum scaling factor: see Vacuum and Analyze. +-a analyze base threshold: see Vacuum and Analyze. +-A analyze scaling factor: see Vacuum and Analyze. +-L log file: Name of file to which output is submitted, otherwise STDERR +-U username: Username pg_autovacuum will use to connect with, if not + specified the current username is used. -P password: Password pg_autovacuum will use to connect with. -H host: host name or IP to connect too. -p port: port used for connection. -h help: list of command line options. -All arguments have default values defined in pg_autovacuum.h. At the time of this -writing they are: -#define AUTOVACUUM_DEBUG 1 -#define BASETHRESHOLD 100 -#define SCALINGFACTOR 2 -#define SLEEPVALUE 3 -#define SLEEPSCALINGFACTOR 2 -#define UPDATE_INTERVAL 2 +All arguments have default values defined in pg_autovacuum.h. At the +time of writing they are: + +-d 1 +-v 1000 +-V 2 +-a 500 (half of -v is not specified) +-A 1 (half of -v is not specified) +-s 300 (5 minutes) +-S 2 Vacuum and Analyze: -pg_autovacuum performes either a vacuums analyze or just analyze depending on the table activity. -If the number of (inserts + updates) > insertThreshold, then an only an analyze is performed. -If the number of (deletes + updates ) > deleteThreshold, then a vacuum analyze is performed. -deleteThreshold is equal to: tuple_base_value + (tuple_scaling_factor * "number of tuples in the table") -insertThreshold is equal to: 0.5 * tuple_base_value + (tuple_scaling_factor * "number of tuples in the table") -The insertThreshold is half the deleteThreshold because it's a much lighter operation (approx 5%-10% of vacuum), -so running it more often costs us little in performance degredation. +------------------- + +pg_autovacuum performs either a vacuum analyze or just analyze depending +on the quantity and type of table activity (insert, update, or delete): + +- If the number of (inserts + updates + deletes) > AnalyzeThreshold, then + only an analyze is performed. + +- If the number of (deletes + updates ) > VacuumThreshold, then a + vacuum analyze is performed. + +deleteThreshold is equal to: + vacuum_base_value + (vacuum_scaling_factor * "number of tuples in the table") + +insertThreshold is equal to: + analyze_base_value + (analyze_scaling_factor * "number of tuples in the table") + +The AnalyzeThreshold defaults to half of the VacuumThreshold since it +represents a much less expensive operation (approx 5%-10% of vacuum), and +running it more often should not substantially degrade system performance. Sleeping: -pg_autovacuum sleeps after it is done checking all the databases. It does this so as -to limit the amount of system resources it consumes. This also allows the system -administrator to configure pg_autovacuum to be more or less aggressive. Reducing the -sleep time will cause pg_autovacuum to respond more quickly to changes, be they database -addition / removal, table addition / removal, or just normal table activity. However, -setting these values to high can have a negative net effect on the server. If a table -gets vacuumed 5 times during the course of a large update, it might take much longer -than if it was vacuumed only once. +--------- + +pg_autovacuum sleeps for a while after it is done checking all the +databases. It does this in order to limit the amount of system +resources it consumes. This also allows the system administrator to +configure pg_autovacuum to be more or less aggressive. + +Reducing the sleep time will cause pg_autovacuum to respond more +quickly to changes, whether they be database addition/removal, table +addition/removal, or just normal table activity. + +On the other hand, setting pg_autovaccum to sleep values to agressivly +(for too short a period of time) can have a negative effect on server +performance. If a table gets vacuumed 5 times during the course of a +large update, this is likely to take much longer than if the table was +vacuumed only once, at the end. + The total time it sleeps is equal to: -base_sleep_value + sleep_scaling_factor * "duration of the previous loop" -What it monitors: -pg_autovacuum dynamically generates a list of databases and tables to monitor, in -addition it will dynamically add and remove databases and tables that are -removed from the database server while pg_autovacuum is running. + base_sleep_value + sleep_scaling_factor * "duration of the previous + loop" + +Note that timing measurements are made in seconds; specifying +"pg_vacuum -s 1" means pg_autovacuum could poll the database upto 60 times +minute. In a system with large tables where vacuums may run for several +minutes, longer times between vacuums are likely to be appropriate. + +What pg_autovacuum monitors: +---------------------------- + +pg_autovacuum dynamically generates a list of all databases and tables that +exist on the server. It will dynamically add and remove databases and +tables that are removed from the database server while pg_autovacuum is +running. Overhead is fairly small per object. For example: 10 databases +with 10 tables each appears to less than 10k of memory on my Linux box. diff --git a/contrib/pg_autovacuum/TODO b/contrib/pg_autovacuum/TODO index 50e5aaaec5..f9d383d986 100644 --- a/contrib/pg_autovacuum/TODO +++ b/contrib/pg_autovacuum/TODO @@ -1,6 +1,5 @@ Todo Items for pg_autovacuum client - -_Allow it to detach from the tty +-------------------------------------------------------------------------- _create a FSM export function and see if I can use it for pg_autovacuum @@ -9,6 +8,7 @@ _look into possible benifits of pgstattuple contrib work _Continue trying to reduce server load created by polling. Done: +-------------------------------------------------------------------------- _Check if required pg_stats are enables, if not exit with error _Reduce the number connections and queries to the server @@ -34,3 +34,10 @@ _change name to pg_autovacuum _Add proper table and database removal functions so that we can properly clear up before we exit, and make sure we don't leak memory when removing tables and such. + +_Decouple insert and delete thresholds + +_Fix Vacuum debug routine to include the database name. + +_Allow it to detach from the tty + diff --git a/contrib/pg_autovacuum/pg_autovacuum.c b/contrib/pg_autovacuum/pg_autovacuum.c index 804436afbd..dce065d7b6 100644 --- a/contrib/pg_autovacuum/pg_autovacuum.c +++ b/contrib/pg_autovacuum/pg_autovacuum.c @@ -1,206 +1,329 @@ /* pg_autovacuum.c * All the code for the pg_autovacuum program * (c) 2003 Matthew T. O'Connor + * Revisions by Christopher B. Browne, Liberty RMS */ #include "pg_autovacuum.h" +#define TIMEBUFF 256 +FILE *LOGOUTPUT; +char timebuffer[TIMEBUFF]; +char logbuffer[4096]; -/* Create and return tbl_info struct with initalized to values from row or res */ -tbl_info *init_table_info(PGresult *res, int row) +void +log_entry (const char *logentry) { - tbl_info *new_tbl=(tbl_info *)malloc(sizeof(tbl_info)); + time_t curtime; + struct tm *loctime; + curtime = time (NULL); + loctime = localtime (&curtime); + strftime (timebuffer, TIMEBUFF, "%Y-%m-%d %r", loctime); /* cbb - %F is not always available */ + fprintf (LOGOUTPUT, "[%s] %s\n", timebuffer, logentry); +} + +/* Function used to detatch the pg_autovacuum daemon from the tty and go into the background * +* This code is mostly ripped directly from pm_dameonize in postmaster.c * +* with unneeded code removed. */ +void daemonize () +{ + pid_t pid; - if(!new_tbl) + pid = fork(); + if (pid == (pid_t) -1) { - fprintf(stderr,"init_table_info: Cannot get memory\n"); - return NULL; + log_entry("Error: cannot disassociate from controlling TTY"); + fflush(LOGOUTPUT); + _exit(1); + } + else if (pid) + { /* parent */ + /* Parent should just exit, without doing any atexit cleanup */ + _exit(0); } - if(NULL == res) - return NULL; - - new_tbl->schema_name=(char *)malloc(strlen(PQgetvalue(res,row,PQfnumber(res,"schemaname")))+1); - if(!new_tbl->schema_name) +/* GH: If there's no setsid(), we hopefully don't need silent mode. + * Until there's a better solution. */ +#ifdef HAVE_SETSID + if (setsid() < 0) { - fprintf(stderr,"init_table_info: malloc failed on new_tbl->schema_name\n"); - return NULL; + log_entry("Error: cannot disassociate from controlling TTY"); + fflush(LOGOUTPUT); + _exit(1); } - strcpy(new_tbl->schema_name,PQgetvalue(res,row,PQfnumber(res,"schemaname"))); +#endif - new_tbl->table_name=(char *)malloc(strlen(PQgetvalue(res,row,PQfnumber(res,"relname"))) + strlen(new_tbl->schema_name)+2); - if(!new_tbl->table_name) - { - fprintf(stderr,"init_table_info: malloc failed on new_tbl->table_name\n"); +} + +/* Create and return tbl_info struct with initialized to values from row or res */ +tbl_info * +init_table_info (PGresult * res, int row, db_info *dbi) +{ + tbl_info *new_tbl = (tbl_info *) malloc (sizeof (tbl_info)); + + if (!new_tbl) { + log_entry ("init_table_info: Cannot get memory"); + fflush (LOGOUTPUT); return NULL; } - strcpy(new_tbl->table_name,new_tbl->schema_name); - strcat(new_tbl->table_name,"."); - strcat(new_tbl->table_name,PQgetvalue(res,row,PQfnumber(res,"relname"))); - new_tbl->InsertsAtLastAnalyze=(atol(PQgetvalue(res,row,PQfnumber(res,"n_tup_ins"))) + atol(PQgetvalue(res,row,PQfnumber(res,"n_tup_upd")))); - new_tbl->DeletesAtLastVacuum =(atol(PQgetvalue(res,row,PQfnumber(res,"n_tup_del"))) + atol(PQgetvalue(res,row,PQfnumber(res,"n_tup_upd")))); + if (NULL == res) + return NULL; - new_tbl->relfilenode=atoi(PQgetvalue(res,row,PQfnumber(res,"relfilenode"))); - new_tbl->reltuples=atoi(PQgetvalue(res,row,PQfnumber(res,"reltuples"))); - new_tbl->relpages=atoi(PQgetvalue(res,row,PQfnumber(res,"relpages"))); + new_tbl->dbi = dbi; /* set pointer to db */ - new_tbl->insertThreshold=args->tuple_base_threshold + args->tuple_scaling_factor*new_tbl->reltuples; - new_tbl->deleteThreshold=args->tuple_base_threshold + args->tuple_scaling_factor*new_tbl->reltuples; + new_tbl->schema_name = (char *) + malloc (strlen (PQgetvalue (res, row, PQfnumber (res, "schemaname"))) + 1); + if (!new_tbl->schema_name) { + log_entry ("init_table_info: malloc failed on new_tbl->schema_name"); + fflush (LOGOUTPUT); + return NULL; + } + strcpy (new_tbl->schema_name, + PQgetvalue (res, row, PQfnumber (res, "schemaname"))); + + new_tbl->table_name = (char *) + malloc (strlen (PQgetvalue (res, row, PQfnumber (res, "relname"))) + + strlen (new_tbl->schema_name) + 2); + if (!new_tbl->table_name) { + log_entry ("init_table_info: malloc failed on new_tbl->table_name"); + fflush (LOGOUTPUT); + return NULL; + } + strcpy (new_tbl->table_name, new_tbl->schema_name); + strcat (new_tbl->table_name, "."); + strcat (new_tbl->table_name, PQgetvalue (res, row, PQfnumber (res, "relname"))); + + new_tbl->CountAtLastAnalyze = + (atol (PQgetvalue (res, row, PQfnumber (res, "n_tup_ins"))) + + atol (PQgetvalue (res, row, PQfnumber (res, "n_tup_upd")))); + new_tbl->curr_analyze_count = new_tbl->CountAtLastAnalyze; + + new_tbl->CountAtLastVacuum = + (atol (PQgetvalue (res, row, PQfnumber (res, "n_tup_del"))) + + atol (PQgetvalue (res, row, PQfnumber (res, "n_tup_upd")))); + new_tbl->curr_vacuum_count = new_tbl->CountAtLastVacuum; + + new_tbl->relfilenode = atoi (PQgetvalue (res, row, PQfnumber (res, "relfilenode"))); + new_tbl->reltuples = atoi (PQgetvalue (res, row, PQfnumber (res, "reltuples"))); + new_tbl->relpages = atoi (PQgetvalue (res, row, PQfnumber (res, "relpages"))); + + new_tbl->analyze_threshold = + args->analyze_base_threshold + args->analyze_scaling_factor * new_tbl->reltuples; + new_tbl->vacuum_threshold = + args->vacuum_base_threshold + args->vacuum_scaling_factor * new_tbl->reltuples; + + if (args->debug >= 2) { + print_table_info (new_tbl); + } - if(args->debug >= 2) {print_table_info(new_tbl);} - return new_tbl; } /* Set thresholds = base_value + scaling_factor * reltuples - Should be called after a vacuum since vacuum updates valuesin pg_class */ -void update_table_thresholds(db_info *dbi,tbl_info *tbl) + Should be called after a vacuum since vacuum updates values in pg_class */ +void +update_table_thresholds (db_info * dbi, tbl_info * tbl,int vacuum_type) { - PGresult *res=NULL; - int disconnect=0; - char query[128]; + PGresult *res = NULL; + int disconnect = 0; + char query[128]; - if(NULL==dbi->conn) - { dbi->conn=db_connect(dbi); disconnect=1;} + if (NULL == dbi->conn) { + dbi->conn = db_connect (dbi); + disconnect = 1; + } - if(NULL != dbi->conn) - { - snprintf(query,sizeof(query),"select relfilenode,reltuples,relpages from pg_class where relfilenode=%i",tbl->relfilenode); - res=send_query(query,dbi); - if(NULL!=res) - { - tbl->reltuples = atoi(PQgetvalue(res,0,PQfnumber(res,"reltuples"))); - tbl->relpages = atoi(PQgetvalue(res,0,PQfnumber(res,"relpages"))); - tbl->deleteThreshold = (args->tuple_base_threshold + args->tuple_scaling_factor*tbl->reltuples); - tbl->insertThreshold = (0.5 * tbl->deleteThreshold); - PQclear(res); + if (NULL != dbi->conn) { + snprintf (query, sizeof (query), PAGES_QUERY, tbl->relfilenode); + res = send_query (query, dbi); + if (NULL != res) { + tbl->reltuples = + atoi (PQgetvalue (res, 0, PQfnumber (res, "reltuples"))); + tbl->relpages = atoi (PQgetvalue (res, 0, PQfnumber (res, "relpages"))); + + /* update vacuum thresholds only of we just did a vacuum analyze */ + if(VACUUM_ANALYZE == vacuum_type) + { + tbl->vacuum_threshold = + (args->vacuum_base_threshold + args->vacuum_scaling_factor * tbl->reltuples); + tbl->CountAtLastVacuum = tbl->curr_vacuum_count; + } + + /* update analyze thresholds */ + tbl->analyze_threshold = + (args->analyze_base_threshold + args->analyze_scaling_factor * tbl->reltuples); + tbl->CountAtLastAnalyze = tbl->curr_analyze_count; + + PQclear (res); + + /* If the stats collector is reporting fewer updates then we have on record + then the stats were probably reset, so we need to reset also */ + if ((tbl->curr_analyze_count < tbl->CountAtLastAnalyze) || + (tbl->curr_vacuum_count < tbl->CountAtLastVacuum)) + { + tbl->CountAtLastAnalyze = tbl->curr_analyze_count; + tbl->CountAtLastVacuum = tbl->curr_vacuum_count; + } } - } - if(disconnect) db_disconnect(dbi); + } + if (disconnect) + db_disconnect (dbi); } -void update_table_list(db_info *dbi) +void +update_table_list (db_info * dbi) { - int disconnect=0; - PGresult *res=NULL; - tbl_info *tbl=NULL; - Dlelem *tbl_elem=DLGetHead(dbi->table_list); - int i=0,t=0,found_match=0; - - if(NULL==dbi->conn) - { dbi->conn=db_connect(dbi); disconnect=1;} - - if(NULL != dbi->conn) - { + int disconnect = 0; + PGresult *res = NULL; + tbl_info *tbl = NULL; + Dlelem *tbl_elem = DLGetHead (dbi->table_list); + int i = 0, t = 0, found_match = 0; + + if (NULL == dbi->conn) { + dbi->conn = db_connect (dbi); + disconnect = 1; + } + + if (NULL != dbi->conn) { /* Get a result set that has all the information - we will need to both remove tables from the list + we will need to both remove tables from the list that no longer exist and add tables to the list that are new */ - res=send_query(query_table_stats(dbi),dbi); - t=PQntuples(res); - - /* First: use the tbl_list as the outer loop and - the result set as the inner loop, this will - determine what tables should be removed */ - while(NULL != tbl_elem) - { - tbl=((tbl_info *)DLE_VAL(tbl_elem)); - found_match=0; - - for(i=0;irelfilenode==atoi(PQgetvalue(res,i,PQfnumber(res,"relfilenode")))) - { - found_match=1; - break; - } - } - if(0==found_match) /*then we didn't find this tbl_elem in the result set */ - { - Dlelem *elem_to_remove=tbl_elem; - tbl_elem=DLGetSucc(tbl_elem); - remove_table_from_list(elem_to_remove); - } - else - tbl_elem=DLGetSucc(tbl_elem); - } /* Done removing dropped tables from the table_list */ - - /* Then loop use result set as outer loop and - tbl_list as the inner loop to determine - what tables are new */ - for(i=0;itable_list); - found_match=0; - while(NULL != tbl_elem) - { - tbl=((tbl_info *)DLE_VAL(tbl_elem)); - if(tbl->relfilenode==atoi(PQgetvalue(res,i,PQfnumber(res,"relfilenode")))) - { - found_match=1; - break; - } - tbl_elem=DLGetSucc(tbl_elem); - } - if(0==found_match) /*then we didn't find this result now in the tbl_list */ - { - DLAddTail(dbi->table_list,DLNewElem(init_table_info(res,i))); - if(args->debug >= 1) {printf("added table: %s.%s\n",dbi->dbname,((tbl_info *)DLE_VAL(DLGetTail(dbi->table_list)))->table_name);} - } - } /* end of for loop that adds tables */ - PQclear(res); res=NULL; - if(args->debug >= 3) {print_table_list(dbi->table_list);} - if(disconnect) db_disconnect(dbi); - } + res = send_query (query_table_stats (dbi), dbi); + t = PQntuples (res); + + /* First: use the tbl_list as the outer loop and + the result set as the inner loop, this will + determine what tables should be removed */ + while (NULL != tbl_elem) { + tbl = ((tbl_info *) DLE_VAL (tbl_elem)); + found_match = 0; + + for (i = 0; i < t; i++) { /* loop through result set looking for a match */ + if (tbl->relfilenode == atoi (PQgetvalue (res, i, PQfnumber (res, "relfilenode")))) { + found_match = 1; + break; + } + } + if (0 == found_match) { /* then we didn't find this tbl_elem in the result set */ + Dlelem *elem_to_remove = tbl_elem; + tbl_elem = DLGetSucc (tbl_elem); + remove_table_from_list (elem_to_remove); + } + else + tbl_elem = DLGetSucc (tbl_elem); + } /* Done removing dropped tables from the table_list */ + + /* Then loop use result set as outer loop and + tbl_list as the inner loop to determine + what tables are new */ + for (i = 0; i < t; i++) + { + tbl_elem = DLGetHead (dbi->table_list); + found_match = 0; + while (NULL != tbl_elem) + { + tbl = ((tbl_info *) DLE_VAL (tbl_elem)); + if (tbl->relfilenode == atoi (PQgetvalue (res, i, PQfnumber (res, "relfilenode")))) + { + found_match = 1; + break; + } + tbl_elem = DLGetSucc (tbl_elem); + } + if (0 == found_match) /*then we didn't find this result now in the tbl_list */ + { + DLAddTail (dbi->table_list, DLNewElem (init_table_info (res, i, dbi))); + if (args->debug >= 1) + { + sprintf (logbuffer, "added table: %s.%s", dbi->dbname, + ((tbl_info *) DLE_VAL (DLGetTail (dbi->table_list)))->table_name); + log_entry (logbuffer); + } + } + } /* end of for loop that adds tables */ + fflush (LOGOUTPUT); + PQclear (res); + res = NULL; + if (args->debug >= 3) { + print_table_list (dbi->table_list); + } + if (disconnect) + db_disconnect (dbi); + } } /* Free memory, and remove the node from the list */ -void remove_table_from_list(Dlelem *tbl_to_remove) +void +remove_table_from_list (Dlelem * tbl_to_remove) { - tbl_info *tbl=((tbl_info *)DLE_VAL(tbl_to_remove)); - - if(args->debug >= 1) {printf("Removing table: %s from list.\n",tbl->table_name);} - DLRemove(tbl_to_remove); - - if(tbl->schema_name) - { free(tbl->schema_name); tbl->schema_name=NULL;} - if(tbl->table_name) - { free(tbl->table_name); tbl->table_name=NULL;} - if(tbl) - { free(tbl); tbl=NULL;} - DLFreeElem(tbl_to_remove); + tbl_info *tbl = ((tbl_info *) DLE_VAL (tbl_to_remove)); + + if (args->debug >= 1) { + sprintf (logbuffer, "Removing table: %s from list.", tbl->table_name); + log_entry (logbuffer); + fflush (LOGOUTPUT); + } + DLRemove (tbl_to_remove); + + if (tbl->schema_name) { + free (tbl->schema_name); + tbl->schema_name = NULL; + } + if (tbl->table_name) { + free (tbl->table_name); + tbl->table_name = NULL; + } + if (tbl) { + free (tbl); + tbl = NULL; + } + DLFreeElem (tbl_to_remove); } /* Free the entire table list */ -void free_tbl_list(Dllist *tbl_list) +void +free_tbl_list (Dllist * tbl_list) { - Dlelem *tbl_elem=DLGetHead(tbl_list); - Dlelem *tbl_elem_to_remove=NULL; - while(NULL != tbl_elem) - { - tbl_elem_to_remove=tbl_elem; - tbl_elem=DLGetSucc(tbl_elem); - remove_table_from_list(tbl_elem_to_remove); - } - DLFreeList(tbl_list); + Dlelem *tbl_elem = DLGetHead (tbl_list); + Dlelem *tbl_elem_to_remove = NULL; + while (NULL != tbl_elem) { + tbl_elem_to_remove = tbl_elem; + tbl_elem = DLGetSucc (tbl_elem); + remove_table_from_list (tbl_elem_to_remove); + } + DLFreeList (tbl_list); } -void print_table_list(Dllist *table_list) +void +print_table_list (Dllist * table_list) { - Dlelem *table_elem=DLGetHead(table_list); - while (NULL != table_elem) - { - print_table_info(((tbl_info *)DLE_VAL(table_elem))); - table_elem=DLGetSucc(table_elem); + Dlelem *table_elem = DLGetHead (table_list); + while (NULL != table_elem) { + print_table_info (((tbl_info *) DLE_VAL (table_elem))); + table_elem = DLGetSucc (table_elem); } } -void print_table_info(tbl_info *tbl) +void +print_table_info (tbl_info * tbl) { - printf(" table name: %s\n",tbl->table_name); - printf(" iThresh: %i; Delete Thresh %i\n",tbl->insertThreshold,tbl->deleteThreshold); - printf(" relfilenode: %i; reltuples: %i; relpages: %i\n",tbl->relfilenode,tbl->reltuples,tbl->relpages); - printf(" InsertsAtLastAnalyze: %li; DeletesAtLastVacuum: %li\n",tbl->InsertsAtLastAnalyze,tbl->DeletesAtLastVacuum); + sprintf (logbuffer, " table name: %s.%s", tbl->dbi->dbname, tbl->table_name); + log_entry (logbuffer); + sprintf (logbuffer, " relfilenode: %i",tbl->relfilenode); + log_entry (logbuffer); + sprintf (logbuffer, " reltuples: %i; relpages: %i", tbl->reltuples, tbl->relpages); + log_entry (logbuffer); + sprintf (logbuffer, " curr_analyze_count: %li; cur_delete_count: %li", + tbl->curr_analyze_count, tbl->curr_vacuum_count); + log_entry (logbuffer); + sprintf (logbuffer, " ins_at_last_analyze: %li; del_at_last_vacuum: %li", + tbl->CountAtLastAnalyze, tbl->CountAtLastVacuum); + log_entry (logbuffer); + sprintf (logbuffer, " insert_threshold: %li; delete_threshold %li", + tbl->analyze_threshold, tbl->vacuum_threshold); + log_entry (logbuffer); + fflush (LOGOUTPUT); } /* End of table Management Functions */ @@ -208,144 +331,162 @@ void print_table_info(tbl_info *tbl) /* Beginning of DB Management Functions */ /* init_db_list() creates the db_list and initalizes template1 */ -Dllist *init_db_list() +Dllist * +init_db_list () { - Dllist *db_list=DLNewList(); - db_info *dbs=NULL; - PGresult *res=NULL; - - DLAddHead(db_list,DLNewElem(init_dbinfo((char *)"template1",0,0))); - if(NULL == DLGetHead(db_list)) /* Make sure init_dbinfo was successful */ - { printf("init_db_list(): Error creating db_list for db: template1.\n"); return NULL; } + Dllist *db_list = DLNewList (); + db_info *dbs = NULL; + PGresult *res = NULL; + + DLAddHead (db_list, DLNewElem (init_dbinfo ((char *) "template1", 0, 0))); + if (NULL == DLGetHead (db_list)) { /* Make sure init_dbinfo was successful */ + log_entry ("init_db_list(): Error creating db_list for db: template1."); + fflush (LOGOUTPUT); + return NULL; + } /* We do this just so we can set the proper oid for the template1 database */ - dbs = ((db_info *)DLE_VAL(DLGetHead(db_list))); - dbs->conn=db_connect(dbs); - - if(NULL != dbs->conn) - { - res=send_query("select oid,age(datfrozenxid) from pg_database where datname = 'template1'",dbs); - dbs->oid=atoi(PQgetvalue(res,0,PQfnumber(res,"oid"))); - dbs->age=atoi(PQgetvalue(res,0,PQfnumber(res,"age"))); - if(res) - PQclear(res); - - if(args->debug >= 2) {print_db_list(db_list,0);} - } + dbs = ((db_info *) DLE_VAL (DLGetHead (db_list))); + dbs->conn = db_connect (dbs); + + if (NULL != dbs->conn) { + res = send_query (FROZENOID_QUERY, dbs); + dbs->oid = atoi (PQgetvalue (res, 0, PQfnumber (res, "oid"))); + dbs->age = atoi (PQgetvalue (res, 0, PQfnumber (res, "age"))); + if (res) + PQclear (res); + + if (args->debug >= 2) { + print_db_list (db_list, 0); + } + } return db_list; } /* Simple function to create an instance of the dbinfo struct Initalizes all the pointers and connects to the database */ -db_info *init_dbinfo(char *dbname, int oid, int age) +db_info * +init_dbinfo (char *dbname, int oid, int age) { - db_info *newdbinfo=(db_info *)malloc(sizeof(db_info)); - newdbinfo->insertThreshold=args->tuple_base_threshold; - newdbinfo->deleteThreshold=args->tuple_base_threshold; - newdbinfo->dbname=(char *)malloc(strlen(dbname)+1); - strcpy(newdbinfo->dbname,dbname); - newdbinfo->username=NULL; - if(NULL != args->user) - { - newdbinfo->username=(char *)malloc(strlen(args->user)+1); - strcpy(newdbinfo->username,args->user); + db_info *newdbinfo = (db_info *) malloc (sizeof (db_info)); + newdbinfo->analyze_threshold = args->vacuum_base_threshold; + newdbinfo->vacuum_threshold = args->analyze_base_threshold; + newdbinfo->dbname = (char *) malloc (strlen (dbname) + 1); + strcpy (newdbinfo->dbname, dbname); + newdbinfo->username = NULL; + if (NULL != args->user) { + newdbinfo->username = (char *) malloc (strlen (args->user) + 1); + strcpy (newdbinfo->username, args->user); } - newdbinfo->password=NULL; - if(NULL != args->password) - { - newdbinfo->password=(char *)malloc(strlen(args->password)+1); - strcpy(newdbinfo->password,args->password); + newdbinfo->password = NULL; + if (NULL != args->password) { + newdbinfo->password = (char *) malloc (strlen (args->password) + 1); + strcpy (newdbinfo->password, args->password); + } + newdbinfo->oid = oid; + newdbinfo->age = age; + newdbinfo->table_list = DLNewList (); + newdbinfo->conn = NULL; + + if (args->debug >= 2) { + print_table_list (newdbinfo->table_list); } - newdbinfo->oid=oid; - newdbinfo->age=age; - newdbinfo->table_list=DLNewList(); - newdbinfo->conn=NULL; - - if(args->debug >= 2) {print_table_list(newdbinfo->table_list);} return newdbinfo; } /* Function adds and removes databases from the db_list as appropriate */ -void update_db_list(Dllist *db_list) +void +update_db_list (Dllist * db_list) { - int disconnect=0; - PGresult *res=NULL; - Dlelem *db_elem=DLGetHead(db_list); - db_info *dbi=NULL; - db_info *dbi_template1=DLE_VAL(db_elem); - int i=0,t=0,found_match=0; - - if(args->debug >= 2) {printf("updating the database list\n");} + int disconnect = 0; + PGresult *res = NULL; + Dlelem *db_elem = DLGetHead (db_list); + db_info *dbi = NULL; + db_info *dbi_template1 = DLE_VAL (db_elem); + int i = 0, t = 0, found_match = 0; + + if (args->debug >= 2) { + log_entry ("updating the database list"); + fflush (LOGOUTPUT); + } - if(NULL==dbi_template1->conn) - { dbi_template1->conn=db_connect(dbi_template1); disconnect=1;} + if (NULL == dbi_template1->conn) { + dbi_template1->conn = db_connect (dbi_template1); + disconnect = 1; + } - if(NULL != dbi_template1->conn) - { - /* Get a resu22lt set that has all the information - we will need to both remove databasews from the list - that no longer exist and add databases to the list - that are new */ - res=send_query("select oid,datname,age(datfrozenxid) from pg_database where datname!='template0'",dbi_template1); - t=PQntuples(res); - - /* First: use the db_list as the outer loop and - the result set as the inner loop, this will - determine what databases should be removed */ - while(NULL != db_elem) - { - dbi=((db_info *)DLE_VAL(db_elem)); - found_match=0; - - for(i=0;ioid==atoi(PQgetvalue(res,i,PQfnumber(res,"oid")))) - { - found_match=1; - /* update the dbi->age so that we ensure xid_wraparound won't happen */ - dbi->age=atoi(PQgetvalue(res,i,PQfnumber(res,"age"))); - break; - } - } - if(0==found_match) /*then we didn't find this db_elem in the result set */ - { - Dlelem *elem_to_remove=db_elem; - db_elem=DLGetSucc(db_elem); - remove_db_from_list(elem_to_remove); - } - else - db_elem=DLGetSucc(db_elem); - } /* Done removing dropped databases from the table_list */ - - /* Then loop use result set as outer loop and - db_list as the inner loop to determine - what databases are new */ - for(i=0;ioid==atoi(PQgetvalue(res,i,PQfnumber(res,"oid")))) - { - found_match=1; - break; - } - db_elem=DLGetSucc(db_elem); - } - if(0==found_match) /*then we didn't find this result now in the tbl_list */ - { - DLAddTail(db_list,DLNewElem(init_dbinfo(PQgetvalue(res,i,PQfnumber(res,"datname")), - atoi(PQgetvalue(res,i,PQfnumber(res,"oid"))),atoi(PQgetvalue(res,i,PQfnumber(res,"age")))))); - if(args->debug >= 1) {printf("added database: %s\n",((db_info *)DLE_VAL(DLGetTail(db_list)))->dbname);} - } - } /* end of for loop that adds tables */ - PQclear(res); res=NULL; - if(args->debug >= 3) {print_db_list(db_list,0);} - if(disconnect) db_disconnect(dbi_template1); - } + if (NULL != dbi_template1->conn) { + /* Get a result set that has all the information + we will need to both remove databasews from the list + that no longer exist and add databases to the list + that are new */ + res = send_query (FROZENOID_QUERY2, dbi_template1); + t = PQntuples (res); + + /* First: use the db_list as the outer loop and + the result set as the inner loop, this will + determine what databases should be removed */ + while (NULL != db_elem) { + dbi = ((db_info *) DLE_VAL (db_elem)); + found_match = 0; + + for (i = 0; i < t; i++) { /* loop through result set looking for a match */ + if (dbi->oid == atoi (PQgetvalue (res, i, PQfnumber (res, "oid")))) { + found_match = 1; + /* update the dbi->age so that we ensure xid_wraparound won't happen */ + dbi->age = atoi (PQgetvalue (res, i, PQfnumber (res, "age"))); + break; + } + } + if (0 == found_match) { /*then we didn't find this db_elem in the result set */ + Dlelem *elem_to_remove = db_elem; + db_elem = DLGetSucc (db_elem); + remove_db_from_list (elem_to_remove); + } + else + db_elem = DLGetSucc (db_elem); + } /* Done removing dropped databases from the table_list */ + + /* Then loop use result set as outer loop and + db_list as the inner loop to determine + what databases are new */ + for (i = 0; i < t; i++) + { + db_elem = DLGetHead (db_list); + found_match = 0; + while (NULL != db_elem) + { + dbi = ((db_info *) DLE_VAL (db_elem)); + if (dbi->oid == atoi (PQgetvalue (res, i, PQfnumber (res, "oid")))) + { + found_match = 1; + break; + } + db_elem = DLGetSucc (db_elem); + } + if (0 == found_match) /*then we didn't find this result now in the tbl_list */ + { + DLAddTail (db_list, DLNewElem (init_dbinfo + (PQgetvalue(res, i, PQfnumber (res, "datname")), + atoi (PQgetvalue(res, i, PQfnumber (res, "oid"))), + atoi (PQgetvalue(res, i, PQfnumber (res, "age")))))); + if (args->debug >= 1) + { + sprintf (logbuffer, "added database: %s",((db_info *) DLE_VAL (DLGetTail (db_list)))->dbname); + log_entry (logbuffer); + } + } + } /* end of for loop that adds tables */ + fflush (LOGOUTPUT); + PQclear (res); + res = NULL; + if (args->debug >= 3) { + print_db_list (db_list, 0); + } + if (disconnect) + db_disconnect (dbi_template1); + } } /* xid_wraparound_check @@ -362,81 +503,102 @@ So we do a full database vacuum if age > 1.5billion return 0 if nothing happened, return 1 if the database needed a database wide vacuum */ -int xid_wraparound_check(db_info *dbi) +int +xid_wraparound_check (db_info * dbi) { /* FIXME: should probably do something better here so that we don't vacuum all the - databases on the server at the same time. We have 500million xacts to work with so - we should be able to spread the load of full database vacuums a bit */ - if(1500000000 < dbi->age) - { - PGresult *res=NULL; - res=send_query("vacuum",dbi); - /* FIXME: Perhaps should add a check for PQ_COMMAND_OK */ - PQclear(res); - return 1; - } - return 0; + databases on the server at the same time. We have 500million xacts to work with so + we should be able to spread the load of full database vacuums a bit */ + if (1500000000 < dbi->age) { + PGresult *res = NULL; + res = send_query ("vacuum", dbi); + /* FIXME: Perhaps should add a check for PQ_COMMAND_OK */ + PQclear (res); + return 1; + } + return 0; } /* Close DB connection, free memory, and remove the node from the list */ -void remove_db_from_list(Dlelem *db_to_remove) +void +remove_db_from_list (Dlelem * db_to_remove) { - db_info *dbi=((db_info *)DLE_VAL(db_to_remove)); - - if(args->debug >= 1) {printf("Removing db: %s from list.\n",dbi->dbname);} - DLRemove(db_to_remove); - if(dbi->conn) - db_disconnect(dbi); - if(dbi->dbname) - { free(dbi->dbname); dbi->dbname=NULL;} - if(dbi->username) - { free(dbi->username); dbi->username=NULL;} - if(dbi->password) - { free(dbi->password); dbi->password=NULL;} - if(dbi->table_list) - { free_tbl_list(dbi->table_list); dbi->table_list=NULL;} - if(dbi) - { free(dbi); dbi=NULL;} - DLFreeElem(db_to_remove); + db_info *dbi = ((db_info *) DLE_VAL (db_to_remove)); + + if (args->debug >= 1) { + sprintf (logbuffer, "Removing db: %s from list.", dbi->dbname); + log_entry (logbuffer); + fflush (LOGOUTPUT); + } + DLRemove (db_to_remove); + if (dbi->conn) + db_disconnect (dbi); + if (dbi->dbname) { + free (dbi->dbname); + dbi->dbname = NULL; + } + if (dbi->username) { + free (dbi->username); + dbi->username = NULL; + } + if (dbi->password) { + free (dbi->password); + dbi->password = NULL; + } + if (dbi->table_list) { + free_tbl_list (dbi->table_list); + dbi->table_list = NULL; + } + if (dbi) { + free (dbi); + dbi = NULL; + } + DLFreeElem (db_to_remove); } /* Function is called before program exit to free all memory mostly it's just to keep valgrind happy */ -void free_db_list(Dllist *db_list) +void +free_db_list (Dllist * db_list) { - Dlelem *db_elem=DLGetHead(db_list); - Dlelem *db_elem_to_remove=NULL; - while(NULL != db_elem) - { - db_elem_to_remove=db_elem; - db_elem=DLGetSucc(db_elem); - remove_db_from_list(db_elem_to_remove); - db_elem_to_remove=NULL; - } - DLFreeList(db_list); + Dlelem *db_elem = DLGetHead (db_list); + Dlelem *db_elem_to_remove = NULL; + while (NULL != db_elem) { + db_elem_to_remove = db_elem; + db_elem = DLGetSucc (db_elem); + remove_db_from_list (db_elem_to_remove); + db_elem_to_remove = NULL; + } + DLFreeList (db_list); } -void print_db_list(Dllist *db_list, int print_table_lists) +void +print_db_list (Dllist * db_list, int print_table_lists) { - Dlelem *db_elem=DLGetHead(db_list); - while(NULL != db_elem) - { - print_db_info(((db_info *)DLE_VAL(db_elem)),print_table_lists); - db_elem=DLGetSucc(db_elem); + Dlelem *db_elem = DLGetHead (db_list); + while (NULL != db_elem) { + print_db_info (((db_info *) DLE_VAL (db_elem)), print_table_lists); + db_elem = DLGetSucc (db_elem); } } -void print_db_info(db_info *dbi, int print_tbl_list) +void +print_db_info (db_info * dbi, int print_tbl_list) { - printf("dbname: %s\n Username %s\n Passwd %s\n",dbi->dbname,dbi->username,dbi->password); - printf(" oid %i\n InsertThresh: %i\n DeleteThresh: %i\n",dbi->oid,dbi->insertThreshold,dbi->deleteThreshold); - if(NULL!=dbi->conn) - printf(" conn is valid, we are connected\n"); + sprintf (logbuffer, "dbname: %s Username %s Passwd %s", dbi->dbname, + dbi->username, dbi->password); + log_entry (logbuffer); + sprintf (logbuffer, " oid %i InsertThresh: %i DeleteThresh: %i", dbi->oid, + dbi->analyze_threshold, dbi->vacuum_threshold); + log_entry (logbuffer); + if (NULL != dbi->conn) + log_entry (" conn is valid, we are connected"); else - printf(" conn is null, we are not connected.\n"); + log_entry (" conn is null, we are not connected."); - if(0 < print_tbl_list) - print_table_list(dbi->table_list); + fflush (LOGOUTPUT); + if (0 < print_tbl_list) + print_table_list (dbi->table_list); } /* End of DB List Management Function */ @@ -444,291 +606,407 @@ void print_db_info(db_info *dbi, int print_tbl_list) /* Begninning of misc Functions */ -char *query_table_stats(db_info *dbi) +char * +query_table_stats (db_info * dbi) { - if(!strcmp(dbi->dbname,"template1")) /* Use template1 to monitor the system tables */ - return (char*)TABLE_STATS_ALL; + if (!strcmp (dbi->dbname, "template1")) /* Use template1 to monitor the system tables */ + return (char *) TABLE_STATS_ALL; else - return (char*)TABLE_STATS_USER; + return (char *) TABLE_STATS_USER; } -/* Perhaps add some test to this function to make sure that the stats we need are availalble */ -PGconn *db_connect(db_info *dbi) +/* Perhaps add some test to this function to make sure that the stats we need are available */ +PGconn * +db_connect (db_info * dbi) { - PGconn *db_conn=PQsetdbLogin(args->host, args->port, NULL, NULL, dbi->dbname, dbi->username, dbi->password); - - if(CONNECTION_OK != PQstatus(db_conn)) - { - fprintf(stderr,"Failed connection to database %s with error: %s.\n",dbi->dbname,PQerrorMessage(db_conn)); - PQfinish(db_conn); - db_conn=NULL; + PGconn *db_conn = + PQsetdbLogin (args->host, args->port, NULL, NULL, dbi->dbname, + dbi->username, dbi->password); + + if (CONNECTION_OK != PQstatus (db_conn)) { + sprintf (logbuffer, "Failed connection to database %s with error: %s.", + dbi->dbname, PQerrorMessage (db_conn)); + log_entry (logbuffer); + fflush (LOGOUTPUT); + PQfinish (db_conn); + db_conn = NULL; } return db_conn; -} /* end of db_connect() */ +} /* end of db_connect() */ -void db_disconnect(db_info *dbi) +void +db_disconnect (db_info * dbi) { - if(NULL != dbi->conn) - { - PQfinish(dbi->conn); - dbi->conn=NULL; + if (NULL != dbi->conn) { + PQfinish (dbi->conn); + dbi->conn = NULL; } } -int check_stats_enabled(db_info *dbi) +int +check_stats_enabled (db_info * dbi) { - PGresult *res=NULL; - int ret=0; - res=send_query("show stats_row_level",dbi); - ret = strcmp("on",PQgetvalue(res,0,PQfnumber(res,"stats_row_level"))); - PQclear(res); - return ret; + PGresult *res = NULL; + int ret = 0; + res = send_query ("show stats_row_level", dbi); + ret = + strcmp ("on", PQgetvalue (res, 0, PQfnumber (res, "stats_row_level"))); + PQclear (res); + return ret; } -PGresult *send_query(const char *query,db_info *dbi) +PGresult * +send_query (const char *query, db_info * dbi) { PGresult *res; - if(NULL==dbi->conn) + if (NULL == dbi->conn) return NULL; - res=PQexec(dbi->conn,query); + res = PQexec (dbi->conn, query); - if(!res) - { - fprintf(stderr,"Fatal error occured while sending query (%s) to database %s\n",query,dbi->dbname); - fprintf(stderr,"The error is \n%s\n",PQresultErrorMessage(res)); + if (!res) { + sprintf (logbuffer, + "Fatal error occured while sending query (%s) to database %s", + query, dbi->dbname); + log_entry (logbuffer); + sprintf (logbuffer, "The error is [%s]", PQresultErrorMessage (res)); + log_entry (logbuffer); + fflush (LOGOUTPUT); return NULL; } - if(PQresultStatus(res)!=PGRES_TUPLES_OK && PQresultStatus(res)!=PGRES_COMMAND_OK) - { - fprintf(stderr,"Can not refresh statistics information from the database %s.\n",dbi->dbname); - fprintf(stderr,"The error is \n%s\n",PQresultErrorMessage(res)); - PQclear(res); + if (PQresultStatus (res) != PGRES_TUPLES_OK + && PQresultStatus (res) != PGRES_COMMAND_OK) { + sprintf (logbuffer, + "Can not refresh statistics information from the database %s.", + dbi->dbname); + log_entry (logbuffer); + sprintf (logbuffer, "The error is [%s]", PQresultErrorMessage (res)); + log_entry (logbuffer); + fflush (LOGOUTPUT); + PQclear (res); return NULL; } return res; -} /* End of send_query() */ +} /* End of send_query() */ -void free_cmd_args() +void +free_cmd_args () { - if(NULL!=args) - { - if(NULL!=args->user) - free(args->user); - if(NULL!=args->user) - free(args->password); - free(args); + if (NULL != args) { + if (NULL != args->user) + free (args->user); + if (NULL != args->user) + free (args->password); + free (args); } } -cmd_args *get_cmd_args(int argc,char *argv[]) +cmd_args * +get_cmd_args (int argc, char *argv[]) { int c; - - args=(cmd_args *)malloc(sizeof(cmd_args)); - args->sleep_base_value=SLEEPVALUE; - args->sleep_scaling_factor=SLEEPSCALINGFACTOR; - args->tuple_base_threshold=BASETHRESHOLD; - args->tuple_scaling_factor=SCALINGFACTOR; - args->debug=AUTOVACUUM_DEBUG; - args->user=NULL; - args->password=NULL; - args->host=NULL; - args->port=NULL; - while (-1 != (c = getopt(argc, argv, "s:S:t:T:d:U:P:H:p:h"))) - { - switch (c) - { - case 's': - args->sleep_base_value=atoi(optarg); - break; - case 'S': - args->sleep_scaling_factor = atof(optarg); - break; - case 't': - args->tuple_base_threshold = atoi(optarg); - break; - case 'T': - args->tuple_scaling_factor = atof(optarg); - break; - case 'd': - args->debug = atoi(optarg); - break; - case 'U': - args->user=optarg; - break; - case 'P': - args->password=optarg; - break; - case 'H': - args->host=optarg; - break; - case 'p': - args->port=optarg; - break; - case 'h': - default: - fprintf(stderr, "usage: pg_autovacuum [-d debug][-s sleep base value][-S sleep scaling factor]\n[-t tuple base threshold][-T tulple scaling factor]\n[-U username][-P password][-H host][-p port][-h help]\n"); - exit(1); - break; - } + args = (cmd_args *) malloc (sizeof (cmd_args)); + args->sleep_base_value = SLEEPBASEVALUE; + args->sleep_scaling_factor = SLEEPSCALINGFACTOR; + args->vacuum_base_threshold = VACBASETHRESHOLD; + args->vacuum_scaling_factor = VACSCALINGFACTOR; + args->analyze_base_threshold = -1; + args->analyze_scaling_factor = -1; + args->debug = AUTOVACUUM_DEBUG; + args->daemonize = 0; + + /* Fixme: Should add some sanity checking such as positive integer values etc */ + while (-1 != (c = getopt (argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hD"))) { + switch (c) { + case 's': + args->sleep_base_value = atoi (optarg); + break; + case 'S': + args->sleep_scaling_factor = atof (optarg); + break; + case 'v': + args->vacuum_base_threshold = atoi (optarg); + break; + case 'V': + args->vacuum_scaling_factor = atof (optarg); + break; + case 'a': + args->analyze_base_threshold = atoi (optarg); + break; + case 'A': + args->analyze_scaling_factor = atof (optarg); + break; + case 'D': + args->daemonize++; + break; + case 'd': + args->debug = atoi (optarg); + break; + case 'U': + args->user = optarg; + break; + case 'P': + args->password = optarg; + break; + case 'H': + args->host = optarg; + break; + case 'L': + args->logfile = optarg; + break; + case 'p': + args->port = optarg; + break; + case 'h': + usage(); + exit (0); + default: + /* It's here that we know that things are invalid... + It is not forcibly an error to call usage */ + fprintf (stderr, "Error: Invalid Command Line Options.\n"); + usage(); + exit (1); + break; + } + /* if values for insert thresholds are not specified, + then they default to 1/2 of the delete values */ + if(-1 == args->analyze_base_threshold) + args->analyze_base_threshold = args->vacuum_base_threshold / 2; + if(-1 == args->analyze_scaling_factor) + args->analyze_scaling_factor = args->vacuum_scaling_factor / 2; } - return args; } -void print_cmd_args() +void usage() +{ + int i=0; + float f=0; + fprintf (stderr, "usage: pg_autovacuum \n"); + fprintf (stderr, " [-D] Daemonize (Detach from tty and run in the background)\n"); + i=AUTOVACUUM_DEBUG; + fprintf (stderr, " [-d] debug (debug level=0,1,2,3; default=%i)\n",i); + + i=SLEEPBASEVALUE; + fprintf (stderr, " [-s] sleep base value (default=%i)\n",i); + f=SLEEPSCALINGFACTOR; + fprintf (stderr, " [-S] sleep scaling factor (default=%f)\n",f); + + i=VACBASETHRESHOLD; + fprintf (stderr, " [-v] vacuum base threshold (default=%i)\n",i); + f=VACSCALINGFACTOR; + fprintf (stderr, " [-V] vacuum scaling factor (default=%f)\n",f); + i=i/2; + fprintf (stderr, " [-a] analyze base threshold (default=%i)\n",i); + f=f/2; + fprintf (stderr, " [-A] analyze scaling factor (default=%f)\n",f); + + fprintf (stderr, " [-L] logfile (default=none)\n"); + + fprintf (stderr, " [-U] username (libpq default)\n"); + fprintf (stderr, " [-P] password (libpq default)\n"); + fprintf (stderr, " [-H] host (libpq default)\n"); + fprintf (stderr, " [-p] port (libpq default)\n"); + + fprintf (stderr, " [-h] help (Show this output)\n"); +} + +void +print_cmd_args () { - printf("Printing command_args\n"); - printf(" args->host=%s\n",args->host); - printf(" args->port=%s\n",args->port); - printf(" args->user=%s\n",args->user); - printf(" args->password=%s\n",args->password); - printf(" args->sleep_base_value=%i\n",args->sleep_base_value); - printf(" args->sleep_scaling_factor=%f\n",args->sleep_scaling_factor); - printf(" args->tuple_base_threshold=%i\n",args->tuple_base_threshold); - printf(" args->tuple_scaling_factor=%f\n",args->tuple_scaling_factor); - printf(" args->debug=%i\n",args->debug); + sprintf (logbuffer, "Printing command_args"); + log_entry (logbuffer); + sprintf (logbuffer, " args->host=%s", (args->host) ? args->host : "(null)"); + log_entry (logbuffer); + sprintf (logbuffer, " args->port=%s", (args->port) ? args->port : "(null)"); + log_entry (logbuffer); + sprintf (logbuffer, " args->user=%s", (args->user) ? args->user : "(null)"); + log_entry (logbuffer); + sprintf (logbuffer, " args->password=%s",(args->password) ? args->password : "(null)"); + log_entry (logbuffer); + sprintf (logbuffer, " args->logfile=%s",(args->logfile) ? args->logfile : "(null)"); + log_entry (logbuffer); + sprintf (logbuffer, " args->daemonize=%i",args->daemonize); + log_entry (logbuffer); + + sprintf (logbuffer, " args->sleep_base_value=%i", args->sleep_base_value); + log_entry (logbuffer); + sprintf (logbuffer, " args->sleep_scaling_factor=%f",args->sleep_scaling_factor); + log_entry (logbuffer); + sprintf (logbuffer, " args->vacuum_base_threshold=%i",args->vacuum_base_threshold); + log_entry (logbuffer); + sprintf (logbuffer, " args->vacuum_scaling_factor=%f",args->vacuum_scaling_factor); + log_entry (logbuffer); + sprintf (logbuffer, " args->analyze_base_threshold=%i",args->analyze_base_threshold); + log_entry (logbuffer); + sprintf (logbuffer, " args->analyze_scaling_factor=%f",args->analyze_scaling_factor); + log_entry (logbuffer); + sprintf (logbuffer, " args->debug=%i", args->debug); + log_entry (logbuffer); + + fflush (LOGOUTPUT); } /* Beginning of AutoVacuum Main Program */ -int main(int argc, char *argv[]) +int +main (int argc, char *argv[]) { char buf[256]; - int j=0, loops=0; - int numInserts, numDeletes, sleep_secs; - Dllist *db_list; - Dlelem *db_elem,*tbl_elem; - db_info *dbs; - tbl_info *tbl; - PGresult *res; - long long diff=0; - struct timeval now,then; - - args=get_cmd_args(argc,argv); /* Get Command Line Args and put them in the args struct */ - - if(args->debug >= 2) {print_cmd_args();} - - db_list=init_db_list(); /* Init the db list with template1 */ - if(NULL == db_list) + int j = 0, loops = 0; +/* int numInserts, numDeletes, */ + int sleep_secs; + Dllist *db_list; + Dlelem *db_elem, *tbl_elem; + db_info *dbs; + tbl_info *tbl; + PGresult *res=NULL; + long long diff = 0; + struct timeval now, then; + + args = get_cmd_args (argc, argv); /* Get Command Line Args and put them in the args struct */ + + /* Dameonize if requested */ + if (1 == args->daemonize){ daemonize(); } + + if (args->logfile) { + LOGOUTPUT = fopen (args->logfile, "a"); + if (!LOGOUTPUT) { + fprintf (stderr, "Could not open log file - [%s]\n", args->logfile); + exit(-1); + } + } + else { + LOGOUTPUT = stderr; + } + if (args->debug >= 2) { + print_cmd_args (); + } + + /* Init the db list with template1 */ + db_list = init_db_list (); + if (NULL == db_list) return 1; - if(0!=check_stats_enabled(((db_info*)DLE_VAL(DLGetHead(db_list))))) - { - printf("Error: GUC variable stats_row_level must be enabled.\n Please fix the problems and try again.\n"); - exit(1); - } + if (0 != check_stats_enabled (((db_info *) DLE_VAL (DLGetHead (db_list))))) { + log_entry ("Error: GUC variable stats_row_level must be enabled."); + log_entry (" Please fix the problems and try again."); + fflush (LOGOUTPUT); + + exit (1); + } + + gettimeofday (&then, 0); /* for use later to caluculate sleep time */ + + while (1) { /* Main Loop */ + db_elem = DLGetHead (db_list); /* Reset cur_db_node to the beginning of the db_list */ - gettimeofday(&then, 0); /* for use later to caluculate sleep time */ + dbs = ((db_info *) DLE_VAL (db_elem)); /* get pointer to cur_db's db_info struct */ + if (NULL == dbs->conn) { + dbs->conn = db_connect (dbs); + if (NULL == dbs->conn) { /* Serious problem: We can't connect to template1 */ + log_entry ("Error: Cannot connect to template1, exiting."); + fflush (LOGOUTPUT); + fclose (LOGOUTPUT); + exit (1); + } + } + + if (0 == (loops % UPDATE_INTERVAL)) /* Update the list if it's time */ + update_db_list (db_list); /* Add and remove databases from the list */ + + while (NULL != db_elem) { /* Loop through databases in list */ + dbs = ((db_info *) DLE_VAL (db_elem)); /* get pointer to cur_db's db_info struct */ + if (NULL == dbs->conn) + dbs->conn = db_connect (dbs); + + if (NULL != dbs->conn) { + if (0 == (loops % UPDATE_INTERVAL)) /* Update the list if it's time */ + update_table_list (dbs); /* Add and remove tables from the list */ - while(1) /* Main Loop */ + if (0 == xid_wraparound_check (dbs)); { - db_elem=DLGetHead(db_list); /* Reset cur_db_node to the beginning of the db_list */ - - dbs=((db_info *)DLE_VAL(db_elem)); /* get pointer to cur_db's db_info struct */ - if(NULL==dbs->conn) - { - dbs->conn=db_connect(dbs); - if(NULL==dbs->conn) /* Serious problem: We can't connect to template1 */ - { - printf("Error: Cannot connect to template1, exiting.\n"); - exit(1); - } - } - - if(0==(loops % UPDATE_INTERVAL)) /* Update the list if it's time */ - update_db_list(db_list); /* Add new databases to the list to be checked, and remove databases that no longer exist */ - - while(NULL != db_elem) /* Loop through databases in list */ - { - dbs=((db_info *)DLE_VAL(db_elem)); /* get pointer to cur_db's db_info struct */ - if(NULL==dbs->conn) - dbs->conn=db_connect(dbs); - - if(NULL!=dbs->conn) - { - if(0==(loops % UPDATE_INTERVAL)) /* Update the list if it's time */ - update_table_list(dbs); /* Add new databases to the list to be checked, and remove databases that no longer exist */ - - if(0==xid_wraparound_check(dbs)); - { - res=send_query(query_table_stats(dbs),dbs); /* Get an updated snapshot of this dbs table stats */ - for(j=0;j < PQntuples(res);j++) /* loop through result set */ - { - tbl_elem = DLGetHead(dbs->table_list); /* Reset tbl_elem to top of dbs->table_list */ - while(NULL!=tbl_elem) /* Loop through tables in list */ - { - tbl=((tbl_info *)DLE_VAL(tbl_elem)); /* set tbl_info = current_table */ - if(tbl->relfilenode == atoi(PQgetvalue(res,j,PQfnumber(res,"relfilenode")))) + res = send_query (query_table_stats (dbs), dbs); /* Get an updated snapshot of this dbs table stats */ + for (j = 0; j < PQntuples (res); j++) { /* loop through result set */ + tbl_elem = DLGetHead (dbs->table_list); /* Reset tbl_elem to top of dbs->table_list */ + while (NULL != tbl_elem) { /* Loop through tables in list */ + tbl = ((tbl_info *) DLE_VAL (tbl_elem)); /* set tbl_info = current_table */ + if (tbl->relfilenode == atoi (PQgetvalue(res, j, PQfnumber (res, "relfilenode")))) { + tbl->curr_analyze_count = + (atol (PQgetvalue (res, j, PQfnumber (res, "n_tup_ins"))) + + atol (PQgetvalue (res, j, PQfnumber (res, "n_tup_upd"))) + + atol (PQgetvalue (res, j, PQfnumber (res, "n_tup_del")))); + tbl->curr_vacuum_count = + (atol (PQgetvalue (res, j, PQfnumber (res, "n_tup_del"))) + + atol (PQgetvalue (res, j, PQfnumber (res, "n_tup_upd")))); + + /* Check numDeletes to see if we need to vacuum, if so: + Run vacuum analyze (adding analyze is small so we might as well) + Update table thresholds and related information + if numDeletes is not big enough for vacuum then check numInserts for analyze */ + if ((tbl->curr_vacuum_count - tbl->CountAtLastVacuum) >= tbl->vacuum_threshold) + { + snprintf (buf, sizeof (buf), "vacuum analyze %s", tbl->table_name); + if (args->debug >= 1) { + sprintf (logbuffer, "Performing: %s", buf); + log_entry (logbuffer); + fflush (LOGOUTPUT); + } + send_query (buf, dbs); + update_table_thresholds (dbs, tbl, VACUUM_ANALYZE); + if (args->debug >= 2) {print_table_info (tbl);} + } + else if ((tbl->curr_analyze_count - tbl->CountAtLastAnalyze) >= tbl->analyze_threshold) { - numInserts=(atol(PQgetvalue(res,j,PQfnumber(res,"n_tup_ins"))) + atol(PQgetvalue(res,j,PQfnumber(res,"n_tup_upd")))); - numDeletes=(atol(PQgetvalue(res,j,PQfnumber(res,"n_tup_del"))) + atol(PQgetvalue(res,j,PQfnumber(res,"n_tup_upd")))); - - /* Check numDeletes to see if we need to vacuum, if so: - Run vacuum analyze (adding analyze is small so we might as well) - Update table thresholds and related information - if numDeletes is not big enough for vacuum then check numInserts for analyze */ - if((numDeletes - tbl->DeletesAtLastVacuum) >= tbl->deleteThreshold) - { - snprintf(buf,sizeof(buf),"vacuum %s",tbl->table_name); - if(args->debug >= 1) {printf("Performing: %s\n",buf);} - send_query(buf,dbs); - tbl->DeletesAtLastVacuum=numDeletes; - update_table_thresholds(dbs,tbl); - if(args->debug >= 2) {print_table_info(tbl);} - } - else if((numInserts - tbl->InsertsAtLastAnalyze) >= tbl->insertThreshold) - { - snprintf(buf,sizeof(buf),"analyze %s",tbl->table_name); - if(args->debug >= 1) {printf("Performing: %s\n",buf);} - send_query(buf,dbs); - tbl->InsertsAtLastAnalyze=numInserts; - tbl->reltuples=atoi(PQgetvalue(res,j,PQfnumber(res,"reltuples"))); - tbl->insertThreshold = (args->tuple_base_threshold + args->tuple_scaling_factor*tbl->reltuples); - if(args->debug >= 2) {print_table_info(tbl);} - } - - /* If the stats collector is reporting fewer updates then we have on record - then the stats were probably reset, so we need to reset also */ - if((numInserts < tbl->InsertsAtLastAnalyze)||(numDeletes < tbl->DeletesAtLastVacuum)) - { - tbl->InsertsAtLastAnalyze=numInserts; - tbl->DeletesAtLastVacuum=numDeletes; - } - break; /* once we have found a match, no need to keep checking. */ - } - /* Advance the table pointers for the next loop */ - tbl_elem=DLGetSucc(tbl_elem); - - } /* end for table while loop */ - } /* end for j loop (tuples in PGresult) */ - } /* close of if(xid_wraparound_check()) */ - /* Done working on this db, Clean up, then advance cur_db */ - PQclear(res); res=NULL; - db_disconnect(dbs); - } - db_elem=DLGetSucc(db_elem); /* move on to next DB regardless */ - } /* end of db_list while loop */ + snprintf (buf, sizeof (buf), "analyze %s", tbl->table_name); + if (args->debug >= 1) { + sprintf (logbuffer, "Performing: %s", buf); + log_entry (logbuffer); + fflush (LOGOUTPUT); + } + send_query (buf, dbs); + update_table_thresholds (dbs, tbl, ANALYZE_ONLY); + if (args->debug >= 2) { print_table_info (tbl); } + } + + break; /* once we have found a match, no need to keep checking. */ + } + /* Advance the table pointers for the next loop */ + tbl_elem = DLGetSucc (tbl_elem); + + } /* end for table while loop */ + } /* end for j loop (tuples in PGresult) */ + } /* close of if(xid_wraparound_check()) */ + /* Done working on this db, Clean up, then advance cur_db */ + PQclear (res); + res = NULL; + db_disconnect (dbs); + } + db_elem = DLGetSucc (db_elem); /* move on to next DB regardless */ + } /* end of db_list while loop */ /* Figure out how long to sleep etc ... */ - gettimeofday(&now, 0); + gettimeofday (&now, 0); diff = (now.tv_sec - then.tv_sec) * 1000000 + (now.tv_usec - then.tv_usec); - sleep_secs = args->sleep_base_value + args->sleep_scaling_factor*diff/1000000; + sleep_secs = args->sleep_base_value + args->sleep_scaling_factor * diff / 1000000; loops++; - if(args->debug >= 2) - { printf("%i All DBs checked in: %lld usec, will sleep for %i secs.\n",loops,diff,sleep_secs);} + if (args->debug >= 2) { + sprintf (logbuffer, + "%i All DBs checked in: %lld usec, will sleep for %i secs.", + loops, diff, sleep_secs); + log_entry (logbuffer); + } - sleep(sleep_secs); /* Larger Pause between outer loops */ + sleep (sleep_secs); /* Larger Pause between outer loops */ - gettimeofday(&then, 0); /* Reset time counter */ + gettimeofday (&then, 0); /* Reset time counter */ } /* end of while loop */ - /* program is exiting, this should never run, but is here to make compiler / valgrind happy */ - free_db_list(db_list); - free_cmd_args(); + /* program is exiting, this should never run, but is here to make compiler / valgrind happy */ + free_db_list (db_list); + free_cmd_args (); return EXIT_SUCCESS; } diff --git a/contrib/pg_autovacuum/pg_autovacuum.h b/contrib/pg_autovacuum/pg_autovacuum.h index 18a85bc3d4..63a3196536 100644 --- a/contrib/pg_autovacuum/pg_autovacuum.h +++ b/contrib/pg_autovacuum/pg_autovacuum.h @@ -2,80 +2,112 @@ * Header file for pg_autovacuum.c * (c) 2003 Matthew T. O'Connor */ + #include "postgres_fe.h" #include +#ifdef __GLIBC__ +#include +#endif #include +/* These next two lines are correct when pg_autovaccum is compiled + from within the postgresql source tree */ #include "libpq-fe.h" #include "lib/dllist.h" +/* Had to change the last two lines to compile on + Redhat outside of postgresql source tree */ +/* +#include "/usr/include/libpq-fe.h" +#include "/usr/include/pgsql/server/lib/dllist.h" +*/ #define AUTOVACUUM_DEBUG 1 -#define BASETHRESHOLD 100 -#define SCALINGFACTOR 2 -#define SLEEPVALUE 1 -#define SLEEPSCALINGFACTOR 0 +#define VACBASETHRESHOLD 1000 +#define VACSCALINGFACTOR 2 +#define SLEEPBASEVALUE 300 +#define SLEEPSCALINGFACTOR 2 #define UPDATE_INTERVAL 2 + +/* these two constants are used to tell update_table_stats what operation we just perfomred */ +#define VACUUM_ANALYZE 0 +#define ANALYZE_ONLY 1 + #define TABLE_STATS_ALL "select a.relfilenode,a.relname,a.relnamespace,a.relpages,a.reltuples,b.schemaname,b.n_tup_ins,b.n_tup_upd,b.n_tup_del from pg_class a, pg_stat_all_tables b where a.relfilenode=b.relid" #define TABLE_STATS_USER "select a.relfilenode,a.relname,a.relnamespace,a.relpages,a.reltuples,b.schemaname,b.n_tup_ins,b.n_tup_upd,b.n_tup_del from pg_class a, pg_stat_user_tables b where a.relfilenode=b.relid" #define FRONTEND +#define PAGES_QUERY "select relfilenode,reltuples,relpages from pg_class where relfilenode=%i" +#define FROZENOID_QUERY "select oid,age(datfrozenxid) from pg_database where datname = 'template1'" +#define FROZENOID_QUERY2 "select oid,datname,age(datfrozenxid) from pg_database where datname!='template0'" -struct cmdargs{ - int tuple_base_threshold,sleep_base_value,debug; - float tuple_scaling_factor,sleep_scaling_factor; - char *user, *password, *host, *port; -}; typedef struct cmdargs cmd_args; +/* define cmd_args stucture */ +struct cmdargs +{ + int vacuum_base_threshold, analyze_base_threshold, sleep_base_value, debug, daemonize; + float vacuum_scaling_factor, analyze_scaling_factor, sleep_scaling_factor; + char *user, *password, *host, *logfile, *port; +}; +typedef struct cmdargs cmd_args; /* define cmd_args as global so we can get to them everywhere */ -cmd_args *args; - -struct tableinfo{ - char *schema_name,*table_name; - int insertThreshold,deleteThreshold; - int relfilenode,reltuples,relpages; - long InsertsAtLastAnalyze; /* equal to: inserts + updates as of the last analyze or initial values at startup */ - long DeletesAtLastVacuum; /* equal to: deletes + updates as of the last vacuum or initial values at startup */ - }; typedef struct tableinfo tbl_info; +cmd_args *args; /* Might need to add a time value for last time the whold database was vacuumed. I think we need to guarantee this happens approx every 1Million TX's */ -struct dbinfo{ - int oid,age; - int insertThreshold,deleteThreshold; /* Use these as defaults for table thresholds */ - PGconn *conn; - char *dbname,*username,*password; - Dllist *table_list; - }; typedef struct dbinfo db_info; +struct dbinfo +{ + int oid, age; + int analyze_threshold, vacuum_threshold; /* Use these as defaults for table thresholds */ + PGconn *conn; + char *dbname, *username, *password; + Dllist *table_list; +}; +typedef struct dbinfo db_info; + +struct tableinfo +{ + char *schema_name, *table_name; + int relfilenode, reltuples, relpages; + long analyze_threshold, vacuum_threshold; + long CountAtLastAnalyze; /* equal to: inserts + updates as of the last analyze or initial values at startup */ + long CountAtLastVacuum; /* equal to: deletes + updates as of the last vacuum or initial values at startup */ + long curr_analyze_count, curr_vacuum_count; /* Latest values from stats system */ + db_info *dbi; /* pointer to the database that this table belongs to */ +}; +typedef struct tableinfo tbl_info; /* Functions for dealing with command line arguements */ -static cmd_args *get_cmd_args(int argc,char *argv[]); -static void print_cmd_args(void); -static void free_cmd_args(void); +static cmd_args *get_cmd_args (int argc, char *argv[]); +static void print_cmd_args (void); +static void free_cmd_args (void); +static void usage (void); /* Functions for managing database lists */ -static Dllist *init_db_list(void); -static db_info *init_dbinfo(char *dbname,int oid,int age); -static void update_db_list(Dllist *db_list); -static void remove_db_from_list(Dlelem *db_to_remove); -static void print_db_info(db_info *dbi,int print_table_list); -static void print_db_list(Dllist *db_list,int print_table_lists); -static int xid_wraparound_check(db_info *dbi); -static void free_db_list(Dllist *db_list); +static Dllist *init_db_list (void); +static db_info *init_dbinfo (char *dbname, int oid, int age); +static void update_db_list (Dllist * db_list); +static void remove_db_from_list (Dlelem * db_to_remove); +static void print_db_info (db_info * dbi, int print_table_list); +static void print_db_list (Dllist * db_list, int print_table_lists); +static int xid_wraparound_check (db_info * dbi); +static void free_db_list (Dllist * db_list); /* Functions for managing table lists */ -static tbl_info *init_table_info(PGresult *conn, int row); -static void update_table_list(db_info *dbi); -static void remove_table_from_list(Dlelem *tbl_to_remove); -static void print_table_list(Dllist *tbl_node); -static void print_table_info(tbl_info *tbl); -static void update_table_thresholds(db_info *dbi,tbl_info *tbl); -static void free_tbl_list(Dllist *tbl_list); +static tbl_info *init_table_info (PGresult * conn, int row, db_info *dbi); +static void update_table_list (db_info * dbi); +static void remove_table_from_list (Dlelem * tbl_to_remove); +static void print_table_list (Dllist * tbl_node); +static void print_table_info (tbl_info * tbl); +static void update_table_thresholds (db_info * dbi, tbl_info * tbl, int vacuum_type); +static void free_tbl_list (Dllist * tbl_list); /* A few database helper functions */ -static int check_stats_enabled(db_info *dbi); -static PGconn *db_connect(db_info *dbi); -static void db_disconnect(db_info *dbi); -static PGresult *send_query(const char *query,db_info *dbi); -static char *query_table_stats(db_info *dbi); - +static int check_stats_enabled (db_info * dbi); +static PGconn *db_connect (db_info * dbi); +static void db_disconnect (db_info * dbi); +static PGresult *send_query (const char *query, db_info * dbi); +static char *query_table_stats (db_info * dbi); +/* Other Generally needed Functions */ +static void daemonize(void); +static void log_entry (const char *logentry);