diff options
-rw-r--r-- | contrib/pg_autovacuum/README.pg_autovacuum | 180 | ||||
-rw-r--r-- | contrib/pg_autovacuum/TODO | 11 | ||||
-rw-r--r-- | contrib/pg_autovacuum/pg_autovacuum.c | 1384 | ||||
-rw-r--r-- | contrib/pg_autovacuum/pg_autovacuum.h | 130 |
4 files changed, 1049 insertions, 656 deletions
diff --git a/contrib/pg_autovacuum/README.pg_autovacuum b/contrib/pg_autovacuum/README.pg_autovacuum index 2f79ba543e6..245783ecf47 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 50e5aaaec5c..f9d383d9863 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 804436afbd8..dce065d7b6f 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;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))); - 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;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) {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 18a85bc3d44..63a31965361 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 <unistd.h> +#ifdef __GLIBC__ +#include <getopt.h> +#endif #include <sys/time.h> +/* 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); |