+ {
+ 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);
+ }
+}
+
+/* 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
+*/
+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;
+}
+
+/* Close DB connection, free memory, and remove the node from the list */
+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);
+}
+
+/* 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)
+{
+ 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)
+{
+ 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)
+{
+ 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");
+ else
+ printf(" conn is null, we are not connected.\n");
+
+ if(0 < print_tbl_list)
+ print_table_list(dbi->table_list);
+}
+
+/* End of DB List Management Function */
+
+/* Begninning of misc Functions */
+
+
+char *query_table_stats(db_info *dbi)
+{
+ if(!strcmp(dbi->dbname,"template1")) /* Use template1 to monitor the system tables */
+ return (char*)TABLE_STATS_ALL;
+ else
+ 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)
+{
+ 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;
+ }
+ return db_conn;
+} /* end of db_connect() */
+
+void db_disconnect(db_info *dbi)
+{
+ if(NULL != dbi->conn)
+ {
+ PQfinish(dbi->conn);
+ dbi->conn=NULL;
+ }
+}
+
+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 *send_query(const char *query,db_info *dbi)
+{
+ PGresult *res;
+
+ if(NULL==dbi->conn)
+ return NULL;
+
+ 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));
+ 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);
+ return NULL;
+ }
+ return res;
+} /* End of send_query() */
+
+
+void free_cmd_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[])
+{
+ 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;
+ }
+ }
+
+ return args;
+}
+
+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);
+}
+
+/* Beginning of AutoVacuum Main Program */
+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)
+ 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);
+ }
+
+ 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 */
+
+ 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"))))
+ {
+ 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 */
+
+ /* Figure out how long to sleep etc ... */
+ 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;
+ loops++;
+ if(args->debug >= 2)
+ { printf("%i All DBs checked in: %lld usec, will sleep for %i secs.\n",loops,diff,sleep_secs);}
+
+ sleep(sleep_secs); /* Larger Pause between outer loops */
+
+ 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 EXIT_SUCCESS;
+}