+++ /dev/null
-/* pg_autovacuum.c
- * All the code for the pg_autovacuum program
- * (c) 2003 Matthew T. O'Connor
- * Revisions by Christopher B. Browne, Liberty RMS
- * Win32 Service code added by Dave Page
- *
- * $PostgreSQL: pgsql/contrib/pg_autovacuum/pg_autovacuum.c,v 1.35 2005/06/15 13:55:23 momjian Exp $
- */
-
-#include "postgres_fe.h"
-
-#include <unistd.h>
-#ifdef HAVE_GETOPT_H
-#include <getopt.h>
-#endif
-#include <time.h>
-#include <sys/time.h>
-#ifdef WIN32
-#include <windows.h>
-#endif
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include "pg_autovacuum.h"
-
-#ifdef WIN32
-SERVICE_STATUS ServiceStatus;
-SERVICE_STATUS_HANDLE hStatus;
-int appMode = 0;
-char deps[255];
-#endif
-
-/* define atooid */
-#define atooid(x) ((Oid) strtoul((x), NULL, 10))
-
-
-static cmd_args *args;
-static FILE *LOGOUTPUT;
-static char logbuffer[4096];
-
-
-/* The main program loop function */
-static int VacuumLoop(int argc, char **argv);
-
-/* 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 void usage(void);
-
-/* Functions for managing database lists */
-static Dllist *init_db_list(void);
-static db_info *init_dbinfo(char *dbname, Oid oid, long 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, 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);
-
-/* Other Generally needed Functions */
-#ifndef WIN32
-static void daemonize(void);
-#endif
-static void log_entry(const char *logentry, int level);
-
-#ifdef WIN32
-/* Windows Service related functions */
-static void ControlHandler(DWORD request);
-static int InstallService();
-static int RemoveService();
-#endif
-
-
-static void
-log_entry(const char *logentry, int level)
-{
- /*
- * Note: Under Windows we dump the log entries to the normal
- * stderr/logfile as well, otherwise it can be a pain to debug
- * service install failures etc.
- */
-
- time_t curtime;
- struct tm *loctime;
- char timebuffer[128],
- slevel[10];
-
-
-#ifdef WIN32
- static HANDLE evtHandle = INVALID_HANDLE_VALUE;
- static int last_level;
- WORD elevel;
-#endif
-
- switch (level)
- {
- case LVL_DEBUG:
- sprintf(slevel, "DEBUG: ");
- break;
-
- case LVL_INFO:
- sprintf(slevel, "INFO: ");
- break;
-
- case LVL_WARNING:
- sprintf(slevel, "WARNING: ");
- break;
-
- case LVL_ERROR:
- sprintf(slevel, "ERROR: ");
- break;
-
- case LVL_EXTRA:
- sprintf(slevel, " ");
- break;
-
- default:
- sprintf(slevel, " ");
- break;
- }
-
- curtime = time(NULL);
- loctime = localtime(&curtime);
- strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S %Z", loctime);
- fprintf(LOGOUTPUT, "[%s] %s%s\n", timebuffer, slevel, logentry);
-
-#ifdef WIN32
-
- /* Restore the previous level if this is extra info */
- if (level == LVL_EXTRA)
- level = last_level;
- last_level = level;
-
- switch (level)
- {
- case LVL_DEBUG:
- elevel = EVENTLOG_INFORMATION_TYPE;
- break;
-
- case LVL_INFO:
- elevel = EVENTLOG_SUCCESS;
- break;
-
- case LVL_WARNING:
- elevel = EVENTLOG_WARNING_TYPE;
- break;
-
- case LVL_ERROR:
- elevel = EVENTLOG_ERROR_TYPE;
- break;
-
- default:
- elevel = EVENTLOG_SUCCESS;
- break;
- }
-
- if (evtHandle == INVALID_HANDLE_VALUE)
- {
- evtHandle = RegisterEventSource(NULL, "PostgreSQL Auto Vacuum");
- if (evtHandle == NULL)
- {
- evtHandle = INVALID_HANDLE_VALUE;
- return;
- }
- }
-
- ReportEvent(evtHandle, elevel, 0, 0, NULL, 1, 0, &logentry, NULL);
-#endif
-}
-
-/*
- * Function used to detach the pg_autovacuum daemon from the tty and go into
- * the background.
- *
- * This code is ripped directly from pmdaemonize in postmaster.c.
- */
-#ifndef WIN32
-static void
-daemonize(void)
-{
- int i;
- pid_t pid;
-
- pid = fork();
- if (pid == (pid_t) -1)
- {
- log_entry("cannot disassociate from controlling TTY", LVL_ERROR);
- fflush(LOGOUTPUT);
- _exit(1);
- }
- else if (pid)
- { /* parent */
- /* Parent should just exit, without doing any atexit cleanup */
- _exit(0);
- }
-
-/* 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)
- {
- log_entry("cannot disassociate from controlling TTY", LVL_ERROR);
- fflush(LOGOUTPUT);
- _exit(1);
- }
-#endif
- i = open(NULL_DEV, O_RDWR);
- dup2(i, 0);
- dup2(i, 1);
- dup2(i, 2);
- close(i);
-}
-#endif /* WIN32 */
-
-/* Create and return tbl_info struct with initialized to values from row or res */
-static 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", LVL_ERROR);
- fflush(LOGOUTPUT);
- return NULL;
- }
-
- if (res == NULL)
- return NULL;
-
- new_tbl->dbi = dbi; /* set pointer to db */
-
- 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", LVL_ERROR);
- 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) + 6);
- if (!new_tbl->table_name)
- {
- log_entry("init_table_info: malloc failed on new_tbl->table_name", LVL_ERROR);
- fflush(LOGOUTPUT);
- return NULL;
- }
-
- /*
- * Put both the schema and table name in quotes so that we can work
- * with mixed case table names
- */
- strcpy(new_tbl->table_name, "\"");
- strcat(new_tbl->table_name, new_tbl->schema_name);
- strcat(new_tbl->table_name, "\".\"");
- strcat(new_tbl->table_name, PQgetvalue(res, row, PQfnumber(res, "relname")));
- strcat(new_tbl->table_name, "\"");
-
- new_tbl->CountAtLastAnalyze =
- (atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_ins"))) +
- atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_upd"))) +
- atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_del"))));
- 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->relid = atooid(PQgetvalue(res, row, PQfnumber(res, "oid")));
- new_tbl->reltuples = atof(PQgetvalue(res, row, PQfnumber(res, "reltuples")));
- new_tbl->relpages = atooid(PQgetvalue(res, row, PQfnumber(res, "relpages")));
-
- if (strcmp("t", PQgetvalue(res, row, PQfnumber(res, "relisshared"))))
- new_tbl->relisshared = 0;
- else
- new_tbl->relisshared = 1;
-
- 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);
-
- return new_tbl;
-}
-
-/* Set thresholds = base_value + scaling_factor * reltuples
- Should be called after a vacuum since vacuum updates values in pg_class */
-static void
-update_table_thresholds(db_info * dbi, tbl_info * tbl, int vacuum_type)
-{
- PGresult *res = NULL;
- int disconnect = 0;
- char query[128];
-
- if (dbi->conn == NULL)
- {
- dbi->conn = db_connect(dbi);
- disconnect = 1;
- }
-
- if (dbi->conn != NULL)
- {
- snprintf(query, sizeof(query), PAGES_QUERY, tbl->relid);
- res = send_query(query, dbi);
- if (res != NULL)
- {
- tbl->reltuples =
- atof(PQgetvalue(res, 0, PQfnumber(res, "reltuples")));
- tbl->relpages = atooid(PQgetvalue(res, 0, PQfnumber(res, "relpages")));
-
- /*
- * update vacuum thresholds only of we just did a vacuum
- * analyze
- */
- if (vacuum_type == VACUUM_ANALYZE)
- {
- 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);
-}
-
-static 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 (dbi->conn == NULL)
- {
- dbi->conn = db_connect(dbi);
- disconnect = 1;
- }
-
- if (dbi->conn != NULL)
- {
- /*
- * Get a result set that has all the information 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((char *) TABLE_STATS_QUERY, dbi);
- if (res != NULL)
- {
- 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 (tbl_elem != NULL)
- {
- 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->relid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
- {
- found_match = 1;
- break;
- }
- }
- if (found_match == 0)
- { /* 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 (tbl_elem != NULL)
- {
- tbl = ((tbl_info *) DLE_VAL(tbl_elem));
- if (tbl->relid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
- {
- found_match = 1;
- break;
- }
- tbl_elem = DLGetSucc(tbl_elem);
- }
- if (found_match == 0) /* 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, LVL_DEBUG);
- }
- }
- } /* 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 */
-static void
-remove_table_from_list(Dlelem *tbl_to_remove)
-{
- tbl_info *tbl = ((tbl_info *) DLE_VAL(tbl_to_remove));
-
- if (args->debug >= 1)
- {
- sprintf(logbuffer, "Removing table: %s.%s from list.", tbl->dbi->dbname, tbl->table_name);
- log_entry(logbuffer, LVL_DEBUG);
- 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 */
-static void
-free_tbl_list(Dllist *tbl_list)
-{
- Dlelem *tbl_elem = DLGetHead(tbl_list);
- Dlelem *tbl_elem_to_remove = NULL;
-
- while (tbl_elem != NULL)
- {
- tbl_elem_to_remove = tbl_elem;
- tbl_elem = DLGetSucc(tbl_elem);
- remove_table_from_list(tbl_elem_to_remove);
- }
- DLFreeList(tbl_list);
-}
-
-static void
-print_table_list(Dllist *table_list)
-{
- Dlelem *table_elem = DLGetHead(table_list);
-
- while (table_elem != NULL)
- {
- print_table_info(((tbl_info *) DLE_VAL(table_elem)));
- table_elem = DLGetSucc(table_elem);
- }
-}
-
-static void
-print_table_info(tbl_info * tbl)
-{
- sprintf(logbuffer, " table name: %s.%s", tbl->dbi->dbname, tbl->table_name);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " relid: %u; relisshared: %d", tbl->relid, tbl->relisshared);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " reltuples: %f; relpages: %u", tbl->reltuples, tbl->relpages);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " curr_analyze_count: %li; curr_vacuum_count: %li",
- tbl->curr_analyze_count, tbl->curr_vacuum_count);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " last_analyze_count: %li; last_vacuum_count: %li",
- tbl->CountAtLastAnalyze, tbl->CountAtLastVacuum);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " analyze_threshold: %li; vacuum_threshold: %li",
- tbl->analyze_threshold, tbl->vacuum_threshold);
- log_entry(logbuffer, LVL_INFO);
- fflush(LOGOUTPUT);
-}
-
-/* End of table Management Functions */
-
-/* Beginning of DB Management Functions */
-
-/* init_db_list() creates the db_list and initalizes template1 */
-static Dllist *
-init_db_list(void)
-{
- Dllist *db_list = DLNewList();
- db_info *dbs = NULL;
- PGresult *res = NULL;
-#ifdef WIN32
- int k = 0;
-#endif
-
- DLAddHead(db_list, DLNewElem(init_dbinfo((char *) "template1", 0, 0)));
- if (DLGetHead(db_list) == NULL)
- { /* Make sure init_dbinfo was successful */
- log_entry("init_db_list(): Error creating db_list for db: template1.", LVL_ERROR);
- 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);
-
-#ifdef WIN32
- while (dbs->conn == NULL && !appMode && k < 10)
- {
- int j;
-
- /* Pause for 30 seconds to allow the database to start up */
- log_entry("Pausing 30 seconds to allow the database to startup completely", LVL_INFO);
- fflush(LOGOUTPUT);
- ServiceStatus.dwWaitHint = 10;
- for (j=0; j<6; j++)
- {
- pg_usleep(5000000);
- ServiceStatus.dwCheckPoint++;
- SetServiceStatus(hStatus, &ServiceStatus);
- fflush(LOGOUTPUT);
- }
-
- /* now try again */
- log_entry("Attempting to connect again.", LVL_INFO);
- dbs->conn = db_connect(dbs);
- k++;
- }
-#endif
-
- if (dbs->conn != NULL)
- {
- res = send_query(FROZENOID_QUERY, dbs);
- if (res != NULL)
- {
- dbs->oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
- dbs->age = atol(PQgetvalue(res, 0, PQfnumber(res, "age")));
- if (res)
- PQclear(res);
-
- if (args->debug >= 2)
- print_db_list(db_list, 0);
- }
- else
- return NULL;
- }
- return db_list;
-}
-
-/* Simple function to create an instance of the dbinfo struct
- Initalizes all the pointers and connects to the database */
-static db_info *
-init_dbinfo(char *dbname, Oid oid, long age)
-{
- 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 (args->user != NULL)
- {
- newdbinfo->username = (char *) malloc(strlen(args->user) + 1);
- strcpy(newdbinfo->username, args->user);
- }
- newdbinfo->password = NULL;
- if (args->password != NULL)
- {
- 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);
-
- return newdbinfo;
-}
-
-/* Function adds and removes databases from the db_list as appropriate */
-static 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)
- {
- log_entry("updating the database list", LVL_DEBUG);
- fflush(LOGOUTPUT);
- }
-
- if (dbi_template1->conn == NULL)
- {
- dbi_template1->conn = db_connect(dbi_template1);
- disconnect = 1;
- }
-
- if (dbi_template1->conn != NULL)
- {
- /*
- * 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);
- if (res != NULL)
- {
- 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 (db_elem != NULL)
- {
- 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 == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
- {
- found_match = 1;
-
- /*
- * update the dbi->age so that we ensure
- * xid_wraparound won't happen
- */
- dbi->age = atol(PQgetvalue(res, i, PQfnumber(res, "age")));
- break;
- }
- }
- if (found_match == 0)
- { /* 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 (db_elem != NULL)
- {
- dbi = ((db_info *) DLE_VAL(db_elem));
- if (dbi->oid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
- {
- found_match = 1;
- break;
- }
- db_elem = DLGetSucc(db_elem);
- }
- if (found_match == 0) /* then we didn't find this result
- * now in the tbl_list */
- {
- DLAddTail(db_list, DLNewElem(init_dbinfo
- (PQgetvalue(res, i, PQfnumber(res, "datname")),
- atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))),
- atol(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, LVL_DEBUG);
- }
- }
- } /* 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
-
-From the docs:
-
-With the standard freezing policy, the age column will start at one billion for a
-freshly-vacuumed database. When the age approaches two billion, the database must
-be vacuumed again to avoid risk of wraparound failures. Recommended practice is
-to vacuum each database at least once every half-a-billion (500 million) transactions,
-so as to provide plenty of safety margin.
-
-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
-*/
-static 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 (dbi->age > 1500000000)
- {
- PGresult *res = NULL;
-
- res = send_query("VACUUM", dbi);
- /* FIXME: Perhaps should add a check for PQ_COMMAND_OK */
- if (res != NULL)
- PQclear(res);
- return 1;
- }
- return 0;
-}
-
-/* Close DB connection, free memory, and remove the node from the list */
-static void
-remove_db_from_list(Dlelem *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, LVL_DEBUG);
- 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 */
-static void
-free_db_list(Dllist *db_list)
-{
- Dlelem *db_elem = DLGetHead(db_list);
- Dlelem *db_elem_to_remove = NULL;
-
- while (db_elem != NULL)
- {
- 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);
-}
-
-static void
-print_db_list(Dllist *db_list, int print_table_lists)
-{
- Dlelem *db_elem = DLGetHead(db_list);
-
- while (db_elem != NULL)
- {
- print_db_info(((db_info *) DLE_VAL(db_elem)), print_table_lists);
- db_elem = DLGetSucc(db_elem);
- }
-}
-
-static void
-print_db_info(db_info * dbi, int print_tbl_list)
-{
- sprintf(logbuffer, "dbname: %s", (dbi->dbname) ? dbi->dbname : "(null)");
- log_entry(logbuffer, LVL_INFO);
-
- sprintf(logbuffer, " oid: %u", dbi->oid);
- log_entry(logbuffer, LVL_INFO);
-
- sprintf(logbuffer, " username: %s", (dbi->username) ? dbi->username : "(null)");
- log_entry(logbuffer, LVL_INFO);
-
- sprintf(logbuffer, " password: %s", (dbi->password) ? dbi->password : "(null)");
- log_entry(logbuffer, LVL_INFO);
-
- if (dbi->conn != NULL)
- log_entry(" conn is valid, (connected)", LVL_INFO);
- else
- log_entry(" conn is null, (not connected)", LVL_INFO);
-
- sprintf(logbuffer, " default_analyze_threshold: %li", dbi->analyze_threshold);
- log_entry(logbuffer, LVL_INFO);
-
- sprintf(logbuffer, " default_vacuum_threshold: %li", dbi->vacuum_threshold);
- log_entry(logbuffer, LVL_INFO);
-
- fflush(LOGOUTPUT);
- if (print_tbl_list > 0)
- print_table_list(dbi->table_list);
-}
-
-/* End of DB List Management Function */
-
-/* Beginning of misc Functions */
-
-/* Perhaps add some test to this function to make sure that the stats we need are available */
-static PGconn *
-db_connect(db_info * dbi)
-{
- PGconn *db_conn =
- PQsetdbLogin(args->host, args->port, NULL, NULL, dbi->dbname,
- dbi->username, dbi->password);
-
- if (PQstatus(db_conn) != CONNECTION_OK)
- {
- sprintf(logbuffer, "Failed connection to database %s with error: %s.",
- dbi->dbname, PQerrorMessage(db_conn));
- log_entry(logbuffer, LVL_ERROR);
- fflush(LOGOUTPUT);
- PQfinish(db_conn);
- db_conn = NULL;
- }
-
- return db_conn;
-} /* end of db_connect() */
-
-static void
-db_disconnect(db_info * dbi)
-{
- if (dbi->conn != NULL)
- {
- PQfinish(dbi->conn);
- dbi->conn = NULL;
- }
-}
-
-static int
-check_stats_enabled(db_info * dbi)
-{
- PGresult *res;
- int ret = 0;
-
- res = send_query("SHOW stats_row_level", dbi);
- if (res != NULL)
- {
- ret = strcmp("on", PQgetvalue(res, 0, PQfnumber(res, "stats_row_level")));
- PQclear(res);
- }
- return ret;
-}
-
-static PGresult *
-send_query(const char *query, db_info * dbi)
-{
- PGresult *res;
-
- if (dbi->conn == NULL)
- return NULL;
-
- if (args->debug >= 4)
- log_entry(query, LVL_DEBUG);
-
- res = PQexec(dbi->conn, query);
-
- if (!res)
- {
- sprintf(logbuffer,
- "Fatal error occured while sending query (%s) to database %s",
- query, dbi->dbname);
- log_entry(logbuffer, LVL_ERROR);
- sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res));
- log_entry(logbuffer, LVL_EXTRA);
- fflush(LOGOUTPUT);
- return NULL;
- }
- 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, LVL_ERROR);
- sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res));
- log_entry(logbuffer, LVL_EXTRA);
- fflush(LOGOUTPUT);
- PQclear(res);
- return NULL;
- }
- return res;
-} /* End of send_query() */
-
-/*
- * Perform either a vacuum or a vacuum analyze
- */
-static void
-perform_maintenance_command(db_info * dbi, tbl_info * tbl, int operation)
-{
- char buf[256];
-
- /*
- * Set the vacuum_cost variables if supplied on command line
- */
- if (args->av_vacuum_cost_delay != -1)
- {
- snprintf(buf, sizeof(buf), "set vacuum_cost_delay = %d",
- args->av_vacuum_cost_delay);
- send_query(buf, dbi);
- }
- if (args->av_vacuum_cost_page_hit != -1)
- {
- snprintf(buf, sizeof(buf), "set vacuum_cost_page_hit = %d",
- args->av_vacuum_cost_page_hit);
- send_query(buf, dbi);
- }
- if (args->av_vacuum_cost_page_miss != -1)
- {
- snprintf(buf, sizeof(buf), "set vacuum_cost_page_miss = %d",
- args->av_vacuum_cost_page_miss);
- send_query(buf, dbi);
- }
- if (args->av_vacuum_cost_page_dirty != -1)
- {
- snprintf(buf, sizeof(buf), "set vacuum_cost_page_dirty = %d",
- args->av_vacuum_cost_page_dirty);
- send_query(buf, dbi);
- }
- if (args->av_vacuum_cost_limit != -1)
- {
- snprintf(buf, sizeof(buf), "set vacuum_cost_limit = %d",
- args->av_vacuum_cost_limit);
- send_query(buf, dbi);
- }
-
- /*
- * if ((relisshared = t and database != template1) or
- * if operation = ANALYZE_ONLY)
- * then only do an analyze
- */
- if ((tbl->relisshared > 0 && strcmp("template1", dbi->dbname) != 0) ||
- (operation == ANALYZE_ONLY))
- snprintf(buf, sizeof(buf), "ANALYZE %s.%s", dbi->dbname, tbl->table_name);
- else if (operation == VACUUM_ANALYZE)
- snprintf(buf, sizeof(buf), "VACUUM ANALYZE %s.%s", dbi->dbname, tbl->table_name);
- else
- return;
-
- if (args->debug >= 1)
- {
- sprintf(logbuffer, "Performing: %s on database %s", buf, dbi->dbname);
- log_entry(logbuffer, LVL_DEBUG);
- fflush(LOGOUTPUT);
- }
-
- send_query(buf, dbi);
-
- update_table_thresholds(dbi, tbl, operation);
-
- if (args->debug >= 2)
- print_table_info(tbl);
-}
-
-static void
-free_cmd_args(void)
-{
- if (args != NULL)
- {
- if (args->user != NULL)
- free(args->user);
- if (args->password != NULL)
- free(args->password);
- free(args);
- }
-}
-
-static cmd_args *
-get_cmd_args(int argc, char *argv[])
-{
- int c;
-
- 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->update_interval = UPDATE_INTERVAL;
-#ifndef WIN32
- args->daemonize = 0;
-#else
- args->service_dependencies = 0;
- args->install_as_service = 0;
- args->remove_as_service = 0;
- args->service_user = 0;
- args->service_password = 0;
-#endif
- args->user = 0;
- args->password = 0;
- args->host = 0;
- args->logfile = 0;
- args->port = 0;
-
- /*
- * Cost-Based Vacuum Delay Settings for pg_autovacuum
- */
- args->av_vacuum_cost_delay = -1;
- args->av_vacuum_cost_page_hit = -1;
- args->av_vacuum_cost_page_miss = -1;
- args->av_vacuum_cost_page_dirty = -1;
- args->av_vacuum_cost_limit = -1;
-
- /*
- * Fixme: Should add some sanity checking such as positive integer
- * values etc
- */
-#ifndef WIN32
- while ((c = getopt(argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hDc:C:m:n:l:")) != -1)
-#else
- while ((c = getopt(argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hIRN:W:E:c:C:m:n:l:")) != -1)
-#endif
- {
- 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 'i':
- args->update_interval = atoi(optarg);
- break;
- case 'c':
- args->av_vacuum_cost_delay = atoi(optarg);
- break;
- case 'C':
- args->av_vacuum_cost_page_hit = atoi(optarg);
- break;
- case 'm':
- args->av_vacuum_cost_page_miss = atoi(optarg);
- break;
- case 'n':
- args->av_vacuum_cost_page_dirty = atoi(optarg);
- break;
- case 'l':
- args->av_vacuum_cost_limit = atoi(optarg);
- break;
-#ifndef WIN32
- case 'D':
- args->daemonize++;
- break;
-#endif
- 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);
-#ifdef WIN32
- case 'E':
- /*
- * CreateService() expects a list of service
- * dependencies as a NUL-separated, double-NUL
- * terminated list (although we only allow the user to
- * specify a single dependency). So we zero out the
- * list first, and make sure to leave room for two NUL
- * terminators.
- */
- ZeroMemory(deps, sizeof(deps));
- snprintf(deps, sizeof(deps) - 2, "%s", optarg);
- args->service_dependencies = deps;
- break;
- case 'I':
- args->install_as_service++;
- break;
- case 'R':
- args->remove_as_service++;
- break;
- case 'N':
- args->service_user = optarg;
- break;
- case 'W':
- args->service_password = optarg;
- break;
-#endif
- 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 (args->analyze_base_threshold == -1)
- args->analyze_base_threshold = args->vacuum_base_threshold / 2;
- if (args->analyze_scaling_factor == -1)
- args->analyze_scaling_factor = args->vacuum_scaling_factor / 2;
- }
- return args;
-}
-
-static void
-usage(void)
-{
- int i = 0;
- float f = 0;
-
- fprintf(stderr, "usage: pg_autovacuum \n");
-#ifndef WIN32
- fprintf(stderr, " [-D] Daemonize (Detach from tty and run in the background)\n");
-#else
- fprintf(stderr, " [-I] Install as a Windows service\n");
- fprintf(stderr, " [-R] Remove as a Windows service (all other options will be ignored)\n");
- fprintf(stderr, " [-N] Username to run service as (only useful when installing as a Windows service)\n");
- fprintf(stderr, " [-W] Password to run service with (only useful when installing as a Windows service)\n");
- fprintf(stderr, " [-E] Dependent service that must start before this service (only useful when installing as a Windows service)\n");
-#endif
- i = AUTOVACUUM_DEBUG;
- fprintf(stderr, " [-d] debug (debug level=0,1,2,3; default=%d)\n", i);
-
- i = SLEEPBASEVALUE;
- fprintf(stderr, " [-s] sleep base value (default=%d)\n", i);
- f = SLEEPSCALINGFACTOR;
- fprintf(stderr, " [-S] sleep scaling factor (default=%f)\n", f);
-
- i = VACBASETHRESHOLD;
- fprintf(stderr, " [-v] vacuum base threshold (default=%d)\n", i);
- f = VACSCALINGFACTOR;
- fprintf(stderr, " [-V] vacuum scaling factor (default=%f)\n", f);
- i = i / 2;
- fprintf(stderr, " [-a] analyze base threshold (default=%d)\n", i);
- f = f / 2;
- fprintf(stderr, " [-A] analyze scaling factor (default=%f)\n", f);
-
- fprintf(stderr, " [-L] logfile (default=none)\n");
-
- fprintf(stderr, " [-c] vacuum_cost_delay (default=none)\n");
- fprintf(stderr, " [-C] vacuum_cost_page_hit (default=none)\n");
- fprintf(stderr, " [-m] vacuum_cost_page_miss (default=none)\n");
- fprintf(stderr, " [-n] vacuum_cost_page_dirty (default=none)\n");
- fprintf(stderr, " [-l] vacuum_cost_limit (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");
-}
-
-static void
-print_cmd_args(void)
-{
- sprintf(logbuffer, "Printing command_args");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->host=%s", (args->host) ? args->host : "(null)");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->port=%s", (args->port) ? args->port : "(null)");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->username=%s", (args->user) ? args->user : "(null)");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->password=%s", (args->password) ? args->password : "(null)");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->logfile=%s", (args->logfile) ? args->logfile : "(null)");
- log_entry(logbuffer, LVL_INFO);
-#ifndef WIN32
- sprintf(logbuffer, " args->daemonize=%d", args->daemonize);
- log_entry(logbuffer, LVL_INFO);
-#else
- sprintf(logbuffer, " args->install_as_service=%d", args->install_as_service);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->remove_as_service=%d", args->remove_as_service);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->service_dependencies=%s", (args->service_dependencies) ? args->service_dependencies : "(null)");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->service_user=%s", (args->service_user) ? args->service_user : "(null)");
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->service_password=%s", (args->service_password) ? args->service_password : "(null)");
- log_entry(logbuffer, LVL_INFO);
-#endif
-
- sprintf(logbuffer, " args->sleep_base_value=%d", args->sleep_base_value);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->sleep_scaling_factor=%f", args->sleep_scaling_factor);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->vacuum_base_threshold=%d", args->vacuum_base_threshold);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->vacuum_scaling_factor=%f", args->vacuum_scaling_factor);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->analyze_base_threshold=%d", args->analyze_base_threshold);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->analyze_scaling_factor=%f", args->analyze_scaling_factor);
- log_entry(logbuffer, LVL_INFO);
- sprintf(logbuffer, " args->update_interval=%i", args->update_interval);
- log_entry(logbuffer, LVL_INFO);
-
- if (args->av_vacuum_cost_delay != -1)
- sprintf(logbuffer, " args->av_vacuum_cost_delay=%d", args->av_vacuum_cost_delay);
- else
- sprintf(logbuffer, " args->av_vacuum_cost_delay=(default)");
- log_entry(logbuffer, LVL_INFO);
- if (args->av_vacuum_cost_page_hit != -1)
- sprintf(logbuffer, " args->av_vacuum_cost_page_hit=%d", args->av_vacuum_cost_page_hit);
- else
- sprintf(logbuffer, " args->av_vacuum_cost_page_hit=(default)");
- log_entry(logbuffer, LVL_INFO);
- if (args->av_vacuum_cost_page_miss != -1)
- sprintf(logbuffer, " args->av_vacuum_cost_page_miss=%d", args->av_vacuum_cost_page_miss);
- else
- sprintf(logbuffer, " args->av_vacuum_cost_page_miss=(default)");
- log_entry(logbuffer, LVL_INFO);
- if (args->av_vacuum_cost_page_dirty != -1)
- sprintf(logbuffer, " args->av_vacuum_cost_page_dirty=%d", args->av_vacuum_cost_page_dirty);
- else
- sprintf(logbuffer, " args->av_vacuum_cost_page_dirty=(default)");
- log_entry(logbuffer, LVL_INFO);
- if (args->av_vacuum_cost_limit != -1)
- sprintf(logbuffer, " args->av_vacuum_cost_limit=%d", args->av_vacuum_cost_limit);
- else
- sprintf(logbuffer, " args->av_vacuum_cost_limit=(default)");
- log_entry(logbuffer, LVL_INFO);
-
- sprintf(logbuffer, " args->debug=%d", args->debug);
- log_entry(logbuffer, LVL_INFO);
-
- fflush(LOGOUTPUT);
-}
-
-#ifdef WIN32
-
-/* Handle control requests from the Service Control Manager */
-static void
-ControlHandler(DWORD request)
-{
- switch (request)
- {
- case SERVICE_CONTROL_STOP:
- case SERVICE_CONTROL_SHUTDOWN:
- log_entry("pg_autovacuum service stopping...", LVL_INFO);
- fflush(LOGOUTPUT);
- ServiceStatus.dwWin32ExitCode = 0;
- ServiceStatus.dwCurrentState = SERVICE_STOPPED;
- SetServiceStatus(hStatus, &ServiceStatus);
- return;
-
- default:
- break;
- }
-
- /* Report current status */
- SetServiceStatus(hStatus, &ServiceStatus);
-
- return;
-}
-
-/* Register with the Service Control Manager */
-static int
-InstallService(void)
-{
- SC_HANDLE schService = NULL;
- SC_HANDLE schSCManager = NULL;
- char szFilename[MAX_PATH],
- szKey[MAX_PATH],
- szCommand[MAX_PATH + 1024],
- szMsgDLL[MAX_PATH];
- HKEY hk = NULL;
- DWORD dwData = 0;
-
- /*
- * Register the service with the SCM
- */
- GetModuleFileName(NULL, szFilename, MAX_PATH);
-
- /* Open the Service Control Manager on the local computer. */
- schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if (!schSCManager)
- return -1;
-
- schService = CreateService(
- schSCManager, /* SCManager database */
- TEXT("pg_autovacuum"), /* Name of service */
- TEXT("PostgreSQL Auto Vacuum"), /* Name to display */
- SERVICE_ALL_ACCESS, /* Desired access */
- SERVICE_WIN32_OWN_PROCESS, /* Service type */
- SERVICE_AUTO_START, /* Start type */
- SERVICE_ERROR_NORMAL, /* Error control type */
- szFilename, /* Service binary */
- NULL, /* No load ordering group */
- NULL, /* No tag identifier */
- args->service_dependencies, /* Dependencies */
- args->service_user, /* Service account */
- args->service_password); /* Account password */
-
- if (!schService)
- return -2;
-
- /*
- * Rewrite the command line for the service
- */
- sprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\pg_autovacuum");
- if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_ALL_ACCESS, &hk))
- return -3;
-
- /* Build the command line */
- sprintf(szCommand, "\"%s\"", szFilename);
- if (args->host)
- sprintf(szCommand, "%s -H %s", szCommand, args->host);
- if (args->port)
- sprintf(szCommand, "%s -p %s", szCommand, args->port);
- if (args->user)
- sprintf(szCommand, "%s -U \"%s\"", szCommand, args->user);
- if (args->password)
- sprintf(szCommand, "%s -P \"%s\"", szCommand, args->password);
- if (args->logfile)
- sprintf(szCommand, "%s -L \"%s\"", szCommand, args->logfile);
- if (args->sleep_base_value != (int) SLEEPBASEVALUE)
- sprintf(szCommand, "%s -s %d", szCommand, args->sleep_base_value);
- if (args->sleep_scaling_factor != (float) SLEEPSCALINGFACTOR)
- sprintf(szCommand, "%s -S %f", szCommand, args->sleep_scaling_factor);
- if (args->vacuum_base_threshold != (int) VACBASETHRESHOLD)
- sprintf(szCommand, "%s -v %d", szCommand, args->vacuum_base_threshold);
- if (args->vacuum_scaling_factor != (float) VACSCALINGFACTOR)
- sprintf(szCommand, "%s -V %f", szCommand, args->vacuum_scaling_factor);
- if (args->analyze_base_threshold != (int) (VACBASETHRESHOLD / 2))
- sprintf(szCommand, "%s -a %d", szCommand, args->analyze_base_threshold);
- if (args->analyze_scaling_factor != (float) (VACSCALINGFACTOR / 2))
- sprintf(szCommand, "%s -A %f", szCommand, args->analyze_scaling_factor);
- if (args->debug != (int) AUTOVACUUM_DEBUG)
- sprintf(szCommand, "%s -d %d", szCommand, args->debug);
- if (args->av_vacuum_cost_delay != -1)
- sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_delay);
- if (args->av_vacuum_cost_page_hit != -1)
- sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_page_hit);
- if (args->av_vacuum_cost_page_miss != -1)
- sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_page_miss);
- if (args->av_vacuum_cost_page_dirty != -1)
- sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_page_dirty);
- if (args->av_vacuum_cost_limit != -1)
- sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_limit);
-
- /* And write the new value */
- if (RegSetValueEx(hk, "ImagePath", 0, REG_EXPAND_SZ, (LPBYTE) szCommand, (DWORD) strlen(szCommand) + 1))
- return -4;
- RegCloseKey(hk);
-
- /*
- * Set the Event source for the application log
- */
- sprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\PostgreSQL Auto Vacuum");
- if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, NULL))
- return -5;
-
- /* TODO Try to find pgevent.dll, rather than hope it's in the path. ! */
- /* Message DLL */
- sprintf(szMsgDLL, "pgevent.dll");
- if (RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ, (LPBYTE) szMsgDLL, (DWORD) strlen(szMsgDLL) + 1))
- return -6;
-
- /* Set the event types supported */
- dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE | EVENTLOG_SUCCESS;
- if (RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD, (LPBYTE) & dwData, sizeof(DWORD)))
- return -9;
-
- RegCloseKey(hk);
- return 0;
-}
-
-/* Unregister from the Service Control Manager */
-static int
-RemoveService(void)
-{
- SC_HANDLE schService = NULL;
- SC_HANDLE schSCManager = NULL;
- char szKey[MAX_PATH];
- HKEY hk = NULL;
-
- /* Open the SCM */
- schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if (!schSCManager)
- return -1;
-
- /* Open the service */
- schService = OpenService(schSCManager, TEXT("pg_autovacuum"), SC_MANAGER_ALL_ACCESS);
- if (!schService)
- return -2;
-
- /* Now delete the service */
- if (!DeleteService(schService))
- return -3;
-
- /*
- * Remove the Event source from the application log
- */
- sprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application");
- if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_ALL_ACCESS, &hk))
- return -4;
- if (RegDeleteKey(hk, "PostgreSQL Auto Vacuum"))
- return -5;
-
- return 0;
-}
-#endif /* WIN32 */
-
-static
-int
-VacuumLoop(int argc, char **argv)
-{
- 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;
- double diff;
-
- struct timeval now,
- then;
-
-#ifdef WIN32
-
- if (appMode)
- log_entry("pg_autovacuum starting in Windows Application mode", LVL_INFO);
- else
- log_entry("pg_autovacuum starting in Windows Service mode", LVL_INFO);
-
- ServiceStatus.dwServiceType = SERVICE_WIN32;
- ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
- ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
- ServiceStatus.dwWin32ExitCode = 0;
- ServiceStatus.dwServiceSpecificExitCode = 0;
- ServiceStatus.dwCheckPoint = 0;
- ServiceStatus.dwWaitHint = 0;
-
- if (!appMode)
- {
- hStatus = RegisterServiceCtrlHandler("pg_autovacuum", (LPHANDLER_FUNCTION) ControlHandler);
- if (hStatus == (SERVICE_STATUS_HANDLE) 0)
- return -1;
- }
-#endif /* WIN32 */
-
- /* Init the db list with template1 */
- db_list = init_db_list();
- if (db_list == NULL)
- return 1;
-
- if (check_stats_enabled(((db_info *) DLE_VAL(DLGetHead(db_list)))) != 0)
- {
- log_entry("GUC variable stats_row_level must be enabled.", LVL_ERROR);
- log_entry(" Please fix the problems and try again.", LVL_EXTRA);
- fflush(LOGOUTPUT);
-
- exit(1);
- }
-
- gettimeofday(&then, 0); /* for use later to caluculate sleep time */
-
-#ifndef WIN32
- while (1)
-#else
- /* We can now report the running status to SCM. */
- ServiceStatus.dwCurrentState = SERVICE_RUNNING;
- if (!appMode)
- SetServiceStatus(hStatus, &ServiceStatus);
-
- while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
-#endif
- {
- /* Main Loop */
-
- 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 (dbs->conn == NULL)
- {
- dbs->conn = db_connect(dbs);
- if (dbs->conn == NULL)
- { /* Serious problem: We can't connect to
- * template1 */
- log_entry("Cannot connect to template1, exiting.", LVL_ERROR);
- fflush(LOGOUTPUT);
- fclose(LOGOUTPUT);
-#ifdef WIN32
- ServiceStatus.dwCurrentState = SERVICE_STOPPED;
- ServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
- ServiceStatus.dwServiceSpecificExitCode = -1;
- if (!appMode)
- SetServiceStatus(hStatus, &ServiceStatus);
-#endif
- exit(1);
- }
- }
-
- if (loops % args->update_interval == 0) /* Update the list if it's
- * time */
- update_db_list(db_list); /* Add and remove databases from
- * the list */
-
- while (db_elem != NULL)
- { /* Loop through databases in list */
- dbs = ((db_info *) DLE_VAL(db_elem)); /* get pointer to
- * cur_db's db_info
- * struct */
- if (dbs->conn == NULL)
- dbs->conn = db_connect(dbs);
-
- if (dbs->conn != NULL)
- {
- if (loops % args->update_interval == 0) /* Update the list if
- * it's time */
- update_table_list(dbs); /* Add and remove tables
- * from the list */
-
- if (xid_wraparound_check(dbs) == 0)
- {
- res = send_query(TABLE_STATS_QUERY, dbs); /* Get an updated
- * snapshot of this dbs
- * table stats */
- if (res != NULL)
- {
- 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 (tbl_elem != NULL)
- { /* Loop through tables in list */
- tbl = ((tbl_info *) DLE_VAL(tbl_elem)); /* set tbl_info =
- * current_table */
- if (tbl->relid == atooid(PQgetvalue(res, j, PQfnumber(res, "oid"))))
- {
- 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)
- perform_maintenance_command(dbs, tbl, VACUUM_ANALYZE);
- else if (tbl->curr_analyze_count - tbl->CountAtLastAnalyze >= tbl->analyze_threshold)
- perform_maintenance_command(dbs, tbl, ANALYZE_ONLY);
-
- break; /* We found a match, no need to keep looping. */
- }
-
- /*
- * 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) */
- } /* end if (res != NULL) */
- } /* 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);
- diff = (int) (now.tv_sec - then.tv_sec) * 1000000.0 + (int) (now.tv_usec - then.tv_usec);
-
- sleep_secs = args->sleep_base_value + args->sleep_scaling_factor * diff / 1000000.0;
- loops++;
- if (args->debug >= 2)
- {
- sprintf(logbuffer,
- "%d All DBs checked in: %.0f usec, will sleep for %d secs.",
- loops, diff, sleep_secs);
- log_entry(logbuffer, LVL_DEBUG);
- fflush(LOGOUTPUT);
- }
-
- /* Larger Pause between outer loops */
- /*
- * pg_usleep() is wrong here because its maximum is ~2000 seconds,
- * and we don't need signal interruptability on Win32 here.
- */
-#ifndef WIN32
- sleep(sleep_secs); /* Unix sleep is seconds */
-#else
- sleep(sleep_secs * 1000); /* Win32 sleep() is milliseconds */
-#endif
-
- 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();
- return 0;
-}
-
-/* Beginning of AutoVacuum Main Program */
-int
-main(int argc, char *argv[])
-{
-
-#ifdef WIN32
- LPVOID lpMsgBuf;
- SERVICE_TABLE_ENTRY ServiceTable[2];
-#endif
-
- args = get_cmd_args(argc, argv); /* Get Command Line Args and put
- * them in the args struct */
-#ifndef WIN32
- /* Dameonize if requested */
- if (args->daemonize == 1)
- daemonize();
-#endif
-
- 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();
-
-#ifdef WIN32
- /* Install as a Windows service if required */
- if (args->install_as_service)
- {
- if (InstallService() != 0)
- {
- FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) & lpMsgBuf, 0, NULL);
- fprintf(stderr, "Error: %s\n", (char *) lpMsgBuf);
- exit(-1);
- }
- else
- {
- fprintf(stderr, "Successfully installed pg_autovacuum as a service.\n");
- exit(0);
- }
- }
-
- /* Remove as a Windows service if required */
- if (args->remove_as_service)
- {
- if (RemoveService() != 0)
- {
- FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) & lpMsgBuf, 0, NULL);
- fprintf(stderr, "Error: %s\n", (char *) lpMsgBuf);
- exit(-1);
- }
- else
- {
- fprintf(stderr, "Successfully removed pg_autovacuum as a service.\n");
- exit(0);
- }
- }
-
- /* Normal service startup */
- ServiceTable[0].lpServiceName = "pg_autovacuum";
- ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION) VacuumLoop;
-
- ServiceTable[1].lpServiceName = NULL;
- ServiceTable[1].lpServiceProc = NULL;
-
- /* Start the control dispatcher thread for our service */
- if (!StartServiceCtrlDispatcher(ServiceTable))
- {
- appMode = 1;
- VacuumLoop(0, NULL);
- }
-
-#else /* Unix */
-
- /* Call the main program loop. */
- VacuumLoop(0, NULL);
-#endif /* WIN32 */
-
- return EXIT_SUCCESS;
-}