2 * All the code for the pg_autovacuum program
3 * (c) 2003 Matthew T. O'Connor
4 * Revisions by Christopher B. Browne, Liberty RMS
7 #include "pg_autovacuum.h"
10 char timebuffer[TIMEBUFF];
14 log_entry(const char *logentry)
20 loctime = localtime(&curtime);
21 strftime(timebuffer, TIMEBUFF, "%Y-%m-%d %r", loctime); /* cbb - %F is not
23 fprintf(LOGOUTPUT, "[%s] %s\n", timebuffer, logentry);
26 /* Function used to detatch the pg_autovacuum daemon from the tty and go into the background *
27 * This code is mostly ripped directly from pm_dameonize in postmaster.c *
28 * with unneeded code removed. */
35 if (pid == (pid_t) -1)
37 log_entry("Error: cannot disassociate from controlling TTY");
43 /* Parent should just exit, without doing any atexit cleanup */
47 /* GH: If there's no setsid(), we hopefully don't need silent mode.
48 * Until there's a better solution. */
52 log_entry("Error: cannot disassociate from controlling TTY");
60 /* Create and return tbl_info struct with initialized to values from row or res */
62 init_table_info(PGresult *res, int row, db_info * dbi)
64 tbl_info *new_tbl = (tbl_info *) malloc(sizeof(tbl_info));
68 log_entry("init_table_info: Cannot get memory");
76 new_tbl->dbi = dbi; /* set pointer to db */
78 new_tbl->schema_name = (char *)
79 malloc(strlen(PQgetvalue(res, row, PQfnumber(res, "schemaname"))) + 1);
80 if (!new_tbl->schema_name)
82 log_entry("init_table_info: malloc failed on new_tbl->schema_name");
86 strcpy(new_tbl->schema_name,
87 PQgetvalue(res, row, PQfnumber(res, "schemaname")));
89 new_tbl->table_name = (char *)
90 malloc(strlen(PQgetvalue(res, row, PQfnumber(res, "relname"))) +
91 strlen(new_tbl->schema_name) + 6);
92 if (!new_tbl->table_name)
94 log_entry("init_table_info: malloc failed on new_tbl->table_name");
100 * Put both the schema and table name in quotes so that we can work
101 * with mixed case table names
103 strcpy(new_tbl->table_name, "\"");
104 strcat(new_tbl->table_name, new_tbl->schema_name);
105 strcat(new_tbl->table_name, "\".\"");
106 strcat(new_tbl->table_name, PQgetvalue(res, row, PQfnumber(res, "relname")));
107 strcat(new_tbl->table_name, "\"");
109 new_tbl->CountAtLastAnalyze =
110 (atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_ins"))) +
111 atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_upd"))) +
112 atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_del"))));
113 new_tbl->curr_analyze_count = new_tbl->CountAtLastAnalyze;
115 new_tbl->CountAtLastVacuum =
116 (atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_del"))) +
117 atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_upd"))));
118 new_tbl->curr_vacuum_count = new_tbl->CountAtLastVacuum;
120 new_tbl->relid = atooid(PQgetvalue(res, row, PQfnumber(res, "oid")));
121 new_tbl->reltuples = atof(PQgetvalue(res, row, PQfnumber(res, "reltuples")));
122 new_tbl->relpages = atooid(PQgetvalue(res, row, PQfnumber(res, "relpages")));
124 if (strcmp("t", PQgetvalue(res, row, PQfnumber(res, "relisshared"))))
125 new_tbl->relisshared = 0;
127 new_tbl->relisshared = 1;
129 new_tbl->analyze_threshold =
130 args->analyze_base_threshold + args->analyze_scaling_factor * new_tbl->reltuples;
131 new_tbl->vacuum_threshold =
132 args->vacuum_base_threshold + args->vacuum_scaling_factor * new_tbl->reltuples;
134 if (args->debug >= 2)
135 print_table_info(new_tbl);
140 /* Set thresholds = base_value + scaling_factor * reltuples
141 Should be called after a vacuum since vacuum updates values in pg_class */
143 update_table_thresholds(db_info * dbi, tbl_info * tbl, int vacuum_type)
145 PGresult *res = NULL;
149 if (dbi->conn == NULL)
151 dbi->conn = db_connect(dbi);
155 if (dbi->conn != NULL)
157 snprintf(query, sizeof(query), PAGES_QUERY, tbl->relid);
158 res = send_query(query, dbi);
162 atof(PQgetvalue(res, 0, PQfnumber(res, "reltuples")));
163 tbl->relpages = atooid(PQgetvalue(res, 0, PQfnumber(res, "relpages")));
166 * update vacuum thresholds only of we just did a vacuum
169 if (vacuum_type == VACUUM_ANALYZE)
171 tbl->vacuum_threshold =
172 (args->vacuum_base_threshold + args->vacuum_scaling_factor * tbl->reltuples);
173 tbl->CountAtLastVacuum = tbl->curr_vacuum_count;
176 /* update analyze thresholds */
177 tbl->analyze_threshold =
178 (args->analyze_base_threshold + args->analyze_scaling_factor * tbl->reltuples);
179 tbl->CountAtLastAnalyze = tbl->curr_analyze_count;
184 * If the stats collector is reporting fewer updates then we
185 * have on record then the stats were probably reset, so we
188 if ((tbl->curr_analyze_count < tbl->CountAtLastAnalyze) ||
189 (tbl->curr_vacuum_count < tbl->CountAtLastVacuum))
191 tbl->CountAtLastAnalyze = tbl->curr_analyze_count;
192 tbl->CountAtLastVacuum = tbl->curr_vacuum_count;
201 update_table_list(db_info * dbi)
204 PGresult *res = NULL;
205 tbl_info *tbl = NULL;
206 Dlelem *tbl_elem = DLGetHead(dbi->table_list);
211 if (dbi->conn == NULL)
213 dbi->conn = db_connect(dbi);
217 if (dbi->conn != NULL)
220 * Get a result set that has all the information we will need to
221 * both remove tables from the list that no longer exist and add
222 * tables to the list that are new
224 res = send_query((char *) TABLE_STATS_QUERY, dbi);
228 * First: use the tbl_list as the outer loop and the result set as
229 * the inner loop, this will determine what tables should be
232 while (tbl_elem != NULL)
234 tbl = ((tbl_info *) DLE_VAL(tbl_elem));
237 for (i = 0; i < t; i++)
238 { /* loop through result set looking for a
240 if (tbl->relid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
246 if (found_match == 0)
247 { /* then we didn't find this tbl_elem in
249 Dlelem *elem_to_remove = tbl_elem;
251 tbl_elem = DLGetSucc(tbl_elem);
252 remove_table_from_list(elem_to_remove);
255 tbl_elem = DLGetSucc(tbl_elem);
256 } /* Done removing dropped tables from the
260 * Then loop use result set as outer loop and tbl_list as the
261 * inner loop to determine what tables are new
263 for (i = 0; i < t; i++)
265 tbl_elem = DLGetHead(dbi->table_list);
267 while (tbl_elem != NULL)
269 tbl = ((tbl_info *) DLE_VAL(tbl_elem));
270 if (tbl->relid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
275 tbl_elem = DLGetSucc(tbl_elem);
277 if (found_match == 0) /* then we didn't find this result
278 * now in the tbl_list */
280 DLAddTail(dbi->table_list, DLNewElem(init_table_info(res, i, dbi)));
281 if (args->debug >= 1)
283 sprintf(logbuffer, "added table: %s.%s", dbi->dbname,
284 ((tbl_info *) DLE_VAL(DLGetTail(dbi->table_list)))->table_name);
285 log_entry(logbuffer);
288 } /* end of for loop that adds tables */
292 if (args->debug >= 3)
293 print_table_list(dbi->table_list);
299 /* Free memory, and remove the node from the list */
301 remove_table_from_list(Dlelem *tbl_to_remove)
303 tbl_info *tbl = ((tbl_info *) DLE_VAL(tbl_to_remove));
305 if (args->debug >= 1)
307 sprintf(logbuffer, "Removing table: %s from list.", tbl->table_name);
308 log_entry(logbuffer);
311 DLRemove(tbl_to_remove);
313 if (tbl->schema_name)
315 free(tbl->schema_name);
316 tbl->schema_name = NULL;
320 free(tbl->table_name);
321 tbl->table_name = NULL;
328 DLFreeElem(tbl_to_remove);
331 /* Free the entire table list */
333 free_tbl_list(Dllist *tbl_list)
335 Dlelem *tbl_elem = DLGetHead(tbl_list);
336 Dlelem *tbl_elem_to_remove = NULL;
338 while (tbl_elem != NULL)
340 tbl_elem_to_remove = tbl_elem;
341 tbl_elem = DLGetSucc(tbl_elem);
342 remove_table_from_list(tbl_elem_to_remove);
344 DLFreeList(tbl_list);
348 print_table_list(Dllist *table_list)
350 Dlelem *table_elem = DLGetHead(table_list);
352 while (table_elem != NULL)
354 print_table_info(((tbl_info *) DLE_VAL(table_elem)));
355 table_elem = DLGetSucc(table_elem);
360 print_table_info(tbl_info * tbl)
362 sprintf(logbuffer, " table name: %s.%s", tbl->dbi->dbname, tbl->table_name);
363 log_entry(logbuffer);
364 sprintf(logbuffer, " relid: %u; relisshared: %i", tbl->relid, tbl->relisshared);
365 log_entry(logbuffer);
366 sprintf(logbuffer, " reltuples: %f; relpages: %u", tbl->reltuples, tbl->relpages);
367 log_entry(logbuffer);
368 sprintf(logbuffer, " curr_analyze_count: %li; curr_vacuum_count: %li",
369 tbl->curr_analyze_count, tbl->curr_vacuum_count);
370 log_entry(logbuffer);
371 sprintf(logbuffer, " last_analyze_count: %li; last_vacuum_count: %li",
372 tbl->CountAtLastAnalyze, tbl->CountAtLastVacuum);
373 log_entry(logbuffer);
374 sprintf(logbuffer, " analyze_threshold: %li; vacuum_threshold: %li",
375 tbl->analyze_threshold, tbl->vacuum_threshold);
376 log_entry(logbuffer);
380 /* End of table Management Functions */
382 /* Beginning of DB Management Functions */
384 /* init_db_list() creates the db_list and initalizes template1 */
388 Dllist *db_list = DLNewList();
390 PGresult *res = NULL;
392 DLAddHead(db_list, DLNewElem(init_dbinfo((char *) "template1", 0, 0)));
393 if (DLGetHead(db_list) == NULL)
394 { /* Make sure init_dbinfo was successful */
395 log_entry("init_db_list(): Error creating db_list for db: template1.");
401 * We do this just so we can set the proper oid for the template1
404 dbs = ((db_info *) DLE_VAL(DLGetHead(db_list)));
405 dbs->conn = db_connect(dbs);
407 if (dbs->conn != NULL)
409 res = send_query(FROZENOID_QUERY, dbs);
410 dbs->oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
411 dbs->age = atol(PQgetvalue(res, 0, PQfnumber(res, "age")));
415 if (args->debug >= 2)
416 print_db_list(db_list, 0);
421 /* Simple function to create an instance of the dbinfo struct
422 Initalizes all the pointers and connects to the database */
424 init_dbinfo(char *dbname, Oid oid, long age)
426 db_info *newdbinfo = (db_info *) malloc(sizeof(db_info));
428 newdbinfo->analyze_threshold = args->vacuum_base_threshold;
429 newdbinfo->vacuum_threshold = args->analyze_base_threshold;
430 newdbinfo->dbname = (char *) malloc(strlen(dbname) + 1);
431 strcpy(newdbinfo->dbname, dbname);
432 newdbinfo->username = NULL;
433 if (args->user != NULL)
435 newdbinfo->username = (char *) malloc(strlen(args->user) + 1);
436 strcpy(newdbinfo->username, args->user);
438 newdbinfo->password = NULL;
439 if (args->password != NULL)
441 newdbinfo->password = (char *) malloc(strlen(args->password) + 1);
442 strcpy(newdbinfo->password, args->password);
444 newdbinfo->oid = oid;
445 newdbinfo->age = age;
446 newdbinfo->table_list = DLNewList();
447 newdbinfo->conn = NULL;
449 if (args->debug >= 2)
450 print_table_list(newdbinfo->table_list);
455 /* Function adds and removes databases from the db_list as appropriate */
457 update_db_list(Dllist *db_list)
460 PGresult *res = NULL;
461 Dlelem *db_elem = DLGetHead(db_list);
463 db_info *dbi_template1 = DLE_VAL(db_elem);
468 if (args->debug >= 2)
470 log_entry("updating the database list");
474 if (dbi_template1->conn == NULL)
476 dbi_template1->conn = db_connect(dbi_template1);
480 if (dbi_template1->conn != NULL)
483 * Get a result set that has all the information we will need to
484 * both remove databasews from the list that no longer exist and
485 * add databases to the list that are new
487 res = send_query(FROZENOID_QUERY2, dbi_template1);
491 * First: use the db_list as the outer loop and the result set as
492 * the inner loop, this will determine what databases should be
495 while (db_elem != NULL)
497 dbi = ((db_info *) DLE_VAL(db_elem));
500 for (i = 0; i < t; i++)
501 { /* loop through result set looking for a
503 if (dbi->oid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
508 * update the dbi->age so that we ensure
509 * xid_wraparound won't happen
511 dbi->age = atol(PQgetvalue(res, i, PQfnumber(res, "age")));
515 if (found_match == 0)
516 { /* then we didn't find this db_elem in the
518 Dlelem *elem_to_remove = db_elem;
520 db_elem = DLGetSucc(db_elem);
521 remove_db_from_list(elem_to_remove);
524 db_elem = DLGetSucc(db_elem);
525 } /* Done removing dropped databases from
529 * Then loop use result set as outer loop and db_list as the inner
530 * loop to determine what databases are new
532 for (i = 0; i < t; i++)
534 db_elem = DLGetHead(db_list);
536 while (db_elem != NULL)
538 dbi = ((db_info *) DLE_VAL(db_elem));
539 if (dbi->oid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
544 db_elem = DLGetSucc(db_elem);
546 if (found_match == 0) /* then we didn't find this result
547 * now in the tbl_list */
549 DLAddTail(db_list, DLNewElem(init_dbinfo
550 (PQgetvalue(res, i, PQfnumber(res, "datname")),
551 atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))),
552 atol(PQgetvalue(res, i, PQfnumber(res, "age"))))));
553 if (args->debug >= 1)
555 sprintf(logbuffer, "added database: %s", ((db_info *) DLE_VAL(DLGetTail(db_list)))->dbname);
556 log_entry(logbuffer);
559 } /* end of for loop that adds tables */
563 if (args->debug >= 3)
564 print_db_list(db_list, 0);
566 db_disconnect(dbi_template1);
570 /* xid_wraparound_check
574 With the standard freezing policy, the age column will start at one billion for a
575 freshly-vacuumed database. When the age approaches two billion, the database must
576 be vacuumed again to avoid risk of wraparound failures. Recommended practice is
577 to vacuum each database at least once every half-a-billion (500 million) transactions,
578 so as to provide plenty of safety margin.
580 So we do a full database vacuum if age > 1.5billion
581 return 0 if nothing happened,
582 return 1 if the database needed a database wide vacuum
585 xid_wraparound_check(db_info * dbi)
588 * FIXME: should probably do something better here so that we don't
589 * vacuum all the databases on the server at the same time. We have
590 * 500million xacts to work with so we should be able to spread the
591 * load of full database vacuums a bit
593 if (dbi->age > 1500000000 )
595 PGresult *res = NULL;
597 res = send_query("VACUUM", dbi);
598 /* FIXME: Perhaps should add a check for PQ_COMMAND_OK */
605 /* Close DB connection, free memory, and remove the node from the list */
607 remove_db_from_list(Dlelem *db_to_remove)
609 db_info *dbi = ((db_info *) DLE_VAL(db_to_remove));
611 if (args->debug >= 1)
613 sprintf(logbuffer, "Removing db: %s from list.", dbi->dbname);
614 log_entry(logbuffer);
617 DLRemove(db_to_remove);
628 dbi->username = NULL;
633 dbi->password = NULL;
637 free_tbl_list(dbi->table_list);
638 dbi->table_list = NULL;
645 DLFreeElem(db_to_remove);
648 /* Function is called before program exit to free all memory
649 mostly it's just to keep valgrind happy */
651 free_db_list(Dllist *db_list)
653 Dlelem *db_elem = DLGetHead(db_list);
654 Dlelem *db_elem_to_remove = NULL;
656 while (db_elem != NULL)
658 db_elem_to_remove = db_elem;
659 db_elem = DLGetSucc(db_elem);
660 remove_db_from_list(db_elem_to_remove);
661 db_elem_to_remove = NULL;
667 print_db_list(Dllist *db_list, int print_table_lists)
669 Dlelem *db_elem = DLGetHead(db_list);
671 while (db_elem != NULL)
673 print_db_info(((db_info *) DLE_VAL(db_elem)), print_table_lists);
674 db_elem = DLGetSucc(db_elem);
679 print_db_info(db_info * dbi, int print_tbl_list)
681 sprintf(logbuffer, "dbname: %s", (dbi->dbname) ? dbi->dbname : "(null)");
682 log_entry(logbuffer);
684 sprintf(logbuffer, " oid: %u", dbi->oid);
685 log_entry(logbuffer);
687 sprintf(logbuffer, " username: %s", (dbi->username) ? dbi->username : "(null)");
688 log_entry(logbuffer);
690 sprintf(logbuffer, " password: %s", (dbi->password) ? dbi->password : "(null)");
691 log_entry(logbuffer);
693 if (dbi->conn != NULL)
694 log_entry(" conn is valid, (connected)");
696 log_entry(" conn is null, (not connected)");
698 sprintf(logbuffer, " default_analyze_threshold: %li", dbi->analyze_threshold);
699 log_entry(logbuffer);
701 sprintf(logbuffer, " default_vacuum_threshold: %li", dbi->vacuum_threshold);
702 log_entry(logbuffer);
705 if (print_tbl_list > 0)
706 print_table_list(dbi->table_list);
709 /* End of DB List Management Function */
711 /* Beginning of misc Functions */
713 /* Perhaps add some test to this function to make sure that the stats we need are available */
715 db_connect(db_info * dbi)
718 PQsetdbLogin(args->host, args->port, NULL, NULL, dbi->dbname,
719 dbi->username, dbi->password);
721 if (PQstatus(db_conn) != CONNECTION_OK)
723 sprintf(logbuffer, "Failed connection to database %s with error: %s.",
724 dbi->dbname, PQerrorMessage(db_conn));
725 log_entry(logbuffer);
731 } /* end of db_connect() */
734 db_disconnect(db_info * dbi)
736 if (dbi->conn != NULL)
744 check_stats_enabled(db_info * dbi)
749 res = send_query("SHOW stats_row_level", dbi);
752 ret = strcmp("on", PQgetvalue(res, 0, PQfnumber(res, "stats_row_level")));
759 send_query(const char *query, db_info * dbi)
763 if (dbi->conn == NULL)
766 if (args->debug >= 4)
769 res = PQexec(dbi->conn, query);
774 "Fatal error occured while sending query (%s) to database %s",
776 log_entry(logbuffer);
777 sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res));
778 log_entry(logbuffer);
782 if (PQresultStatus(res) != PGRES_TUPLES_OK &&
783 PQresultStatus(res) != PGRES_COMMAND_OK)
786 "Can not refresh statistics information from the database %s.",
788 log_entry(logbuffer);
789 sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res));
790 log_entry(logbuffer);
796 } /* End of send_query() */
804 if (args->user != NULL)
806 if (args->password != NULL)
807 free(args->password);
813 get_cmd_args(int argc, char *argv[])
817 args = (cmd_args *) malloc(sizeof(cmd_args));
818 args->sleep_base_value = SLEEPBASEVALUE;
819 args->sleep_scaling_factor = SLEEPSCALINGFACTOR;
820 args->vacuum_base_threshold = VACBASETHRESHOLD;
821 args->vacuum_scaling_factor = VACSCALINGFACTOR;
822 args->analyze_base_threshold = -1;
823 args->analyze_scaling_factor = -1;
824 args->debug = AUTOVACUUM_DEBUG;
833 * Fixme: Should add some sanity checking such as positive integer
836 while ((c = getopt(argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hD")) != -1)
841 args->sleep_base_value = atoi(optarg);
844 args->sleep_scaling_factor = atof(optarg);
847 args->vacuum_base_threshold = atoi(optarg);
850 args->vacuum_scaling_factor = atof(optarg);
853 args->analyze_base_threshold = atoi(optarg);
856 args->analyze_scaling_factor = atof(optarg);
862 args->debug = atoi(optarg);
868 args->password = optarg;
874 args->logfile = optarg;
885 * It's here that we know that things are invalid... It is
886 * not forcibly an error to call usage
888 fprintf(stderr, "Error: Invalid Command Line Options.\n");
895 * if values for insert thresholds are not specified, then they
896 * default to 1/2 of the delete values
898 if (args->analyze_base_threshold == -1)
899 args->analyze_base_threshold = args->vacuum_base_threshold / 2;
900 if (args->analyze_scaling_factor == -1)
901 args->analyze_scaling_factor = args->vacuum_scaling_factor / 2;
912 fprintf(stderr, "usage: pg_autovacuum \n");
913 fprintf(stderr, " [-D] Daemonize (Detach from tty and run in the background)\n");
914 i = AUTOVACUUM_DEBUG;
915 fprintf(stderr, " [-d] debug (debug level=0,1,2,3; default=%i)\n", i);
918 fprintf(stderr, " [-s] sleep base value (default=%i)\n", i);
919 f = SLEEPSCALINGFACTOR;
920 fprintf(stderr, " [-S] sleep scaling factor (default=%f)\n", f);
922 i = VACBASETHRESHOLD;
923 fprintf(stderr, " [-v] vacuum base threshold (default=%i)\n", i);
924 f = VACSCALINGFACTOR;
925 fprintf(stderr, " [-V] vacuum scaling factor (default=%f)\n", f);
927 fprintf(stderr, " [-a] analyze base threshold (default=%i)\n", i);
929 fprintf(stderr, " [-A] analyze scaling factor (default=%f)\n", f);
931 fprintf(stderr, " [-L] logfile (default=none)\n");
933 fprintf(stderr, " [-U] username (libpq default)\n");
934 fprintf(stderr, " [-P] password (libpq default)\n");
935 fprintf(stderr, " [-H] host (libpq default)\n");
936 fprintf(stderr, " [-p] port (libpq default)\n");
938 fprintf(stderr, " [-h] help (Show this output)\n");
944 sprintf(logbuffer, "Printing command_args");
945 log_entry(logbuffer);
946 sprintf(logbuffer, " args->host=%s", (args->host) ? args->host : "(null)");
947 log_entry(logbuffer);
948 sprintf(logbuffer, " args->port=%s", (args->port) ? args->port : "(null)");
949 log_entry(logbuffer);
950 sprintf(logbuffer, " args->username=%s", (args->user) ? args->user : "(null)");
951 log_entry(logbuffer);
952 sprintf(logbuffer, " args->password=%s", (args->password) ? args->password : "(null)");
953 log_entry(logbuffer);
954 sprintf(logbuffer, " args->logfile=%s", (args->logfile) ? args->logfile : "(null)");
955 log_entry(logbuffer);
956 sprintf(logbuffer, " args->daemonize=%i", args->daemonize);
957 log_entry(logbuffer);
959 sprintf(logbuffer, " args->sleep_base_value=%i", args->sleep_base_value);
960 log_entry(logbuffer);
961 sprintf(logbuffer, " args->sleep_scaling_factor=%f", args->sleep_scaling_factor);
962 log_entry(logbuffer);
963 sprintf(logbuffer, " args->vacuum_base_threshold=%i", args->vacuum_base_threshold);
964 log_entry(logbuffer);
965 sprintf(logbuffer, " args->vacuum_scaling_factor=%f", args->vacuum_scaling_factor);
966 log_entry(logbuffer);
967 sprintf(logbuffer, " args->analyze_base_threshold=%i", args->analyze_base_threshold);
968 log_entry(logbuffer);
969 sprintf(logbuffer, " args->analyze_scaling_factor=%f", args->analyze_scaling_factor);
970 log_entry(logbuffer);
971 sprintf(logbuffer, " args->debug=%i", args->debug);
972 log_entry(logbuffer);
977 /* Beginning of AutoVacuum Main Program */
979 main(int argc, char *argv[])
985 /* int numInserts, numDeletes, */
992 PGresult *res = NULL;
997 args = get_cmd_args(argc, argv); /* Get Command Line Args and put
998 * them in the args struct */
1000 /* Dameonize if requested */
1001 if (args->daemonize == 1)
1006 LOGOUTPUT = fopen(args->logfile, "a");
1009 fprintf(stderr, "Could not open log file - [%s]\n", args->logfile);
1015 if (args->debug >= 2)
1018 /* Init the db list with template1 */
1019 db_list = init_db_list();
1020 if (db_list == NULL)
1023 if (check_stats_enabled(((db_info *) DLE_VAL(DLGetHead(db_list)))) != 0)
1025 log_entry("Error: GUC variable stats_row_level must be enabled.");
1026 log_entry(" Please fix the problems and try again.");
1032 gettimeofday(&then, 0); /* for use later to caluculate sleep time */
1036 db_elem = DLGetHead(db_list); /* Reset cur_db_node to the
1037 * beginning of the db_list */
1039 dbs = ((db_info *) DLE_VAL(db_elem)); /* get pointer to cur_db's
1041 if (dbs->conn == NULL)
1043 dbs->conn = db_connect(dbs);
1044 if (dbs->conn == NULL)
1045 { /* Serious problem: We can't connect to
1047 log_entry("Error: Cannot connect to template1, exiting.");
1054 if (loops % UPDATE_INTERVAL == 0) /* Update the list if it's
1056 update_db_list(db_list); /* Add and remove databases from
1059 while (db_elem != NULL)
1060 { /* Loop through databases in list */
1061 dbs = ((db_info *) DLE_VAL(db_elem)); /* get pointer to
1064 if (dbs->conn == NULL)
1065 dbs->conn = db_connect(dbs);
1067 if (dbs->conn != NULL)
1069 if (loops % UPDATE_INTERVAL == 0) /* Update the list if
1071 update_table_list(dbs); /* Add and remove tables
1074 if (xid_wraparound_check(dbs) == 0)
1076 res = send_query(TABLE_STATS_QUERY, dbs); /* Get an updated
1077 * snapshot of this dbs
1079 for (j = 0; j < PQntuples(res); j++)
1080 { /* loop through result set */
1081 tbl_elem = DLGetHead(dbs->table_list); /* Reset tbl_elem to top
1082 * of dbs->table_list */
1083 while (tbl_elem != NULL)
1084 { /* Loop through tables in list */
1085 tbl = ((tbl_info *) DLE_VAL(tbl_elem)); /* set tbl_info =
1087 if (tbl->relid == atooid(PQgetvalue(res, j, PQfnumber(res, "oid"))))
1089 tbl->curr_analyze_count =
1090 (atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_ins"))) +
1091 atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_upd"))) +
1092 atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_del"))));
1093 tbl->curr_vacuum_count =
1094 (atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_del"))) +
1095 atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_upd"))));
1098 * Check numDeletes to see if we need to
1099 * vacuum, if so: Run vacuum analyze
1100 * (adding analyze is small so we might as
1101 * well) Update table thresholds and
1102 * related information if numDeletes is
1103 * not big enough for vacuum then check
1104 * numInserts for analyze
1106 if (tbl->curr_vacuum_count - tbl->CountAtLastVacuum >= tbl->vacuum_threshold)
1109 * if relisshared = t and database !=
1110 * template1 then only do an analyze
1112 if (tbl->relisshared > 0 && strcmp("template1", dbs->dbname))
1113 snprintf(buf, sizeof(buf), "ANALYZE %s", tbl->table_name);
1115 snprintf(buf, sizeof(buf), "VACUUM ANALYZE %s", tbl->table_name);
1116 if (args->debug >= 1)
1118 sprintf(logbuffer, "Performing: %s", buf);
1119 log_entry(logbuffer);
1122 send_query(buf, dbs);
1123 update_table_thresholds(dbs, tbl, VACUUM_ANALYZE);
1124 if (args->debug >= 2)
1125 print_table_info(tbl);
1127 else if (tbl->curr_analyze_count - tbl->CountAtLastAnalyze >= tbl->analyze_threshold)
1129 snprintf(buf, sizeof(buf), "ANALYZE %s", tbl->table_name);
1130 if (args->debug >= 1)
1132 sprintf(logbuffer, "Performing: %s", buf);
1133 log_entry(logbuffer);
1136 send_query(buf, dbs);
1137 update_table_thresholds(dbs, tbl, ANALYZE_ONLY);
1138 if (args->debug >= 2)
1139 print_table_info(tbl);
1142 break; /* once we have found a match, no
1143 * need to keep checking. */
1147 * Advance the table pointers for the next
1150 tbl_elem = DLGetSucc(tbl_elem);
1152 } /* end for table while loop */
1153 } /* end for j loop (tuples in PGresult) */
1154 } /* close of if(xid_wraparound_check()) */
1155 /* Done working on this db, Clean up, then advance cur_db */
1160 db_elem = DLGetSucc(db_elem); /* move on to next DB
1162 } /* end of db_list while loop */
1164 /* Figure out how long to sleep etc ... */
1165 gettimeofday(&now, 0);
1166 diff = (int) (now.tv_sec - then.tv_sec) * 1000000.0 + (int) (now.tv_usec - then.tv_usec);
1168 sleep_secs = args->sleep_base_value + args->sleep_scaling_factor * diff / 1000000.0;
1170 if (args->debug >= 2)
1173 "%i All DBs checked in: %.0f usec, will sleep for %i secs.",
1174 loops, diff, sleep_secs);
1175 log_entry(logbuffer);
1179 sleep(sleep_secs); /* Larger Pause between outer loops */
1181 gettimeofday(&then, 0); /* Reset time counter */
1183 } /* end of while loop */
1186 * program is exiting, this should never run, but is here to make
1187 * compiler / valgrind happy
1189 free_db_list(db_list);
1191 return EXIT_SUCCESS;