~vcs-imports/mammoth-replicator/trunk

« back to all changes in this revision

Viewing changes to contrib/pg_autovacuum/pg_autovacuum.c

  • Committer: alvherre
  • Date: 2005-12-16 21:24:52 UTC
  • Revision ID: svn-v4:db760fc0-0f08-0410-9d63-cc6633f64896:trunk:1
Initial import of the REL8_0_3 sources from the Pgsql CVS repository.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* pg_autovacuum.c
 
2
 * All the code for the pg_autovacuum program
 
3
 * (c) 2003 Matthew T. O'Connor
 
4
 * Revisions by Christopher B. Browne, Liberty RMS
 
5
 * Win32 Service code added by Dave Page
 
6
 *
 
7
 * $PostgreSQL: pgsql/contrib/pg_autovacuum/pg_autovacuum.c,v 1.27.4.3 2005-04-03 00:02:03 tgl Exp $
 
8
 */
 
9
 
 
10
#include "postgres_fe.h"
 
11
 
 
12
#include <unistd.h>
 
13
#ifdef HAVE_GETOPT_H
 
14
#include <getopt.h>
 
15
#endif
 
16
#include <time.h>
 
17
#include <sys/time.h>
 
18
#ifdef WIN32
 
19
#include <windows.h>
 
20
#endif
 
21
#include <sys/stat.h>
 
22
#include <fcntl.h>
 
23
 
 
24
#include "pg_autovacuum.h"
 
25
 
 
26
#ifdef WIN32
 
27
SERVICE_STATUS ServiceStatus;
 
28
SERVICE_STATUS_HANDLE hStatus;
 
29
int                     appMode = 0;
 
30
char       deps[255];
 
31
#endif
 
32
 
 
33
/* define atooid */
 
34
#define atooid(x)  ((Oid) strtoul((x), NULL, 10))
 
35
 
 
36
 
 
37
static cmd_args *args;
 
38
static FILE        *LOGOUTPUT;
 
39
static char             logbuffer[4096];
 
40
 
 
41
 
 
42
/* The main program loop function */
 
43
static int      VacuumLoop(int argc, char **argv);
 
44
 
 
45
/* Functions for dealing with command line arguements */
 
46
static cmd_args *get_cmd_args(int argc, char *argv[]);
 
47
static void print_cmd_args(void);
 
48
static void free_cmd_args(void);
 
49
static void usage(void);
 
50
 
 
51
/* Functions for managing database lists */
 
52
static Dllist *init_db_list(void);
 
53
static db_info *init_dbinfo(char *dbname, Oid oid, long age);
 
54
static void update_db_list(Dllist *db_list);
 
55
static void remove_db_from_list(Dlelem *db_to_remove);
 
56
static void print_db_info(db_info * dbi, int print_table_list);
 
57
static void print_db_list(Dllist *db_list, int print_table_lists);
 
58
static int      xid_wraparound_check(db_info * dbi);
 
59
static void free_db_list(Dllist *db_list);
 
60
 
 
61
/* Functions for managing table lists */
 
62
static tbl_info *init_table_info(PGresult *conn, int row, db_info * dbi);
 
63
static void update_table_list(db_info * dbi);
 
64
static void remove_table_from_list(Dlelem *tbl_to_remove);
 
65
static void print_table_list(Dllist *tbl_node);
 
66
static void print_table_info(tbl_info * tbl);
 
67
static void update_table_thresholds(db_info * dbi, tbl_info * tbl, int vacuum_type);
 
68
static void free_tbl_list(Dllist *tbl_list);
 
69
 
 
70
/* A few database helper functions */
 
71
static int      check_stats_enabled(db_info * dbi);
 
72
static PGconn *db_connect(db_info * dbi);
 
73
static void db_disconnect(db_info * dbi);
 
74
static PGresult *send_query(const char *query, db_info * dbi);
 
75
 
 
76
/* Other Generally needed Functions */
 
77
#ifndef WIN32
 
78
static void daemonize(void);
 
79
#endif
 
80
static void log_entry(const char *logentry, int level);
 
81
 
 
82
#ifdef WIN32
 
83
/* Windows Service related functions */
 
84
static void ControlHandler(DWORD request);
 
85
static int      InstallService();
 
86
static int      RemoveService();
 
87
#endif
 
88
 
 
89
 
 
90
static void
 
91
log_entry(const char *logentry, int level)
 
92
{
 
93
        /*
 
94
         * Note: Under Windows we dump the log entries to the normal
 
95
         * stderr/logfile as well, otherwise it can be a pain to debug
 
96
         * service install failures etc.
 
97
         */
 
98
 
 
99
        time_t          curtime;
 
100
        struct tm  *loctime;
 
101
        char            timebuffer[128],
 
102
                                slevel[10];
 
103
 
 
104
 
 
105
#ifdef WIN32
 
106
        static HANDLE evtHandle = INVALID_HANDLE_VALUE;
 
107
        static int      last_level;
 
108
        WORD            elevel;
 
109
#endif
 
110
 
 
111
        switch (level)
 
112
        {
 
113
                case LVL_DEBUG:
 
114
                        sprintf(slevel, "DEBUG:   ");
 
115
                        break;
 
116
 
 
117
                case LVL_INFO:
 
118
                        sprintf(slevel, "INFO:    ");
 
119
                        break;
 
120
 
 
121
                case LVL_WARNING:
 
122
                        sprintf(slevel, "WARNING: ");
 
123
                        break;
 
124
 
 
125
                case LVL_ERROR:
 
126
                        sprintf(slevel, "ERROR:   ");
 
127
                        break;
 
128
 
 
129
                case LVL_EXTRA:
 
130
                        sprintf(slevel, "         ");
 
131
                        break;
 
132
 
 
133
                default:
 
134
                        sprintf(slevel, "         ");
 
135
                        break;
 
136
        }
 
137
 
 
138
        curtime = time(NULL);
 
139
        loctime = localtime(&curtime);
 
140
        strftime(timebuffer, sizeof(timebuffer), "%Y-%m-%d %H:%M:%S %Z", loctime);
 
141
        fprintf(LOGOUTPUT, "[%s] %s%s\n", timebuffer, slevel, logentry);
 
142
 
 
143
#ifdef WIN32
 
144
 
 
145
        /* Restore the previous level if this is extra info */
 
146
        if (level == LVL_EXTRA)
 
147
                level = last_level;
 
148
        last_level = level;
 
149
 
 
150
        switch (level)
 
151
        {
 
152
                case LVL_DEBUG:
 
153
                        elevel = EVENTLOG_INFORMATION_TYPE;
 
154
                        break;
 
155
 
 
156
                case LVL_INFO:
 
157
                        elevel = EVENTLOG_SUCCESS;
 
158
                        break;
 
159
 
 
160
                case LVL_WARNING:
 
161
                        elevel = EVENTLOG_WARNING_TYPE;
 
162
                        break;
 
163
 
 
164
                case LVL_ERROR:
 
165
                        elevel = EVENTLOG_ERROR_TYPE;
 
166
                        break;
 
167
 
 
168
                default:
 
169
                        elevel = EVENTLOG_SUCCESS;
 
170
                        break;
 
171
        }
 
172
 
 
173
        if (evtHandle == INVALID_HANDLE_VALUE)
 
174
        {
 
175
                evtHandle = RegisterEventSource(NULL, "PostgreSQL Auto Vacuum");
 
176
                if (evtHandle == NULL)
 
177
                {
 
178
                        evtHandle = INVALID_HANDLE_VALUE;
 
179
                        return;
 
180
                }
 
181
        }
 
182
 
 
183
        ReportEvent(evtHandle, elevel, 0, 0, NULL, 1, 0, &logentry, NULL);
 
184
#endif
 
185
}
 
186
 
 
187
/*
 
188
 * Function used to detach the pg_autovacuum daemon from the tty and go into
 
189
 * the background.
 
190
 *
 
191
 * This code is ripped directly from pmdaemonize in postmaster.c.
 
192
 */
 
193
#ifndef WIN32
 
194
static void
 
195
daemonize(void)
 
196
{
 
197
        int                     i;
 
198
        pid_t           pid;
 
199
 
 
200
        pid = fork();
 
201
        if (pid == (pid_t) -1)
 
202
        {
 
203
                log_entry("cannot disassociate from controlling TTY", LVL_ERROR);
 
204
                fflush(LOGOUTPUT);
 
205
                _exit(1);
 
206
        }
 
207
        else if (pid)
 
208
        {                                                       /* parent */
 
209
                /* Parent should just exit, without doing any atexit cleanup */
 
210
                _exit(0);
 
211
        }
 
212
 
 
213
/* GH: If there's no setsid(), we hopefully don't need silent mode.
 
214
 * Until there's a better solution.
 
215
 */
 
216
#ifdef HAVE_SETSID
 
217
        if (setsid() < 0)
 
218
        {
 
219
                log_entry("cannot disassociate from controlling TTY", LVL_ERROR);
 
220
                fflush(LOGOUTPUT);
 
221
                _exit(1);
 
222
        }
 
223
#endif
 
224
        i = open(NULL_DEV, O_RDWR);
 
225
        dup2(i, 0);
 
226
        dup2(i, 1);
 
227
        dup2(i, 2);
 
228
        close(i);
 
229
}
 
230
#endif   /* WIN32 */
 
231
 
 
232
/* Create and return tbl_info struct with initialized to values from row or res */
 
233
static tbl_info *
 
234
init_table_info(PGresult *res, int row, db_info * dbi)
 
235
{
 
236
        tbl_info   *new_tbl = (tbl_info *) malloc(sizeof(tbl_info));
 
237
 
 
238
        if (!new_tbl)
 
239
        {
 
240
                log_entry("init_table_info: Cannot get memory", LVL_ERROR);
 
241
                fflush(LOGOUTPUT);
 
242
                return NULL;
 
243
        }
 
244
 
 
245
        if (res == NULL)
 
246
                return NULL;
 
247
 
 
248
        new_tbl->dbi = dbi;                     /* set pointer to db */
 
249
 
 
250
        new_tbl->schema_name = (char *)
 
251
                malloc(strlen(PQgetvalue(res, row, PQfnumber(res, "schemaname"))) + 1);
 
252
        if (!new_tbl->schema_name)
 
253
        {
 
254
                log_entry("init_table_info: malloc failed on new_tbl->schema_name", LVL_ERROR);
 
255
                fflush(LOGOUTPUT);
 
256
                return NULL;
 
257
        }
 
258
        strcpy(new_tbl->schema_name,
 
259
                   PQgetvalue(res, row, PQfnumber(res, "schemaname")));
 
260
 
 
261
        new_tbl->table_name = (char *)
 
262
                malloc(strlen(PQgetvalue(res, row, PQfnumber(res, "relname"))) +
 
263
                           strlen(new_tbl->schema_name) + 6);
 
264
        if (!new_tbl->table_name)
 
265
        {
 
266
                log_entry("init_table_info: malloc failed on new_tbl->table_name", LVL_ERROR);
 
267
                fflush(LOGOUTPUT);
 
268
                return NULL;
 
269
        }
 
270
 
 
271
        /*
 
272
         * Put both the schema and table name in quotes so that we can work
 
273
         * with mixed case table names
 
274
         */
 
275
        strcpy(new_tbl->table_name, "\"");
 
276
        strcat(new_tbl->table_name, new_tbl->schema_name);
 
277
        strcat(new_tbl->table_name, "\".\"");
 
278
        strcat(new_tbl->table_name, PQgetvalue(res, row, PQfnumber(res, "relname")));
 
279
        strcat(new_tbl->table_name, "\"");
 
280
 
 
281
        new_tbl->CountAtLastAnalyze =
 
282
                (atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_ins"))) +
 
283
                 atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_upd"))) +
 
284
                 atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_del"))));
 
285
        new_tbl->curr_analyze_count = new_tbl->CountAtLastAnalyze;
 
286
 
 
287
        new_tbl->CountAtLastVacuum =
 
288
                (atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_del"))) +
 
289
                 atol(PQgetvalue(res, row, PQfnumber(res, "n_tup_upd"))));
 
290
        new_tbl->curr_vacuum_count = new_tbl->CountAtLastVacuum;
 
291
 
 
292
        new_tbl->relid = atooid(PQgetvalue(res, row, PQfnumber(res, "oid")));
 
293
        new_tbl->reltuples = atof(PQgetvalue(res, row, PQfnumber(res, "reltuples")));
 
294
        new_tbl->relpages = atooid(PQgetvalue(res, row, PQfnumber(res, "relpages")));
 
295
 
 
296
        if (strcmp("t", PQgetvalue(res, row, PQfnumber(res, "relisshared"))))
 
297
                new_tbl->relisshared = 0;
 
298
        else
 
299
                new_tbl->relisshared = 1;
 
300
 
 
301
        new_tbl->analyze_threshold =
 
302
                args->analyze_base_threshold + args->analyze_scaling_factor * new_tbl->reltuples;
 
303
        new_tbl->vacuum_threshold =
 
304
                args->vacuum_base_threshold + args->vacuum_scaling_factor * new_tbl->reltuples;
 
305
 
 
306
        if (args->debug >= 2)
 
307
                print_table_info(new_tbl);
 
308
 
 
309
        return new_tbl;
 
310
}
 
311
 
 
312
/* Set thresholds = base_value + scaling_factor * reltuples
 
313
   Should be called after a vacuum since vacuum updates values in pg_class */
 
314
static void
 
315
update_table_thresholds(db_info * dbi, tbl_info * tbl, int vacuum_type)
 
316
{
 
317
        PGresult   *res = NULL;
 
318
        int                     disconnect = 0;
 
319
        char            query[128];
 
320
 
 
321
        if (dbi->conn == NULL)
 
322
        {
 
323
                dbi->conn = db_connect(dbi);
 
324
                disconnect = 1;
 
325
        }
 
326
 
 
327
        if (dbi->conn != NULL)
 
328
        {
 
329
                snprintf(query, sizeof(query), PAGES_QUERY, tbl->relid);
 
330
                res = send_query(query, dbi);
 
331
                if (res != NULL)
 
332
                {
 
333
                        tbl->reltuples =
 
334
                                atof(PQgetvalue(res, 0, PQfnumber(res, "reltuples")));
 
335
                        tbl->relpages = atooid(PQgetvalue(res, 0, PQfnumber(res, "relpages")));
 
336
 
 
337
                        /*
 
338
                         * update vacuum thresholds only of we just did a vacuum
 
339
                         * analyze
 
340
                         */
 
341
                        if (vacuum_type == VACUUM_ANALYZE)
 
342
                        {
 
343
                                tbl->vacuum_threshold =
 
344
                                        (args->vacuum_base_threshold + args->vacuum_scaling_factor * tbl->reltuples);
 
345
                                tbl->CountAtLastVacuum = tbl->curr_vacuum_count;
 
346
                        }
 
347
 
 
348
                        /* update analyze thresholds */
 
349
                        tbl->analyze_threshold =
 
350
                                (args->analyze_base_threshold + args->analyze_scaling_factor * tbl->reltuples);
 
351
                        tbl->CountAtLastAnalyze = tbl->curr_analyze_count;
 
352
 
 
353
                        PQclear(res);
 
354
 
 
355
                        /*
 
356
                         * If the stats collector is reporting fewer updates then we
 
357
                         * have on record then the stats were probably reset, so we
 
358
                         * need to reset also
 
359
                         */
 
360
                        if ((tbl->curr_analyze_count < tbl->CountAtLastAnalyze) ||
 
361
                                (tbl->curr_vacuum_count < tbl->CountAtLastVacuum))
 
362
                        {
 
363
                                tbl->CountAtLastAnalyze = tbl->curr_analyze_count;
 
364
                                tbl->CountAtLastVacuum = tbl->curr_vacuum_count;
 
365
                        }
 
366
                }
 
367
        }
 
368
        if (disconnect)
 
369
                db_disconnect(dbi);
 
370
}
 
371
 
 
372
static void
 
373
update_table_list(db_info * dbi)
 
374
{
 
375
        int                     disconnect = 0;
 
376
        PGresult   *res = NULL;
 
377
        tbl_info   *tbl = NULL;
 
378
        Dlelem     *tbl_elem = DLGetHead(dbi->table_list);
 
379
        int                     i = 0,
 
380
                                t = 0,
 
381
                                found_match = 0;
 
382
 
 
383
        if (dbi->conn == NULL)
 
384
        {
 
385
                dbi->conn = db_connect(dbi);
 
386
                disconnect = 1;
 
387
        }
 
388
 
 
389
        if (dbi->conn != NULL)
 
390
        {
 
391
                /*
 
392
                 * Get a result set that has all the information we will need to
 
393
                 * both remove tables from the list that no longer exist and add
 
394
                 * tables to the list that are new
 
395
                 */
 
396
                res = send_query((char *) TABLE_STATS_QUERY, dbi);
 
397
                if (res != NULL)
 
398
                {
 
399
                        t = PQntuples(res);
 
400
 
 
401
                        /*
 
402
                         * First: use the tbl_list as the outer loop and the result
 
403
                         * set as the inner loop, this will determine what tables
 
404
                         * should be removed
 
405
                         */
 
406
                        while (tbl_elem != NULL)
 
407
                        {
 
408
                                tbl = ((tbl_info *) DLE_VAL(tbl_elem));
 
409
                                found_match = 0;
 
410
 
 
411
                                for (i = 0; i < t; i++)
 
412
                                {                               /* loop through result set looking for a
 
413
                                                                 * match */
 
414
                                        if (tbl->relid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
 
415
                                        {
 
416
                                                found_match = 1;
 
417
                                                break;
 
418
                                        }
 
419
                                }
 
420
                                if (found_match == 0)
 
421
                                {                               /* then we didn't find this tbl_elem in
 
422
                                                                 * the result set */
 
423
                                        Dlelem     *elem_to_remove = tbl_elem;
 
424
 
 
425
                                        tbl_elem = DLGetSucc(tbl_elem);
 
426
                                        remove_table_from_list(elem_to_remove);
 
427
                                }
 
428
                                else
 
429
                                        tbl_elem = DLGetSucc(tbl_elem);
 
430
                        }                                       /* Done removing dropped tables from the
 
431
                                                                 * table_list */
 
432
 
 
433
                        /*
 
434
                         * Then loop use result set as outer loop and tbl_list as the
 
435
                         * inner loop to determine what tables are new
 
436
                         */
 
437
                        for (i = 0; i < t; i++)
 
438
                        {
 
439
                                tbl_elem = DLGetHead(dbi->table_list);
 
440
                                found_match = 0;
 
441
                                while (tbl_elem != NULL)
 
442
                                {
 
443
                                        tbl = ((tbl_info *) DLE_VAL(tbl_elem));
 
444
                                        if (tbl->relid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
 
445
                                        {
 
446
                                                found_match = 1;
 
447
                                                break;
 
448
                                        }
 
449
                                        tbl_elem = DLGetSucc(tbl_elem);
 
450
                                }
 
451
                                if (found_match == 0)   /* then we didn't find this result
 
452
                                                                                 * now in the tbl_list */
 
453
                                {
 
454
                                        DLAddTail(dbi->table_list, DLNewElem(init_table_info(res, i, dbi)));
 
455
                                        if (args->debug >= 1)
 
456
                                        {
 
457
                                                sprintf(logbuffer, "added table: %s.%s", dbi->dbname,
 
458
                                                                ((tbl_info *) DLE_VAL(DLGetTail(dbi->table_list)))->table_name);
 
459
                                                log_entry(logbuffer, LVL_DEBUG);
 
460
                                        }
 
461
                                }
 
462
                        }                                       /* end of for loop that adds tables */
 
463
                }
 
464
                fflush(LOGOUTPUT);
 
465
                PQclear(res);
 
466
                res = NULL;
 
467
                if (args->debug >= 3)
 
468
                        print_table_list(dbi->table_list);
 
469
                if (disconnect)
 
470
                        db_disconnect(dbi);
 
471
        }
 
472
}
 
473
 
 
474
/* Free memory, and remove the node from the list */
 
475
static void
 
476
remove_table_from_list(Dlelem *tbl_to_remove)
 
477
{
 
478
        tbl_info   *tbl = ((tbl_info *) DLE_VAL(tbl_to_remove));
 
479
 
 
480
        if (args->debug >= 1)
 
481
        {
 
482
                sprintf(logbuffer, "Removing table: %s from list.", tbl->table_name);
 
483
                log_entry(logbuffer, LVL_DEBUG);
 
484
                fflush(LOGOUTPUT);
 
485
        }
 
486
        DLRemove(tbl_to_remove);
 
487
 
 
488
        if (tbl->schema_name)
 
489
        {
 
490
                free(tbl->schema_name);
 
491
                tbl->schema_name = NULL;
 
492
        }
 
493
        if (tbl->table_name)
 
494
        {
 
495
                free(tbl->table_name);
 
496
                tbl->table_name = NULL;
 
497
        }
 
498
        if (tbl)
 
499
        {
 
500
                free(tbl);
 
501
                tbl = NULL;
 
502
        }
 
503
        DLFreeElem(tbl_to_remove);
 
504
}
 
505
 
 
506
/* Free the entire table list */
 
507
static void
 
508
free_tbl_list(Dllist *tbl_list)
 
509
{
 
510
        Dlelem     *tbl_elem = DLGetHead(tbl_list);
 
511
        Dlelem     *tbl_elem_to_remove = NULL;
 
512
 
 
513
        while (tbl_elem != NULL)
 
514
        {
 
515
                tbl_elem_to_remove = tbl_elem;
 
516
                tbl_elem = DLGetSucc(tbl_elem);
 
517
                remove_table_from_list(tbl_elem_to_remove);
 
518
        }
 
519
        DLFreeList(tbl_list);
 
520
}
 
521
 
 
522
static void
 
523
print_table_list(Dllist *table_list)
 
524
{
 
525
        Dlelem     *table_elem = DLGetHead(table_list);
 
526
 
 
527
        while (table_elem != NULL)
 
528
        {
 
529
                print_table_info(((tbl_info *) DLE_VAL(table_elem)));
 
530
                table_elem = DLGetSucc(table_elem);
 
531
        }
 
532
}
 
533
 
 
534
static void
 
535
print_table_info(tbl_info * tbl)
 
536
{
 
537
        sprintf(logbuffer, "  table name: %s.%s", tbl->dbi->dbname, tbl->table_name);
 
538
        log_entry(logbuffer, LVL_INFO);
 
539
        sprintf(logbuffer, "     relid: %u;   relisshared: %d", tbl->relid, tbl->relisshared);
 
540
        log_entry(logbuffer, LVL_INFO);
 
541
        sprintf(logbuffer, "     reltuples: %f;  relpages: %u", tbl->reltuples, tbl->relpages);
 
542
        log_entry(logbuffer, LVL_INFO);
 
543
        sprintf(logbuffer, "     curr_analyze_count: %li; curr_vacuum_count: %li",
 
544
                        tbl->curr_analyze_count, tbl->curr_vacuum_count);
 
545
        log_entry(logbuffer, LVL_INFO);
 
546
        sprintf(logbuffer, "     last_analyze_count: %li; last_vacuum_count: %li",
 
547
                        tbl->CountAtLastAnalyze, tbl->CountAtLastVacuum);
 
548
        log_entry(logbuffer, LVL_INFO);
 
549
        sprintf(logbuffer, "     analyze_threshold: %li; vacuum_threshold: %li",
 
550
                        tbl->analyze_threshold, tbl->vacuum_threshold);
 
551
        log_entry(logbuffer, LVL_INFO);
 
552
        fflush(LOGOUTPUT);
 
553
}
 
554
 
 
555
/* End of table Management Functions */
 
556
 
 
557
/* Beginning of DB Management Functions */
 
558
 
 
559
/* init_db_list() creates the db_list and initalizes template1 */
 
560
static Dllist *
 
561
init_db_list(void)
 
562
{
 
563
        Dllist     *db_list = DLNewList();
 
564
        db_info    *dbs = NULL;
 
565
        PGresult   *res = NULL;
 
566
#ifdef WIN32
 
567
        int                     k = 0;
 
568
#endif
 
569
 
 
570
        DLAddHead(db_list, DLNewElem(init_dbinfo((char *) "template1", 0, 0)));
 
571
        if (DLGetHead(db_list) == NULL)
 
572
        {                                                       /* Make sure init_dbinfo was successful */
 
573
                log_entry("init_db_list(): Error creating db_list for db: template1.", LVL_ERROR);
 
574
                fflush(LOGOUTPUT);
 
575
                return NULL;
 
576
        }
 
577
 
 
578
        /*
 
579
         * We do this just so we can set the proper oid for the template1
 
580
         * database
 
581
         */
 
582
        dbs = ((db_info *) DLE_VAL(DLGetHead(db_list)));
 
583
        dbs->conn = db_connect(dbs);
 
584
 
 
585
#ifdef WIN32
 
586
        while (dbs->conn == NULL && !appMode && k < 10)
 
587
        {
 
588
                int        j;
 
589
 
 
590
                /* Pause for 30 seconds to allow the database to start up */
 
591
                log_entry("Pausing 30 seconds to allow the database to startup completely", LVL_INFO);
 
592
                fflush(LOGOUTPUT);
 
593
                ServiceStatus.dwWaitHint = 10;
 
594
                for (j=0; j<6; j++)
 
595
                {
 
596
                        pg_usleep(5000000);
 
597
                        ServiceStatus.dwCheckPoint++;
 
598
                        SetServiceStatus(hStatus, &ServiceStatus);
 
599
                        fflush(LOGOUTPUT);
 
600
                }
 
601
 
 
602
                /* now try again */
 
603
                log_entry("Attempting to connect again.", LVL_INFO);
 
604
                dbs->conn = db_connect(dbs);
 
605
                k++;
 
606
        }
 
607
#endif
 
608
 
 
609
        if (dbs->conn != NULL)
 
610
        {
 
611
                res = send_query(FROZENOID_QUERY, dbs);
 
612
                if (res != NULL)
 
613
                {
 
614
                        dbs->oid = atooid(PQgetvalue(res, 0, PQfnumber(res, "oid")));
 
615
                        dbs->age = atol(PQgetvalue(res, 0, PQfnumber(res, "age")));
 
616
                        if (res)
 
617
                                PQclear(res);
 
618
 
 
619
                        if (args->debug >= 2)
 
620
                                print_db_list(db_list, 0);
 
621
                }
 
622
                else
 
623
                        return NULL;
 
624
        }
 
625
        return db_list;
 
626
}
 
627
 
 
628
/* Simple function to create an instance of the dbinfo struct
 
629
        Initalizes all the pointers and connects to the database  */
 
630
static db_info *
 
631
init_dbinfo(char *dbname, Oid oid, long age)
 
632
{
 
633
        db_info    *newdbinfo = (db_info *) malloc(sizeof(db_info));
 
634
 
 
635
        newdbinfo->analyze_threshold = args->vacuum_base_threshold;
 
636
        newdbinfo->vacuum_threshold = args->analyze_base_threshold;
 
637
        newdbinfo->dbname = (char *) malloc(strlen(dbname) + 1);
 
638
        strcpy(newdbinfo->dbname, dbname);
 
639
        newdbinfo->username = NULL;
 
640
        if (args->user != NULL)
 
641
        {
 
642
                newdbinfo->username = (char *) malloc(strlen(args->user) + 1);
 
643
                strcpy(newdbinfo->username, args->user);
 
644
        }
 
645
        newdbinfo->password = NULL;
 
646
        if (args->password != NULL)
 
647
        {
 
648
                newdbinfo->password = (char *) malloc(strlen(args->password) + 1);
 
649
                strcpy(newdbinfo->password, args->password);
 
650
        }
 
651
        newdbinfo->oid = oid;
 
652
        newdbinfo->age = age;
 
653
        newdbinfo->table_list = DLNewList();
 
654
        newdbinfo->conn = NULL;
 
655
 
 
656
        if (args->debug >= 2)
 
657
                print_table_list(newdbinfo->table_list);
 
658
 
 
659
        return newdbinfo;
 
660
}
 
661
 
 
662
/* Function adds and removes databases from the db_list as appropriate */
 
663
static void
 
664
update_db_list(Dllist *db_list)
 
665
{
 
666
        int                     disconnect = 0;
 
667
        PGresult   *res = NULL;
 
668
        Dlelem     *db_elem = DLGetHead(db_list);
 
669
        db_info    *dbi = NULL;
 
670
        db_info    *dbi_template1 = DLE_VAL(db_elem);
 
671
        int                     i = 0,
 
672
                                t = 0,
 
673
                                found_match = 0;
 
674
 
 
675
        if (args->debug >= 2)
 
676
        {
 
677
                log_entry("updating the database list", LVL_DEBUG);
 
678
                fflush(LOGOUTPUT);
 
679
        }
 
680
 
 
681
        if (dbi_template1->conn == NULL)
 
682
        {
 
683
                dbi_template1->conn = db_connect(dbi_template1);
 
684
                disconnect = 1;
 
685
        }
 
686
 
 
687
        if (dbi_template1->conn != NULL)
 
688
        {
 
689
                /*
 
690
                 * Get a result set that has all the information we will need to
 
691
                 * both remove databasews from the list that no longer exist and
 
692
                 * add databases to the list that are new
 
693
                 */
 
694
                res = send_query(FROZENOID_QUERY2, dbi_template1);
 
695
                if (res != NULL)
 
696
                {
 
697
                        t = PQntuples(res);
 
698
 
 
699
                        /*
 
700
                         * First: use the db_list as the outer loop and the result set
 
701
                         * as the inner loop, this will determine what databases
 
702
                         * should be removed
 
703
                         */
 
704
                        while (db_elem != NULL)
 
705
                        {
 
706
                                dbi = ((db_info *) DLE_VAL(db_elem));
 
707
                                found_match = 0;
 
708
 
 
709
                                for (i = 0; i < t; i++)
 
710
                                {                               /* loop through result set looking for a
 
711
                                                                 * match */
 
712
                                        if (dbi->oid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
 
713
                                        {
 
714
                                                found_match = 1;
 
715
 
 
716
                                                /*
 
717
                                                 * update the dbi->age so that we ensure
 
718
                                                 * xid_wraparound won't happen
 
719
                                                 */
 
720
                                                dbi->age = atol(PQgetvalue(res, i, PQfnumber(res, "age")));
 
721
                                                break;
 
722
                                        }
 
723
                                }
 
724
                                if (found_match == 0)
 
725
                                {                               /* then we didn't find this db_elem in the
 
726
                                                                 * result set */
 
727
                                        Dlelem     *elem_to_remove = db_elem;
 
728
 
 
729
                                        db_elem = DLGetSucc(db_elem);
 
730
                                        remove_db_from_list(elem_to_remove);
 
731
                                }
 
732
                                else
 
733
                                        db_elem = DLGetSucc(db_elem);
 
734
                        }                                       /* Done removing dropped databases from
 
735
                                                                 * the table_list */
 
736
 
 
737
                        /*
 
738
                         * Then loop use result set as outer loop and db_list as the
 
739
                         * inner loop to determine what databases are new
 
740
                         */
 
741
                        for (i = 0; i < t; i++)
 
742
                        {
 
743
                                db_elem = DLGetHead(db_list);
 
744
                                found_match = 0;
 
745
                                while (db_elem != NULL)
 
746
                                {
 
747
                                        dbi = ((db_info *) DLE_VAL(db_elem));
 
748
                                        if (dbi->oid == atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))))
 
749
                                        {
 
750
                                                found_match = 1;
 
751
                                                break;
 
752
                                        }
 
753
                                        db_elem = DLGetSucc(db_elem);
 
754
                                }
 
755
                                if (found_match == 0)   /* then we didn't find this result
 
756
                                                                                 * now in the tbl_list */
 
757
                                {
 
758
                                        DLAddTail(db_list, DLNewElem(init_dbinfo
 
759
                                                  (PQgetvalue(res, i, PQfnumber(res, "datname")),
 
760
                                           atooid(PQgetvalue(res, i, PQfnumber(res, "oid"))),
 
761
                                          atol(PQgetvalue(res, i, PQfnumber(res, "age"))))));
 
762
                                        if (args->debug >= 1)
 
763
                                        {
 
764
                                                sprintf(logbuffer, "added database: %s", ((db_info *) DLE_VAL(DLGetTail(db_list)))->dbname);
 
765
                                                log_entry(logbuffer, LVL_DEBUG);
 
766
                                        }
 
767
                                }
 
768
                        }                                       /* end of for loop that adds tables */
 
769
                }
 
770
                fflush(LOGOUTPUT);
 
771
                PQclear(res);
 
772
                res = NULL;
 
773
                if (args->debug >= 3)
 
774
                        print_db_list(db_list, 0);
 
775
                if (disconnect)
 
776
                        db_disconnect(dbi_template1);
 
777
        }
 
778
}
 
779
 
 
780
/* xid_wraparound_check
 
781
 
 
782
From the docs:
 
783
 
 
784
With the standard freezing policy, the age column will start at one billion for a
 
785
freshly-vacuumed database. When the age approaches two billion, the database must
 
786
be vacuumed again to avoid risk of wraparound failures. Recommended practice is
 
787
to vacuum each database at least once every half-a-billion (500 million) transactions,
 
788
so as to provide plenty of safety margin.
 
789
 
 
790
So we do a full database vacuum if age > 1.5billion
 
791
return 0 if nothing happened,
 
792
return 1 if the database needed a database wide vacuum
 
793
*/
 
794
static int
 
795
xid_wraparound_check(db_info * dbi)
 
796
{
 
797
        /*
 
798
         * FIXME: should probably do something better here so that we don't
 
799
         * vacuum all the databases on the server at the same time.  We have
 
800
         * 500million xacts to work with so we should be able to spread the
 
801
         * load of full database vacuums a bit
 
802
         */
 
803
        if (dbi->age > 1500000000)
 
804
        {
 
805
                PGresult   *res = NULL;
 
806
 
 
807
                res = send_query("VACUUM", dbi);
 
808
                /* FIXME: Perhaps should add a check for PQ_COMMAND_OK */
 
809
                if (res != NULL)
 
810
                        PQclear(res);
 
811
                return 1;
 
812
        }
 
813
        return 0;
 
814
}
 
815
 
 
816
/* Close DB connection, free memory, and remove the node from the list */
 
817
static void
 
818
remove_db_from_list(Dlelem *db_to_remove)
 
819
{
 
820
        db_info    *dbi = ((db_info *) DLE_VAL(db_to_remove));
 
821
 
 
822
        if (args->debug >= 1)
 
823
        {
 
824
                sprintf(logbuffer, "Removing db: %s from list.", dbi->dbname);
 
825
                log_entry(logbuffer, LVL_DEBUG);
 
826
                fflush(LOGOUTPUT);
 
827
        }
 
828
        DLRemove(db_to_remove);
 
829
        if (dbi->conn)
 
830
                db_disconnect(dbi);
 
831
        if (dbi->dbname)
 
832
        {
 
833
                free(dbi->dbname);
 
834
                dbi->dbname = NULL;
 
835
        }
 
836
        if (dbi->username)
 
837
        {
 
838
                free(dbi->username);
 
839
                dbi->username = NULL;
 
840
        }
 
841
        if (dbi->password)
 
842
        {
 
843
                free(dbi->password);
 
844
                dbi->password = NULL;
 
845
        }
 
846
        if (dbi->table_list)
 
847
        {
 
848
                free_tbl_list(dbi->table_list);
 
849
                dbi->table_list = NULL;
 
850
        }
 
851
        if (dbi)
 
852
        {
 
853
                free(dbi);
 
854
                dbi = NULL;
 
855
        }
 
856
        DLFreeElem(db_to_remove);
 
857
}
 
858
 
 
859
/* Function is called before program exit to free all memory
 
860
                mostly it's just to keep valgrind happy */
 
861
static void
 
862
free_db_list(Dllist *db_list)
 
863
{
 
864
        Dlelem     *db_elem = DLGetHead(db_list);
 
865
        Dlelem     *db_elem_to_remove = NULL;
 
866
 
 
867
        while (db_elem != NULL)
 
868
        {
 
869
                db_elem_to_remove = db_elem;
 
870
                db_elem = DLGetSucc(db_elem);
 
871
                remove_db_from_list(db_elem_to_remove);
 
872
                db_elem_to_remove = NULL;
 
873
        }
 
874
        DLFreeList(db_list);
 
875
}
 
876
 
 
877
static void
 
878
print_db_list(Dllist *db_list, int print_table_lists)
 
879
{
 
880
        Dlelem     *db_elem = DLGetHead(db_list);
 
881
 
 
882
        while (db_elem != NULL)
 
883
        {
 
884
                print_db_info(((db_info *) DLE_VAL(db_elem)), print_table_lists);
 
885
                db_elem = DLGetSucc(db_elem);
 
886
        }
 
887
}
 
888
 
 
889
static void
 
890
print_db_info(db_info * dbi, int print_tbl_list)
 
891
{
 
892
        sprintf(logbuffer, "dbname: %s", (dbi->dbname) ? dbi->dbname : "(null)");
 
893
        log_entry(logbuffer, LVL_INFO);
 
894
 
 
895
        sprintf(logbuffer, "  oid: %u", dbi->oid);
 
896
        log_entry(logbuffer, LVL_INFO);
 
897
 
 
898
        sprintf(logbuffer, "  username: %s", (dbi->username) ? dbi->username : "(null)");
 
899
        log_entry(logbuffer, LVL_INFO);
 
900
 
 
901
        sprintf(logbuffer, "  password: %s", (dbi->password) ? dbi->password : "(null)");
 
902
        log_entry(logbuffer, LVL_INFO);
 
903
 
 
904
        if (dbi->conn != NULL)
 
905
                log_entry("  conn is valid, (connected)", LVL_INFO);
 
906
        else
 
907
                log_entry("  conn is null, (not connected)", LVL_INFO);
 
908
 
 
909
        sprintf(logbuffer, "  default_analyze_threshold: %li", dbi->analyze_threshold);
 
910
        log_entry(logbuffer, LVL_INFO);
 
911
 
 
912
        sprintf(logbuffer, "  default_vacuum_threshold: %li", dbi->vacuum_threshold);
 
913
        log_entry(logbuffer, LVL_INFO);
 
914
 
 
915
        fflush(LOGOUTPUT);
 
916
        if (print_tbl_list > 0)
 
917
                print_table_list(dbi->table_list);
 
918
}
 
919
 
 
920
/* End of DB List Management Function */
 
921
 
 
922
/* Beginning of misc Functions */
 
923
 
 
924
/* Perhaps add some test to this function to make sure that the stats we need are available */
 
925
static PGconn *
 
926
db_connect(db_info * dbi)
 
927
{
 
928
        PGconn     *db_conn =
 
929
        PQsetdbLogin(args->host, args->port, NULL, NULL, dbi->dbname,
 
930
                                 dbi->username, dbi->password);
 
931
 
 
932
        if (PQstatus(db_conn) != CONNECTION_OK)
 
933
        {
 
934
                sprintf(logbuffer, "Failed connection to database %s with error: %s.",
 
935
                                dbi->dbname, PQerrorMessage(db_conn));
 
936
                log_entry(logbuffer, LVL_ERROR);
 
937
                fflush(LOGOUTPUT);
 
938
                PQfinish(db_conn);
 
939
                db_conn = NULL;
 
940
        }
 
941
 
 
942
        return db_conn;
 
943
}       /* end of db_connect() */
 
944
 
 
945
static void
 
946
db_disconnect(db_info * dbi)
 
947
{
 
948
        if (dbi->conn != NULL)
 
949
        {
 
950
                PQfinish(dbi->conn);
 
951
                dbi->conn = NULL;
 
952
        }
 
953
}
 
954
 
 
955
static int
 
956
check_stats_enabled(db_info * dbi)
 
957
{
 
958
        PGresult   *res;
 
959
        int                     ret = 0;
 
960
 
 
961
        res = send_query("SHOW stats_row_level", dbi);
 
962
        if (res != NULL)
 
963
        {
 
964
                ret = strcmp("on", PQgetvalue(res, 0, PQfnumber(res, "stats_row_level")));
 
965
                PQclear(res);
 
966
        }
 
967
        return ret;
 
968
}
 
969
 
 
970
static PGresult *
 
971
send_query(const char *query, db_info * dbi)
 
972
{
 
973
        PGresult   *res;
 
974
 
 
975
        if (dbi->conn == NULL)
 
976
                return NULL;
 
977
 
 
978
        if (args->debug >= 4)
 
979
                log_entry(query, LVL_DEBUG);
 
980
 
 
981
        res = PQexec(dbi->conn, query);
 
982
 
 
983
        if (!res)
 
984
        {
 
985
                sprintf(logbuffer,
 
986
                   "Fatal error occured while sending query (%s) to database %s",
 
987
                                query, dbi->dbname);
 
988
                log_entry(logbuffer, LVL_ERROR);
 
989
                sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res));
 
990
                log_entry(logbuffer, LVL_EXTRA);
 
991
                fflush(LOGOUTPUT);
 
992
                return NULL;
 
993
        }
 
994
        if (PQresultStatus(res) != PGRES_TUPLES_OK &&
 
995
                PQresultStatus(res) != PGRES_COMMAND_OK)
 
996
        {
 
997
                sprintf(logbuffer,
 
998
                  "Can not refresh statistics information from the database %s.",
 
999
                                dbi->dbname);
 
1000
                log_entry(logbuffer, LVL_ERROR);
 
1001
                sprintf(logbuffer, "The error is [%s]", PQresultErrorMessage(res));
 
1002
                log_entry(logbuffer, LVL_EXTRA);
 
1003
                fflush(LOGOUTPUT);
 
1004
                PQclear(res);
 
1005
                return NULL;
 
1006
        }
 
1007
        return res;
 
1008
}       /* End of send_query() */
 
1009
 
 
1010
/*
 
1011
 * Perform either a vacuum or a vacuum analyze
 
1012
 */
 
1013
static void
 
1014
perform_maintenance_command(db_info * dbi, tbl_info * tbl, int operation)
 
1015
{
 
1016
        char            buf[256];
 
1017
 
 
1018
        /*
 
1019
         * Set the vacuum_cost variables if supplied on command line
 
1020
         */
 
1021
        if (args->av_vacuum_cost_delay != -1)
 
1022
        {
 
1023
                snprintf(buf, sizeof(buf), "set vacuum_cost_delay = %d",
 
1024
                                 args->av_vacuum_cost_delay);
 
1025
                send_query(buf, dbi);
 
1026
        }
 
1027
        if (args->av_vacuum_cost_page_hit != -1)
 
1028
        {
 
1029
                snprintf(buf, sizeof(buf), "set vacuum_cost_page_hit = %d",
 
1030
                                 args->av_vacuum_cost_page_hit);
 
1031
                send_query(buf, dbi);
 
1032
        }
 
1033
        if (args->av_vacuum_cost_page_miss != -1)
 
1034
        {
 
1035
                snprintf(buf, sizeof(buf), "set vacuum_cost_page_miss = %d",
 
1036
                                 args->av_vacuum_cost_page_miss);
 
1037
                send_query(buf, dbi);
 
1038
        }
 
1039
        if (args->av_vacuum_cost_page_dirty != -1)
 
1040
        {
 
1041
                snprintf(buf, sizeof(buf), "set vacuum_cost_page_dirty = %d",
 
1042
                                 args->av_vacuum_cost_page_dirty);
 
1043
                send_query(buf, dbi);
 
1044
        }
 
1045
        if (args->av_vacuum_cost_limit != -1)
 
1046
        {
 
1047
                snprintf(buf, sizeof(buf), "set vacuum_cost_limit = %d",
 
1048
                                 args->av_vacuum_cost_limit);
 
1049
                send_query(buf, dbi);
 
1050
        }
 
1051
 
 
1052
        /*
 
1053
         * if ((relisshared = t and database != template1) or
 
1054
         * if operation = ANALYZE_ONLY)
 
1055
         * then only do an analyze
 
1056
         */
 
1057
        if ((tbl->relisshared > 0 && strcmp("template1", dbi->dbname) != 0) ||
 
1058
                (operation == ANALYZE_ONLY))
 
1059
                snprintf(buf, sizeof(buf), "ANALYZE %s", tbl->table_name);
 
1060
        else if (operation == VACUUM_ANALYZE)
 
1061
                snprintf(buf, sizeof(buf), "VACUUM ANALYZE %s", tbl->table_name);
 
1062
        else
 
1063
                return;
 
1064
 
 
1065
        if (args->debug >= 1)
 
1066
        {
 
1067
                sprintf(logbuffer, "Performing: %s", buf);
 
1068
                log_entry(logbuffer, LVL_DEBUG);
 
1069
                fflush(LOGOUTPUT);
 
1070
        }
 
1071
 
 
1072
        send_query(buf, dbi);
 
1073
 
 
1074
        update_table_thresholds(dbi, tbl, operation);
 
1075
 
 
1076
        if (args->debug >= 2)
 
1077
                print_table_info(tbl);
 
1078
}
 
1079
 
 
1080
static void
 
1081
free_cmd_args(void)
 
1082
{
 
1083
        if (args != NULL)
 
1084
        {
 
1085
                if (args->user != NULL)
 
1086
                        free(args->user);
 
1087
                if (args->password != NULL)
 
1088
                        free(args->password);
 
1089
                free(args);
 
1090
        }
 
1091
}
 
1092
 
 
1093
static cmd_args *
 
1094
get_cmd_args(int argc, char *argv[])
 
1095
{
 
1096
        int                     c;
 
1097
 
 
1098
        args = (cmd_args *) malloc(sizeof(cmd_args));
 
1099
        args->sleep_base_value = SLEEPBASEVALUE;
 
1100
        args->sleep_scaling_factor = SLEEPSCALINGFACTOR;
 
1101
        args->vacuum_base_threshold = VACBASETHRESHOLD;
 
1102
        args->vacuum_scaling_factor = VACSCALINGFACTOR;
 
1103
        args->analyze_base_threshold = -1;
 
1104
        args->analyze_scaling_factor = -1;
 
1105
        args->debug = AUTOVACUUM_DEBUG;
 
1106
#ifndef WIN32
 
1107
        args->daemonize = 0;
 
1108
#else
 
1109
    args->service_dependencies = 0;
 
1110
        args->install_as_service = 0;
 
1111
        args->remove_as_service = 0;
 
1112
        args->service_user = 0;
 
1113
        args->service_password = 0;
 
1114
#endif
 
1115
        args->user = 0;
 
1116
        args->password = 0;
 
1117
        args->host = 0;
 
1118
        args->logfile = 0;
 
1119
        args->port = 0;
 
1120
 
 
1121
        /*
 
1122
         * Cost-Based Vacuum Delay Settings for pg_autovacuum
 
1123
         */
 
1124
        args->av_vacuum_cost_delay = -1;
 
1125
        args->av_vacuum_cost_page_hit = -1;
 
1126
        args->av_vacuum_cost_page_miss = -1;
 
1127
        args->av_vacuum_cost_page_dirty = -1;
 
1128
        args->av_vacuum_cost_limit = -1;
 
1129
 
 
1130
        /*
 
1131
         * Fixme: Should add some sanity checking such as positive integer
 
1132
         * values etc
 
1133
         */
 
1134
#ifndef WIN32
 
1135
        while ((c = getopt(argc, argv, "s:S:v:V:a:A:d:U:P:H:L:p:hDc:C:m:n:l:")) != -1)
 
1136
#else
 
1137
        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)
 
1138
#endif
 
1139
        {
 
1140
                switch (c)
 
1141
                {
 
1142
                        case 's':
 
1143
                                args->sleep_base_value = atoi(optarg);
 
1144
                                break;
 
1145
                        case 'S':
 
1146
                                args->sleep_scaling_factor = atof(optarg);
 
1147
                                break;
 
1148
                        case 'v':
 
1149
                                args->vacuum_base_threshold = atoi(optarg);
 
1150
                                break;
 
1151
                        case 'V':
 
1152
                                args->vacuum_scaling_factor = atof(optarg);
 
1153
                                break;
 
1154
                        case 'a':
 
1155
                                args->analyze_base_threshold = atoi(optarg);
 
1156
                                break;
 
1157
                        case 'A':
 
1158
                                args->analyze_scaling_factor = atof(optarg);
 
1159
                                break;
 
1160
                        case 'c':
 
1161
                                args->av_vacuum_cost_delay = atoi(optarg);
 
1162
                                break;
 
1163
                        case 'C':
 
1164
                                args->av_vacuum_cost_page_hit = atoi(optarg);
 
1165
                                break;
 
1166
                        case 'm':
 
1167
                                args->av_vacuum_cost_page_miss = atoi(optarg);
 
1168
                                break;
 
1169
                        case 'n':
 
1170
                                args->av_vacuum_cost_page_dirty = atoi(optarg);
 
1171
                                break;
 
1172
                        case 'l':
 
1173
                                args->av_vacuum_cost_limit = atoi(optarg);
 
1174
                                break;
 
1175
#ifndef WIN32
 
1176
                        case 'D':
 
1177
                                args->daemonize++;
 
1178
                                break;
 
1179
#endif
 
1180
                        case 'd':
 
1181
                                args->debug = atoi(optarg);
 
1182
                                break;
 
1183
                        case 'U':
 
1184
                                args->user = optarg;
 
1185
                                break;
 
1186
                        case 'P':
 
1187
                                args->password = optarg;
 
1188
                                break;
 
1189
                        case 'H':
 
1190
                                args->host = optarg;
 
1191
                                break;
 
1192
                        case 'L':
 
1193
                                args->logfile = optarg;
 
1194
                                break;
 
1195
                        case 'p':
 
1196
                                args->port = optarg;
 
1197
                                break;
 
1198
                        case 'h':
 
1199
                                usage();
 
1200
                                exit(0);
 
1201
#ifdef WIN32
 
1202
                        case 'E':
 
1203
                                /*
 
1204
                                 * CreateService() expects a list of service
 
1205
                                 * dependencies as a NUL-separated, double-NUL
 
1206
                                 * terminated list (although we only allow the user to
 
1207
                                 * specify a single dependency). So we zero out the
 
1208
                                 * list first, and make sure to leave room for two NUL
 
1209
                                 * terminators.
 
1210
                                 */
 
1211
                                ZeroMemory(deps, sizeof(deps));
 
1212
                                snprintf(deps, sizeof(deps) - 2, "%s", optarg);
 
1213
                                args->service_dependencies = deps;
 
1214
                                break;
 
1215
                        case 'I':
 
1216
                                args->install_as_service++;
 
1217
                                break;
 
1218
                        case 'R':
 
1219
                                args->remove_as_service++;
 
1220
                                break;
 
1221
                        case 'N':
 
1222
                                args->service_user = optarg;
 
1223
                                break;
 
1224
                        case 'W':
 
1225
                                args->service_password = optarg;
 
1226
                                break;
 
1227
#endif
 
1228
                        default:
 
1229
 
 
1230
                                /*
 
1231
                                 * It's here that we know that things are invalid... It is
 
1232
                                 * not forcibly an error to call usage
 
1233
                                 */
 
1234
                                fprintf(stderr, "Error: Invalid Command Line Options.\n");
 
1235
                                usage();
 
1236
                                exit(1);
 
1237
                                break;
 
1238
                }
 
1239
 
 
1240
                /*
 
1241
                 * if values for insert thresholds are not specified, then they
 
1242
                 * default to 1/2 of the delete values
 
1243
                 */
 
1244
                if (args->analyze_base_threshold == -1)
 
1245
                        args->analyze_base_threshold = args->vacuum_base_threshold / 2;
 
1246
                if (args->analyze_scaling_factor == -1)
 
1247
                        args->analyze_scaling_factor = args->vacuum_scaling_factor / 2;
 
1248
        }
 
1249
        return args;
 
1250
}
 
1251
 
 
1252
static void
 
1253
usage(void)
 
1254
{
 
1255
        int                     i = 0;
 
1256
        float           f = 0;
 
1257
 
 
1258
        fprintf(stderr, "usage: pg_autovacuum \n");
 
1259
#ifndef WIN32
 
1260
        fprintf(stderr, "   [-D] Daemonize (Detach from tty and run in the background)\n");
 
1261
#else
 
1262
        fprintf(stderr, "   [-I] Install as a Windows service\n");
 
1263
        fprintf(stderr, "   [-R] Remove as a Windows service (all other options will be ignored)\n");
 
1264
        fprintf(stderr, "   [-N] Username to run service as (only useful when installing as a Windows service)\n");
 
1265
        fprintf(stderr, "   [-W] Password to run service with (only useful when installing as a Windows service)\n");
 
1266
        fprintf(stderr, "   [-E] Dependent service that must start before this service (only useful when installing as a Windows service)\n");
 
1267
#endif
 
1268
        i = AUTOVACUUM_DEBUG;
 
1269
        fprintf(stderr, "   [-d] debug (debug level=0,1,2,3; default=%d)\n", i);
 
1270
 
 
1271
        i = SLEEPBASEVALUE;
 
1272
        fprintf(stderr, "   [-s] sleep base value (default=%d)\n", i);
 
1273
        f = SLEEPSCALINGFACTOR;
 
1274
        fprintf(stderr, "   [-S] sleep scaling factor (default=%f)\n", f);
 
1275
 
 
1276
        i = VACBASETHRESHOLD;
 
1277
        fprintf(stderr, "   [-v] vacuum base threshold (default=%d)\n", i);
 
1278
        f = VACSCALINGFACTOR;
 
1279
        fprintf(stderr, "   [-V] vacuum scaling factor (default=%f)\n", f);
 
1280
        i = i / 2;
 
1281
        fprintf(stderr, "   [-a] analyze base threshold (default=%d)\n", i);
 
1282
        f = f / 2;
 
1283
        fprintf(stderr, "   [-A] analyze scaling factor (default=%f)\n", f);
 
1284
 
 
1285
        fprintf(stderr, "   [-L] logfile (default=none)\n");
 
1286
 
 
1287
        fprintf(stderr, "   [-c] vacuum_cost_delay (default=none)\n");
 
1288
        fprintf(stderr, "   [-C] vacuum_cost_page_hit (default=none)\n");
 
1289
        fprintf(stderr, "   [-m] vacuum_cost_page_miss (default=none)\n");
 
1290
        fprintf(stderr, "   [-n] vacuum_cost_page_dirty (default=none)\n");
 
1291
        fprintf(stderr, "   [-l] vacuum_cost_limit (default=none)\n");
 
1292
 
 
1293
        fprintf(stderr, "   [-U] username (libpq default)\n");
 
1294
        fprintf(stderr, "   [-P] password (libpq default)\n");
 
1295
        fprintf(stderr, "   [-H] host (libpq default)\n");
 
1296
        fprintf(stderr, "   [-p] port (libpq default)\n");
 
1297
 
 
1298
        fprintf(stderr, "   [-h] help (Show this output)\n");
 
1299
}
 
1300
 
 
1301
static void
 
1302
print_cmd_args(void)
 
1303
{
 
1304
        sprintf(logbuffer, "Printing command_args");
 
1305
        log_entry(logbuffer, LVL_INFO);
 
1306
        sprintf(logbuffer, "  args->host=%s", (args->host) ? args->host : "(null)");
 
1307
        log_entry(logbuffer, LVL_INFO);
 
1308
        sprintf(logbuffer, "  args->port=%s", (args->port) ? args->port : "(null)");
 
1309
        log_entry(logbuffer, LVL_INFO);
 
1310
        sprintf(logbuffer, "  args->username=%s", (args->user) ? args->user : "(null)");
 
1311
        log_entry(logbuffer, LVL_INFO);
 
1312
        sprintf(logbuffer, "  args->password=%s", (args->password) ? args->password : "(null)");
 
1313
        log_entry(logbuffer, LVL_INFO);
 
1314
        sprintf(logbuffer, "  args->logfile=%s", (args->logfile) ? args->logfile : "(null)");
 
1315
        log_entry(logbuffer, LVL_INFO);
 
1316
#ifndef WIN32
 
1317
        sprintf(logbuffer, "  args->daemonize=%d", args->daemonize);
 
1318
        log_entry(logbuffer, LVL_INFO);
 
1319
#else
 
1320
        sprintf(logbuffer, "  args->install_as_service=%d", args->install_as_service);
 
1321
        log_entry(logbuffer, LVL_INFO);
 
1322
        sprintf(logbuffer, "  args->remove_as_service=%d", args->remove_as_service);
 
1323
        log_entry(logbuffer, LVL_INFO);
 
1324
        sprintf(logbuffer, "  args->service_dependencies=%s", (args->service_dependencies) ? args->service_dependencies : "(null)");
 
1325
        log_entry(logbuffer, LVL_INFO);
 
1326
        sprintf(logbuffer, "  args->service_user=%s", (args->service_user) ? args->service_user : "(null)");
 
1327
        log_entry(logbuffer, LVL_INFO);
 
1328
        sprintf(logbuffer, "  args->service_password=%s", (args->service_password) ? args->service_password : "(null)");
 
1329
        log_entry(logbuffer, LVL_INFO);
 
1330
#endif
 
1331
 
 
1332
        sprintf(logbuffer, "  args->sleep_base_value=%d", args->sleep_base_value);
 
1333
        log_entry(logbuffer, LVL_INFO);
 
1334
        sprintf(logbuffer, "  args->sleep_scaling_factor=%f", args->sleep_scaling_factor);
 
1335
        log_entry(logbuffer, LVL_INFO);
 
1336
        sprintf(logbuffer, "  args->vacuum_base_threshold=%d", args->vacuum_base_threshold);
 
1337
        log_entry(logbuffer, LVL_INFO);
 
1338
        sprintf(logbuffer, "  args->vacuum_scaling_factor=%f", args->vacuum_scaling_factor);
 
1339
        log_entry(logbuffer, LVL_INFO);
 
1340
        sprintf(logbuffer, "  args->analyze_base_threshold=%d", args->analyze_base_threshold);
 
1341
        log_entry(logbuffer, LVL_INFO);
 
1342
        sprintf(logbuffer, "  args->analyze_scaling_factor=%f", args->analyze_scaling_factor);
 
1343
        log_entry(logbuffer, LVL_INFO);
 
1344
 
 
1345
        if (args->av_vacuum_cost_delay != -1)
 
1346
                sprintf(logbuffer, "  args->av_vacuum_cost_delay=%d", args->av_vacuum_cost_delay);
 
1347
        else
 
1348
                sprintf(logbuffer, "  args->av_vacuum_cost_delay=(default)");
 
1349
        log_entry(logbuffer, LVL_INFO);
 
1350
        if (args->av_vacuum_cost_page_hit != -1)
 
1351
                sprintf(logbuffer, "  args->av_vacuum_cost_page_hit=%d", args->av_vacuum_cost_page_hit);
 
1352
        else
 
1353
                sprintf(logbuffer, "  args->av_vacuum_cost_page_hit=(default)");
 
1354
        log_entry(logbuffer, LVL_INFO);
 
1355
        if (args->av_vacuum_cost_page_miss != -1)
 
1356
                sprintf(logbuffer, "  args->av_vacuum_cost_page_miss=%d", args->av_vacuum_cost_page_miss);
 
1357
        else
 
1358
                sprintf(logbuffer, "  args->av_vacuum_cost_page_miss=(default)");
 
1359
        log_entry(logbuffer, LVL_INFO);
 
1360
        if (args->av_vacuum_cost_page_dirty != -1)
 
1361
                sprintf(logbuffer, "  args->av_vacuum_cost_page_dirty=%d", args->av_vacuum_cost_page_dirty);
 
1362
        else
 
1363
                sprintf(logbuffer, "  args->av_vacuum_cost_page_dirty=(default)");
 
1364
        log_entry(logbuffer, LVL_INFO);
 
1365
        if (args->av_vacuum_cost_limit != -1)
 
1366
                sprintf(logbuffer, "  args->av_vacuum_cost_limit=%d", args->av_vacuum_cost_limit);
 
1367
        else
 
1368
                sprintf(logbuffer, "  args->av_vacuum_cost_limit=(default)");
 
1369
        log_entry(logbuffer, LVL_INFO);
 
1370
 
 
1371
        sprintf(logbuffer, "  args->debug=%d", args->debug);
 
1372
        log_entry(logbuffer, LVL_INFO);
 
1373
 
 
1374
        fflush(LOGOUTPUT);
 
1375
}
 
1376
 
 
1377
#ifdef WIN32
 
1378
 
 
1379
/* Handle control requests from the Service Control Manager */
 
1380
static void
 
1381
ControlHandler(DWORD request)
 
1382
{
 
1383
        switch (request)
 
1384
        {
 
1385
                case SERVICE_CONTROL_STOP:
 
1386
                case SERVICE_CONTROL_SHUTDOWN:
 
1387
                        log_entry("pg_autovacuum service stopping...", LVL_INFO);
 
1388
                        fflush(LOGOUTPUT);
 
1389
                        ServiceStatus.dwWin32ExitCode = 0;
 
1390
                        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
 
1391
                        SetServiceStatus(hStatus, &ServiceStatus);
 
1392
                        return;
 
1393
 
 
1394
                default:
 
1395
                        break;
 
1396
        }
 
1397
 
 
1398
        /* Report current status */
 
1399
        SetServiceStatus(hStatus, &ServiceStatus);
 
1400
 
 
1401
        return;
 
1402
}
 
1403
 
 
1404
/* Register with the Service Control Manager */
 
1405
static int
 
1406
InstallService(void)
 
1407
{
 
1408
        SC_HANDLE       schService = NULL;
 
1409
        SC_HANDLE       schSCManager = NULL;
 
1410
        char            szFilename[MAX_PATH],
 
1411
                                szKey[MAX_PATH],
 
1412
                                szCommand[MAX_PATH + 1024],
 
1413
                                szMsgDLL[MAX_PATH];
 
1414
        HKEY            hk = NULL;
 
1415
        DWORD           dwData = 0;
 
1416
 
 
1417
        /*
 
1418
         * Register the service with the SCM
 
1419
         */
 
1420
        GetModuleFileName(NULL, szFilename, MAX_PATH);
 
1421
 
 
1422
        /* Open the Service Control Manager on the local computer. */
 
1423
        schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
 
1424
        if (!schSCManager)
 
1425
                return -1;
 
1426
 
 
1427
        schService = CreateService(
 
1428
                                                           schSCManager,        /* SCManager database */
 
1429
                                                           TEXT("pg_autovacuum"),       /* Name of service */
 
1430
                                                           TEXT("PostgreSQL Auto Vacuum"),      /* Name to display */
 
1431
                                                           SERVICE_ALL_ACCESS,          /* Desired access */
 
1432
                                                           SERVICE_WIN32_OWN_PROCESS,           /* Service type */
 
1433
                                                           SERVICE_AUTO_START,          /* Start type */
 
1434
                                                           SERVICE_ERROR_NORMAL,        /* Error control type */
 
1435
                                                           szFilename,          /* Service binary */
 
1436
                                                           NULL,        /* No load ordering group */
 
1437
                                                           NULL,        /* No tag identifier */
 
1438
                                                           args->service_dependencies,  /* Dependencies */
 
1439
                                                           args->service_user,          /* Service account */
 
1440
                                                           args->service_password); /* Account password */
 
1441
 
 
1442
        if (!schService)
 
1443
                return -2;
 
1444
 
 
1445
        /*
 
1446
         * Rewrite the command line for the service
 
1447
         */
 
1448
        sprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\pg_autovacuum");
 
1449
        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_ALL_ACCESS, &hk))
 
1450
                return -3;
 
1451
 
 
1452
        /* Build the command line */
 
1453
        sprintf(szCommand, "\"%s\"", szFilename);
 
1454
        if (args->host)
 
1455
                sprintf(szCommand, "%s -H %s", szCommand, args->host);
 
1456
        if (args->port)
 
1457
                sprintf(szCommand, "%s -p %s", szCommand, args->port);
 
1458
        if (args->user)
 
1459
                sprintf(szCommand, "%s -U \"%s\"", szCommand, args->user);
 
1460
        if (args->password)
 
1461
                sprintf(szCommand, "%s -P \"%s\"", szCommand, args->password);
 
1462
        if (args->logfile)
 
1463
                sprintf(szCommand, "%s -L \"%s\"", szCommand, args->logfile);
 
1464
        if (args->sleep_base_value != (int) SLEEPBASEVALUE)
 
1465
                sprintf(szCommand, "%s -s %d", szCommand, args->sleep_base_value);
 
1466
        if (args->sleep_scaling_factor != (float) SLEEPSCALINGFACTOR)
 
1467
                sprintf(szCommand, "%s -S %f", szCommand, args->sleep_scaling_factor);
 
1468
        if (args->vacuum_base_threshold != (int) VACBASETHRESHOLD)
 
1469
                sprintf(szCommand, "%s -v %d", szCommand, args->vacuum_base_threshold);
 
1470
        if (args->vacuum_scaling_factor != (float) VACSCALINGFACTOR)
 
1471
                sprintf(szCommand, "%s -V %f", szCommand, args->vacuum_scaling_factor);
 
1472
        if (args->analyze_base_threshold != (int) (VACBASETHRESHOLD / 2))
 
1473
                sprintf(szCommand, "%s -a %d", szCommand, args->analyze_base_threshold);
 
1474
        if (args->analyze_scaling_factor != (float) (VACSCALINGFACTOR / 2))
 
1475
                sprintf(szCommand, "%s -A %f", szCommand, args->analyze_scaling_factor);
 
1476
        if (args->debug != (int) AUTOVACUUM_DEBUG)
 
1477
                sprintf(szCommand, "%s -d %d", szCommand, args->debug);
 
1478
        if (args->av_vacuum_cost_delay != -1)
 
1479
                sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_delay);
 
1480
        if (args->av_vacuum_cost_page_hit != -1)
 
1481
                sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_page_hit);
 
1482
        if (args->av_vacuum_cost_page_miss != -1)
 
1483
                sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_page_miss);
 
1484
        if (args->av_vacuum_cost_page_dirty != -1)
 
1485
                sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_page_dirty);
 
1486
        if (args->av_vacuum_cost_limit != -1)
 
1487
                sprintf(szCommand, "%s -d %d", szCommand, args->av_vacuum_cost_limit);
 
1488
 
 
1489
        /* And write the new value */
 
1490
        if (RegSetValueEx(hk, "ImagePath", 0, REG_EXPAND_SZ, (LPBYTE) szCommand, (DWORD) strlen(szCommand) + 1))
 
1491
                return -4;
 
1492
        RegCloseKey(hk);
 
1493
 
 
1494
        /*
 
1495
         * Set the Event source for the application log
 
1496
         */
 
1497
        sprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\PostgreSQL Auto Vacuum");
 
1498
        if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hk, NULL))
 
1499
                return -5;
 
1500
 
 
1501
        /* TODO Try to find pgevent.dll, rather than hope it's in the path. ! */
 
1502
        /* Message DLL */
 
1503
        sprintf(szMsgDLL, "pgevent.dll");
 
1504
        if (RegSetValueEx(hk, "EventMessageFile", 0, REG_EXPAND_SZ, (LPBYTE) szMsgDLL, (DWORD) strlen(szMsgDLL) + 1))
 
1505
                return -6;
 
1506
 
 
1507
        /* Set the event types supported */
 
1508
        dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | EVENTLOG_INFORMATION_TYPE | EVENTLOG_SUCCESS;
 
1509
        if (RegSetValueEx(hk, "TypesSupported", 0, REG_DWORD, (LPBYTE) & dwData, sizeof(DWORD)))
 
1510
                return -9;
 
1511
 
 
1512
        RegCloseKey(hk);
 
1513
        return 0;
 
1514
}
 
1515
 
 
1516
/* Unregister from the Service Control Manager */
 
1517
static int
 
1518
RemoveService(void)
 
1519
{
 
1520
        SC_HANDLE       schService = NULL;
 
1521
        SC_HANDLE       schSCManager = NULL;
 
1522
        char            szKey[MAX_PATH];
 
1523
        HKEY            hk = NULL;
 
1524
 
 
1525
        /* Open the SCM */
 
1526
        schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
 
1527
        if (!schSCManager)
 
1528
                return -1;
 
1529
 
 
1530
        /* Open the service */
 
1531
        schService = OpenService(schSCManager, TEXT("pg_autovacuum"), SC_MANAGER_ALL_ACCESS);
 
1532
        if (!schService)
 
1533
                return -2;
 
1534
 
 
1535
        /* Now delete the service */
 
1536
        if (!DeleteService(schService))
 
1537
                return -3;
 
1538
 
 
1539
        /*
 
1540
         * Remove the Event source from the application log
 
1541
         */
 
1542
        sprintf(szKey, "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application");
 
1543
        if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, szKey, 0, KEY_ALL_ACCESS, &hk))
 
1544
                return -4;
 
1545
        if (RegDeleteKey(hk, "PostgreSQL Auto Vacuum"))
 
1546
                return -5;
 
1547
 
 
1548
        return 0;
 
1549
}
 
1550
#endif   /* WIN32 */
 
1551
 
 
1552
static
 
1553
int
 
1554
VacuumLoop(int argc, char **argv)
 
1555
{
 
1556
        int                     j = 0,
 
1557
                                loops = 0;
 
1558
 
 
1559
        /* int numInserts, numDeletes, */
 
1560
        int                     sleep_secs;
 
1561
        Dllist     *db_list;
 
1562
        Dlelem     *db_elem,
 
1563
                           *tbl_elem;
 
1564
        db_info    *dbs;
 
1565
        tbl_info   *tbl;
 
1566
        PGresult   *res = NULL;
 
1567
        double          diff;
 
1568
 
 
1569
        struct timeval now,
 
1570
                                then;
 
1571
 
 
1572
#ifdef WIN32
 
1573
 
 
1574
        if (appMode)
 
1575
                log_entry("pg_autovacuum starting in Windows Application mode", LVL_INFO);
 
1576
        else
 
1577
                log_entry("pg_autovacuum starting in Windows Service mode", LVL_INFO);
 
1578
 
 
1579
        ServiceStatus.dwServiceType = SERVICE_WIN32;
 
1580
        ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
 
1581
        ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
 
1582
        ServiceStatus.dwWin32ExitCode = 0;
 
1583
        ServiceStatus.dwServiceSpecificExitCode = 0;
 
1584
        ServiceStatus.dwCheckPoint = 0;
 
1585
        ServiceStatus.dwWaitHint = 0;
 
1586
 
 
1587
        if (!appMode)
 
1588
        {
 
1589
                hStatus = RegisterServiceCtrlHandler("pg_autovacuum", (LPHANDLER_FUNCTION) ControlHandler);
 
1590
                if (hStatus == (SERVICE_STATUS_HANDLE) 0)
 
1591
                        return -1;
 
1592
        }
 
1593
#endif   /* WIN32 */
 
1594
 
 
1595
        /* Init the db list with template1 */
 
1596
        db_list = init_db_list();
 
1597
        if (db_list == NULL)
 
1598
                return 1;
 
1599
 
 
1600
        if (check_stats_enabled(((db_info *) DLE_VAL(DLGetHead(db_list)))) != 0)
 
1601
        {
 
1602
                log_entry("GUC variable stats_row_level must be enabled.", LVL_ERROR);
 
1603
                log_entry("       Please fix the problems and try again.", LVL_EXTRA);
 
1604
                fflush(LOGOUTPUT);
 
1605
 
 
1606
                exit(1);
 
1607
        }
 
1608
 
 
1609
        gettimeofday(&then, 0);         /* for use later to caluculate sleep time */
 
1610
 
 
1611
#ifndef WIN32
 
1612
        while (1)
 
1613
#else
 
1614
        /* We can now report the running status to SCM. */
 
1615
        ServiceStatus.dwCurrentState = SERVICE_RUNNING;
 
1616
        if (!appMode)
 
1617
                SetServiceStatus(hStatus, &ServiceStatus);
 
1618
 
 
1619
        while (ServiceStatus.dwCurrentState == SERVICE_RUNNING)
 
1620
#endif
 
1621
        {
 
1622
                /* Main Loop */
 
1623
 
 
1624
                db_elem = DLGetHead(db_list);   /* Reset cur_db_node to the
 
1625
                                                                                 * beginning of the db_list */
 
1626
 
 
1627
                dbs = ((db_info *) DLE_VAL(db_elem));   /* get pointer to cur_db's
 
1628
                                                                                                 * db_info struct */
 
1629
                if (dbs->conn == NULL)
 
1630
                {
 
1631
                        dbs->conn = db_connect(dbs);
 
1632
                        if (dbs->conn == NULL)
 
1633
                        {                                       /* Serious problem: We can't connect to
 
1634
                                                                 * template1 */
 
1635
                                log_entry("Cannot connect to template1, exiting.", LVL_ERROR);
 
1636
                                fflush(LOGOUTPUT);
 
1637
                                fclose(LOGOUTPUT);
 
1638
#ifdef WIN32
 
1639
                                ServiceStatus.dwCurrentState = SERVICE_STOPPED;
 
1640
                                ServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
 
1641
                                ServiceStatus.dwServiceSpecificExitCode = -1;
 
1642
                                if (!appMode)
 
1643
                                        SetServiceStatus(hStatus, &ServiceStatus);
 
1644
#endif
 
1645
                                exit(1);
 
1646
                        }
 
1647
                }
 
1648
 
 
1649
                if (loops % UPDATE_INTERVAL == 0)               /* Update the list if it's
 
1650
                                                                                                 * time */
 
1651
                        update_db_list(db_list);        /* Add and remove databases from
 
1652
                                                                                 * the list */
 
1653
 
 
1654
                while (db_elem != NULL)
 
1655
                {                                               /* Loop through databases in list */
 
1656
                        dbs = ((db_info *) DLE_VAL(db_elem));           /* get pointer to
 
1657
                                                                                                                 * cur_db's db_info
 
1658
                                                                                                                 * struct */
 
1659
                        if (dbs->conn == NULL)
 
1660
                                dbs->conn = db_connect(dbs);
 
1661
 
 
1662
                        if (dbs->conn != NULL)
 
1663
                        {
 
1664
                                if (loops % UPDATE_INTERVAL == 0)               /* Update the list if
 
1665
                                                                                                                 * it's time */
 
1666
                                        update_table_list(dbs);         /* Add and remove tables
 
1667
                                                                                                 * from the list */
 
1668
 
 
1669
                                if (xid_wraparound_check(dbs) == 0)
 
1670
                                {
 
1671
                                        res = send_query(TABLE_STATS_QUERY, dbs);       /* Get an updated
 
1672
                                                                                                                                 * snapshot of this dbs
 
1673
                                                                                                                                 * table stats */
 
1674
                                        if (res != NULL)
 
1675
                                        {
 
1676
                                                for (j = 0; j < PQntuples(res); j++)
 
1677
                                                {               /* loop through result set */
 
1678
                                                        tbl_elem = DLGetHead(dbs->table_list);          /* Reset tbl_elem to top
 
1679
                                                                                                                                                 * of dbs->table_list */
 
1680
                                                        while (tbl_elem != NULL)
 
1681
                                                        {       /* Loop through tables in list */
 
1682
                                                                tbl = ((tbl_info *) DLE_VAL(tbl_elem)); /* set tbl_info =
 
1683
                                                                                                                                                 * current_table */
 
1684
                                                                if (tbl->relid == atooid(PQgetvalue(res, j, PQfnumber(res, "oid"))))
 
1685
                                                                {
 
1686
                                                                        tbl->curr_analyze_count =
 
1687
                                                                                (atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_ins"))) +
 
1688
                                                                                 atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_upd"))) +
 
1689
                                                                                 atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_del"))));
 
1690
                                                                        tbl->curr_vacuum_count =
 
1691
                                                                                (atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_del"))) +
 
1692
                                                                                 atol(PQgetvalue(res, j, PQfnumber(res, "n_tup_upd"))));
 
1693
 
 
1694
                                                                        /*
 
1695
                                                                         * Check numDeletes to see if we need
 
1696
                                                                         * to vacuum, if so: Run vacuum
 
1697
                                                                         * analyze (adding analyze is small so
 
1698
                                                                         * we might as well) Update table
 
1699
                                                                         * thresholds and related information
 
1700
                                                                         * if numDeletes is not big enough for
 
1701
                                                                         * vacuum then check numInserts for
 
1702
                                                                         * analyze
 
1703
                                                                         */
 
1704
                                                                        if (tbl->curr_vacuum_count - tbl->CountAtLastVacuum >= tbl->vacuum_threshold)
 
1705
                                                                                perform_maintenance_command(dbs, tbl, VACUUM_ANALYZE);
 
1706
                                                                        else if (tbl->curr_analyze_count - tbl->CountAtLastAnalyze >= tbl->analyze_threshold)
 
1707
                                                                                perform_maintenance_command(dbs, tbl, ANALYZE_ONLY);
 
1708
 
 
1709
                                                                        break;          /* We found a match, no need to keep looping. */
 
1710
                                                                }
 
1711
 
 
1712
                                                                /*
 
1713
                                                                 * Advance the table pointers for the next
 
1714
                                                                 * loop
 
1715
                                                                 */
 
1716
                                                                tbl_elem = DLGetSucc(tbl_elem);
 
1717
 
 
1718
                                                        }       /* end for table while loop */
 
1719
                                                }               /* end for j loop (tuples in PGresult) */
 
1720
                                        }                       /* end if (res != NULL) */
 
1721
                                }                               /* close of if (xid_wraparound_check()) */
 
1722
                                /* Done working on this db, Clean up, then advance cur_db */
 
1723
                                PQclear(res);
 
1724
                                res = NULL;
 
1725
                                db_disconnect(dbs);
 
1726
                        }
 
1727
                        db_elem = DLGetSucc(db_elem);           /* move on to next DB
 
1728
                                                                                                 * regardless */
 
1729
                }                                               /* end of db_list while loop */
 
1730
 
 
1731
                /* Figure out how long to sleep etc ... */
 
1732
                gettimeofday(&now, 0);
 
1733
                diff = (int) (now.tv_sec - then.tv_sec) * 1000000.0 + (int) (now.tv_usec - then.tv_usec);
 
1734
 
 
1735
                sleep_secs = args->sleep_base_value + args->sleep_scaling_factor * diff / 1000000.0;
 
1736
                loops++;
 
1737
                if (args->debug >= 2)
 
1738
                {
 
1739
                        sprintf(logbuffer,
 
1740
                         "%d All DBs checked in: %.0f usec, will sleep for %d secs.",
 
1741
                                        loops, diff, sleep_secs);
 
1742
                        log_entry(logbuffer, LVL_DEBUG);
 
1743
                        fflush(LOGOUTPUT);
 
1744
                }
 
1745
 
 
1746
                pg_usleep(sleep_secs * 1000000);        /* Larger Pause between outer loops */
 
1747
 
 
1748
                gettimeofday(&then, 0); /* Reset time counter */
 
1749
 
 
1750
        }                                                       /* end of while loop */
 
1751
 
 
1752
        /*
 
1753
         * program is exiting, this should never run, but is here to make
 
1754
         * compiler / valgrind happy
 
1755
         */
 
1756
        free_db_list(db_list);
 
1757
        free_cmd_args();
 
1758
        return 0;
 
1759
}
 
1760
 
 
1761
/* Beginning of AutoVacuum Main Program */
 
1762
int
 
1763
main(int argc, char *argv[])
 
1764
{
 
1765
 
 
1766
#ifdef WIN32
 
1767
        LPVOID          lpMsgBuf;
 
1768
        SERVICE_TABLE_ENTRY ServiceTable[2];
 
1769
#endif
 
1770
 
 
1771
        args = get_cmd_args(argc, argv);        /* Get Command Line Args and put
 
1772
                                                                                 * them in the args struct */
 
1773
#ifndef WIN32
 
1774
        /* Dameonize if requested */
 
1775
        if (args->daemonize == 1)
 
1776
                daemonize();
 
1777
#endif
 
1778
 
 
1779
        if (args->logfile)
 
1780
        {
 
1781
                LOGOUTPUT = fopen(args->logfile, "a");
 
1782
                if (!LOGOUTPUT)
 
1783
                {
 
1784
                        fprintf(stderr, "Could not open log file - [%s]\n", args->logfile);
 
1785
                        exit(-1);
 
1786
                }
 
1787
        }
 
1788
        else
 
1789
                LOGOUTPUT = stderr;
 
1790
        if (args->debug >= 2)
 
1791
                print_cmd_args();
 
1792
 
 
1793
#ifdef WIN32
 
1794
        /* Install as a Windows service if required */
 
1795
        if (args->install_as_service)
 
1796
        {
 
1797
                if (InstallService() != 0)
 
1798
                {
 
1799
                        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) & lpMsgBuf, 0, NULL);
 
1800
            fprintf(stderr, "Error: %s\n", (char *) lpMsgBuf);
 
1801
                        exit(-1);
 
1802
                }
 
1803
                else
 
1804
                {
 
1805
                        fprintf(stderr, "Successfully installed pg_autovacuum as a service.\n");
 
1806
                        exit(0);
 
1807
                }
 
1808
        }
 
1809
 
 
1810
        /* Remove as a Windows service if required */
 
1811
        if (args->remove_as_service)
 
1812
        {
 
1813
                if (RemoveService() != 0)
 
1814
                {
 
1815
                        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) & lpMsgBuf, 0, NULL);
 
1816
            fprintf(stderr, "Error: %s\n", (char *) lpMsgBuf);
 
1817
                        exit(-1);
 
1818
                }
 
1819
                else
 
1820
                {
 
1821
                        fprintf(stderr, "Successfully removed pg_autovacuum as a service.\n");
 
1822
                        exit(0);
 
1823
                }
 
1824
        }
 
1825
 
 
1826
        /* Normal service startup */
 
1827
        ServiceTable[0].lpServiceName = "pg_autovacuum";
 
1828
        ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION) VacuumLoop;
 
1829
 
 
1830
        ServiceTable[1].lpServiceName = NULL;
 
1831
        ServiceTable[1].lpServiceProc = NULL;
 
1832
 
 
1833
        /* Start the control dispatcher thread for our service */
 
1834
        if (!StartServiceCtrlDispatcher(ServiceTable))
 
1835
        {
 
1836
                appMode = 1;
 
1837
                VacuumLoop(0, NULL);
 
1838
        }
 
1839
 
 
1840
#else                                                   /* Unix */
 
1841
 
 
1842
        /* Call the main program loop. */
 
1843
        VacuumLoop(0, NULL);
 
1844
#endif   /* WIN32 */
 
1845
 
 
1846
        return EXIT_SUCCESS;
 
1847
}