1 /*
2 * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc. All Rights Reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * for more details.
13 */
14
15 #include <ctype.h>
16 #include <limits.h>
17 #include <sys/stat.h>
18 #include "logger.h"
19
20 char *configfile;
21 __pmLogCtl logctl;
22 int exit_samples = -1; /* number of samples 'til exit */
23 __int64_t exit_bytes = -1; /* number of bytes 'til exit */
24 __int64_t vol_bytes; /* total in earlier volumes */
25 struct timeval exit_time; /* time interval 'til exit */
26 int vol_switch_samples = -1; /* number of samples 'til vol switch */
27 __int64_t vol_switch_bytes = -1; /* number of bytes 'til vol switch */
28 struct timeval vol_switch_time; /* time interval 'til vol switch */
29 int vol_samples_counter; /* Counts samples - reset for new vol*/
30 int vol_switch_afid = -1; /* afid of event for vol switch */
31 int parse_done;
32 int primary; /* Non-zero for primary pmlogger */
33 char *archBase; /* base name for log files */
34 char *pmcd_host;
35 struct timeval epoch;
36 int archive_version = PM_LOG_VERS02; /* Type of archive to create */
37 int linger; /* linger with no tasks/events */
38 int rflag; /* report sizes */
39 struct timeval delta = { 60, 0 }; /* default logging interval */
40 int unbuffered; /* is -u specified? */
41 int qa_case; /* QA error injection state */
42
43 static int pmcdfd; /* comms to pmcd */
44 static int ctx; /* handle correspondong to ctxp below */
45 static __pmContext *ctxp; /* pmlogger has just this one context */
46 static fd_set fds; /* file descriptors mask for select */
47 static int numfds; /* number of file descriptors in mask */
48
49 static int rsc_fd = -1; /* recording session control see -x */
50 static int rsc_replay;
51 static time_t rsc_start;
52 static char *rsc_prog = "<unknown>";
53 static char *folio_name = "<unknown>";
54 static char *dialog_title = "PCP Archive Recording Session";
55
56 /*
57 * flush stdio buffers
58 */
59 int
60 do_flush(void)
61 {
62 int sts;
63
64 sts = 0;
65 if (fflush(logctl.l_mdfp) != 0)
66 sts = oserror();
67 if (fflush(logctl.l_mfp) != 0 && sts == 0)
68 sts = oserror();
69 if (fflush(logctl.l_tifp) != 0 && sts == 0)
70 sts = oserror();
71
72 return sts;
73 }
74
75 void
76 run_done(int sts, char *msg)
77 {
78 #ifdef PCP_DEBUG
79 if (msg != NULL)
80 fprintf(stderr, "pmlogger: %s, exiting\n", msg);
81 else
82 fprintf(stderr, "pmlogger: End of run time, exiting\n");
83 #endif
84
85 /*
86 * write the last last temportal index entry with the time stamp
87 * of the last pmResult and the seek pointer set to the offset
88 * _before_ the last log record
89 */
90 if (last_stamp.tv_sec != 0) {
91 __pmTimeval tmp;
92 tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
93 tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
94 fseek(logctl.l_mfp, last_log_offset, SEEK_SET);
95 __pmLogPutIndex(&logctl, &tmp);
96 }
97
98 exit(sts);
99 }
100
101 static void
102 run_done_callback(int i, void *j)
103 {
104 run_done(0, NULL);
105 }
106
107 static void
108 vol_switch_callback(int i, void *j)
109 {
110 newvolume(VOL_SW_TIME);
111 }
112
113 static int
114 maxfd(void)
115 {
116 int max = ctlfd;
117 if (clientfd > max)
118 max = clientfd;
119 if (pmcdfd > max)
120 max = pmcdfd;
121 if (rsc_fd > max)
122 max = rsc_fd;
123 return max;
124 }
125
126 /*
127 * tolower_str - convert a string to all lowercase
128 */
129 static void
130 tolower_str(char *str)
131 {
132 char *s = str;
133 while(*s){
134 *s = tolower(*s);
135 s++;
136 }
137 }
138
139 /*
140 * ParseSize - parse a size argument given in a command option
141 *
142 * The size can be in one of the following forms:
143 * "40" = sample counter of 40
144 * "40b" = byte size of 40
145 * "40Kb" = byte size of 40*1024 bytes = 40 kilobytes
146 * "40Mb" = byte size of 40*1024*1024 bytes = 40 megabytes
147 * time-format = time delta in seconds
148 *
149 */
150 static int
151 ParseSize(char *size_arg, int *sample_counter, __int64_t *byte_size,
152 struct timeval *time_delta)
153 {
154 long x = 0; /* the size number */
155 char *ptr = NULL;
156
157 *sample_counter = -1;
158 *byte_size = -1;
159 time_delta->tv_sec = -1;
160 time_delta->tv_usec = -1;
161
162 x = strtol(size_arg, &ptr, 10);
163
164 /* must be positive */
165 if (x <= 0) {
166 return -1;
167 }
168
169 if (*ptr == '\0') {
170 /* we have consumed entire string as a long */
171 /* => we have a sample counter */
172 *sample_counter = x;
173 return 1;
174 }
175
176 if (ptr != size_arg) {
177 /* we have a number followed by something else */
178
179 tolower_str(ptr);
180
181 /* chomp off plurals */
182 {
183 int len = strlen(ptr);
184 if (ptr[len-1] == 's')
185 ptr[len-1] = '\0';
186 }
187
188 /* if bytes */
189 if (strcmp(ptr, "b") == 0 ||
190 strcmp(ptr, "byte") == 0) {
191 *byte_size = x;
192 return 1;
193 }
194
195 /* if kilobytes */
196 if (strcmp(ptr, "k") == 0 ||
197 strcmp(ptr, "kb") == 0 ||
198 strcmp(ptr, "kbyte") == 0 ||
199 strcmp(ptr, "kilobyte") == 0) {
200 *byte_size = x*1024;
201 return 1;
202 }
203
204 /* if megabytes */
205 if (strcmp(ptr, "m") == 0 ||
206 strcmp(ptr, "mb") == 0 ||
207 strcmp(ptr, "mbyte") == 0 ||
208 strcmp(ptr, "megabyte") == 0) {
209 *byte_size = x*1024*1024;
210 return 1;
211 }
212
213 /* if gigabytes */
214 if (strcmp(ptr, "g") == 0 ||
215 strcmp(ptr, "gb") == 0 ||
216 strcmp(ptr, "gbyte") == 0 ||
217 strcmp(ptr, "gigabyte") == 0) {
218 *byte_size = ((__int64_t)x)*1024*1024*1024;
219 return 1;
220 }
221
222 }
223
224 /* Doesn't fit pattern above, try a time interval */
225 {
226 char *interval_err;
227
|
Event alloc_arg: |
Calling allocation function "pmParseInterval" on "interval_err". [details] |
| Also see events: |
[leaked_storage] |
|
At conditional (1): "pmParseInterval(size_arg, time_delta, &interval_err) >= 0": Taking false branch.
|
228 if (pmParseInterval(size_arg, time_delta, &interval_err) >= 0) {
229 return 1;
230 }
|
Event leaked_storage: |
Variable "interval_err" going out of scope leaks the storage it points to. |
| Also see events: |
[alloc_arg] |
231 }
232
233 /* Doesn't match anything, return an error */
234 return -1;
235 }/*ParseSize*/
236
237 /* time manipulation */
238 static void
239 tsub(struct timeval *a, struct timeval *b)
240 {
241 a->tv_usec -= b->tv_usec;
242 if (a->tv_usec < 0) {
243 a->tv_usec += 1000000;
244 a->tv_sec--;
245 }
246 a->tv_sec -= b->tv_sec;
247 if (a->tv_sec < 0) {
248 /* clip negative values at zero */
249 a->tv_sec = 0;
250 a->tv_usec = 0;
251 }
252 }
253
254 static char *
255 do_size(double d)
256 {
257 static char nbuf[100];
258
259 if (d < 10 * 1024)
260 sprintf(nbuf, "%ld bytes", (long)d);
261 else if (d < 10.0 * 1024 * 1024)
262 sprintf(nbuf, "%.1f Kbytes", d/1024);
263 else if (d < 10.0 * 1024 * 1024 * 1024)
264 sprintf(nbuf, "%.1f Mbytes", d/(1024 * 1024));
265 else
266 sprintf(nbuf, "%ld Mbytes", (long)d/(1024 * 1024));
267
268 return nbuf;
269 }
270
271 /*
272 * add text identified by p to the malloc buffer at bp[0] ... bp[nchar -1]
273 * return the length of the result or -1 for an error
274 */
275 static int
276 add_msg(char **bp, int nchar, char *p)
277 {
278 int add_len;
279
280 if (nchar < 0 || p == NULL)
281 return nchar;
282
283 add_len = (int)strlen(p);
284 if (nchar == 0)
285 add_len++;
286 if ((*bp = realloc(*bp, nchar+add_len)) == NULL)
287 return -1;
288 if (nchar == 0)
289 strcpy(*bp, p);
290 else
291 strcat(&(*bp)[nchar-1], p);
292
293 return nchar+add_len;
294 }
295
296
297 /*
298 * generate dialog/message when launching application wishes to break
299 * its association with pmlogger
300 *
301 * cmd is one of the following:
302 * D detach pmlogger and let it run forever
303 * Q terminate pmlogger
304 * ? display status
305 * X fatal error or application exit ... user must decide
306 * to detach or quit
307 */
308 void
309 do_dialog(char cmd)
310 {
311 FILE *msgf = NULL;
312 time_t now;
313 static char lbuf[100+MAXPATHLEN];
314 double archsize;
315 char *q;
316 char *p = NULL;
317 int nchar;
318 char *msg;
319 #if HAVE_MKSTEMP
320 char tmp[MAXPATHLEN];
321 #endif
322
323 /*
324 * flush archive buffers so size is accurate
325 */
326 do_flush();
327
328 time(&now);
329 now -= rsc_start;
330 if (now == 0)
331 /* hack is close enough! */
332 now = 1;
333
334 archsize = vol_bytes + ftell(logctl.l_mfp);
335
336 nchar = add_msg(&p, 0, "");
337 p[0] = '\0';
338
339 sprintf(lbuf, "PCP recording for the archive folio \"%s\" and the host", folio_name);
340 nchar = add_msg(&p, nchar, lbuf);
341 sprintf(lbuf, " \"%s\" has been in progress for %ld %s",
342 pmcd_host,
343 now < 240 ? now : now/60, now < 240 ? "seconds" : "minutes");
344 nchar = add_msg(&p, nchar, lbuf);
345 nchar = add_msg(&p, nchar, " and in that time the pmlogger process has created an");
346 nchar = add_msg(&p, nchar, " archive of ");
347 q = do_size(archsize);
348 nchar = add_msg(&p, nchar, q);
349 nchar = add_msg(&p, nchar, ".");
350 if (rsc_replay) {
351 nchar = add_msg(&p, nchar, "\n\nThis archive may be replayed with the following command:\n");
352 sprintf(lbuf, " $ pmafm %s replay", folio_name);
353 nchar = add_msg(&p, nchar, lbuf);
354 }
355
356 if (cmd == 'D') {
357 nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has asked pmlogger");
358 nchar = add_msg(&p, nchar, " to continue independently and the PCP archive will grow at");
359 nchar = add_msg(&p, nchar, " the rate of ");
360 q = do_size((archsize * 3600) / now);
361 nchar = add_msg(&p, nchar, q);
362 nchar = add_msg(&p, nchar, " per hour or ");
363 q = do_size((archsize * 3600 * 24) / now);
364 nchar = add_msg(&p, nchar, q);
365 nchar = add_msg(&p, nchar, " per day.");
366 }
367
368 if (cmd == 'X') {
369 nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has exited and you");
370 nchar = add_msg(&p, nchar, " must decide if the PCP recording session should be terminated");
371 nchar = add_msg(&p, nchar, " or continued. If recording is continued the PCP archive will");
372 nchar = add_msg(&p, nchar, " grow at the rate of ");
373 q = do_size((archsize * 3600) / now);
374 nchar = add_msg(&p, nchar, q);
375 nchar = add_msg(&p, nchar, " per hour or ");
376 q = do_size((archsize * 3600 * 24) / now);
377 nchar = add_msg(&p, nchar, q);
378 nchar = add_msg(&p, nchar, " per day.");
379 }
380
381 if (cmd == 'Q') {
382 nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has terminated");
383 nchar = add_msg(&p, nchar, " this PCP recording session.\n");
384 }
385
386 if (cmd != 'Q') {
387 nchar = add_msg(&p, nchar, "\n\nAt any time this pmlogger process may be terminated with the");
388 nchar = add_msg(&p, nchar, " following command:\n");
389 sprintf(lbuf, " $ pmsignal -s TERM %" FMT_PID "\n", getpid());
390 nchar = add_msg(&p, nchar, lbuf);
391 }
392
393 if (cmd == 'X')
394 nchar = add_msg(&p, nchar, "\n\nTerminate this PCP recording session now?");
395
396 if (nchar > 0) {
397 char * xconfirm = __pmNativePath(pmGetConfig("PCP_XCONFIRM_PROG"));
398 int fd = -1;
399
400 #if HAVE_MKSTEMP
401 sprintf(tmp, "%s%cmsgXXXXXX", pmGetConfig("PCP_TMP_DIR"), __pmPathSeparator());
402 msg = tmp;
403 fd = mkstemp(tmp);
404 #else
405 if ((msg = tmpnam(NULL)) != NULL)
406 fd = open(msg, O_WRONLY|O_CREAT|O_EXCL, 0600);
407 #endif
408 if (fd >= 0)
409 msgf = fdopen(fd, "w");
410 if (msgf == NULL) {
411 fprintf(stderr, "\nError: failed create temporary message file for recording session dialog\n");
412 fprintf(stderr, "Reason? %s\n", osstrerror());
413 if (fd != -1)
414 close(fd);
415 goto failed;
416 }
417 fputs(p, msgf);
418 fclose(msgf);
419 msgf = NULL;
420
421 if (cmd == 'X')
422 sprintf(lbuf, "%s -c -header \"%s - %s\" -file %s -icon question "
423 "-B Yes -b No 2>/dev/null",
424 xconfirm, dialog_title, rsc_prog, msg);
425 else
426 sprintf(lbuf, "%s -c -header \"%s - %s\" -file %s -icon info "
427 "-b Close 2>/dev/null",
428 xconfirm, dialog_title, rsc_prog, msg);
429
430 if ((msgf = popen(lbuf, "r")) == NULL) {
431 fprintf(stderr, "\nError: failed to start command for recording session dialog\n");
432 fprintf(stderr, "Command: \"%s\"\n", lbuf);
433 goto failed;
434 }
435
436 if (fgets(lbuf, sizeof(lbuf), msgf) == NULL) {
437 fprintf(stderr, "\n%s: pmconfirm(1) failed for recording session dialog\n",
438 cmd == '?' ? "Warning" : "Error");
439 failed:
440 fprintf(stderr, "Dialog:\n");
441 fputs(p, stderr);
442 strcpy(lbuf, "Yes");
443 }
444 else {
445 /* strip at first newline */
446 for (q = lbuf; *q && *q != '\n'; q++)
447 ;
448 *q = '\0';
449 }
450
451 if (msgf != NULL)
452 pclose(msgf);
453 unlink(msg);
454 }
455 else {
456 fprintf(stderr, "Error: failed to create recording session dialog message!\n");
457 fprintf(stderr, "Reason? %s\n", osstrerror());
458 strcpy(lbuf, "Yes");
459 }
460
461 free(p);
462
463 if (cmd == 'Q' || (cmd == 'X' && strcmp(lbuf, "Yes") == 0)) {
464 run_done(0, "Recording session terminated");
465 }
466
467 if (cmd != '?') {
468 /* detach, silently go off to the races ... */
469 close(rsc_fd);
470 FD_CLR(rsc_fd, &fds);
471 rsc_fd = -1;
472 }
473 }
474
475
476 int
477 main(int argc, char **argv)
478 {
479 int c;
480 int sts;
481 int sep = __pmPathSeparator();
482 int errflag = 0;
483 char local[MAXHOSTNAMELEN];
484 char *pmnsfile = PM_NS_DEFAULT;
485 char *logfile = "pmlogger.log";
486 /* default log (not archive) file name */
487 char *endnum;
488 int i;
489 task_t *tp;
490 optcost_t ocp;
491 fd_set readyfds;
492 char *p;
493 char *runtime = NULL;
494
495 __pmSetProgname(argv[0]);
496
497 /*
498 * Warning:
499 * If any of the pmlogger options change, make sure the
500 * corresponding changes are made to pmnewlog when pmlogger
501 * options are passed through from the control file
502 */
503 while ((c = getopt(argc, argv, "c:D:h:l:Ln:Prs:T:t:uv:V:x:?")) != EOF) {
504 switch (c) {
505
506 case 'c': /* config file */
507 if (access(optarg, F_OK) == 0)
508 configfile = optarg;
509 else {
510 /* does not exist as given, try the standard place */
511 char *vardir = pmGetConfig("PCP_VAR_DIR");
512 int sz = strlen(vardir)+strlen("/config/pmlogger/")+strlen(optarg)+1;
513 if ( (configfile = (char *)malloc(sz)) == NULL ) {
514 __pmNoMem("config file name", sz, PM_FATAL_ERR);
515 }
516 sprintf(configfile,
517 "%s%c" "config" "%c" "pmlogger" "%c%s",
518 vardir, sep, sep, sep, optarg);
519 if (access(configfile, F_OK) != 0) {
520 /* still no good, error handling happens below */
521 free(configfile);
522 configfile = optarg;
523 }
524 }
525 break;
526
527 case 'D': /* debug flag */
528 sts = __pmParseDebug(optarg);
529 if (sts < 0) {
530 fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
531 pmProgname, optarg);
532 errflag++;
533 }
534 else
535 pmDebug |= sts;
536 break;
537
538 case 'h': /* hostname for PMCD to contact */
539 pmcd_host = optarg;
540 break;
541
542 case 'l': /* log file name */
543 logfile = optarg;
544 break;
545
546 case 'L': /* linger if not primary logger */
547 linger = 1;
548 break;
549
550 case 'n': /* alternative name space file */
551 pmnsfile = optarg;
552 break;
553
554 case 'P': /* this is the primary pmlogger */
555 primary = 1;
556 break;
557
558 case 'r': /* report sizes of pmResult records */
559 rflag = 1;
560 break;
561
562 case 's': /* exit size */
563 if (ParseSize(optarg, &exit_samples, &exit_bytes,
564 &exit_time) < 0) {
565 fprintf(stderr, "%s: illegal size argument '%s' for -s\n",
566 pmProgname, optarg);
567 errflag++;
568 }
569 if (exit_time.tv_sec > 0) {
570 __pmAFregister(&exit_time, NULL, run_done_callback);
571 }
572 break;
573
574 case 'T': /* end time */
575 runtime = optarg;
576 break;
577
578 case 't': /* change default logging interval */
579 if (pmParseInterval(optarg, &delta, &p) < 0) {
580 fprintf(stderr, "%s: illegal -t argument\n", pmProgname);
581 fputs(p, stderr);
582 free(p);
583 errflag++;
584 }
585 break;
586
587 case 'u': /* flush output buffers after each fetch */
588 unbuffered = 1;
589 break;
590
591 case 'v': /* volume switch after given size */
592 if (ParseSize(optarg, &vol_switch_samples, &vol_switch_bytes,
593 &vol_switch_time) < 0) {
594 fprintf(stderr, "%s: illegal size argument '%s' for -v\n",
595 pmProgname, optarg);
596 errflag++;
597 }
598 if (vol_switch_time.tv_sec > 0) {
599 vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
600 vol_switch_callback);
601 }
602 break;
603
604 case 'V':
605 archive_version = (int)strtol(optarg, &endnum, 10);
606 if (*endnum != '\0' ||
607 (archive_version != PM_LOG_VERS01 &&
608 archive_version != PM_LOG_VERS02)) {
609 fprintf(stderr, "%s: -V requires a version number of "
610 "%d or %d\n", pmProgname,
611 PM_LOG_VERS01, PM_LOG_VERS02);
612 errflag++;
613 }
614 break;
615
616 case 'x': /* recording session control fd */
617 rsc_fd = (int)strtol(optarg, &endnum, 10);
618 if (*endnum != '\0' || rsc_fd < 0) {
619 fprintf(stderr, "%s: -x requires a non-negative numeric argument\n", pmProgname);
620 errflag++;
621 }
622 time(&rsc_start);
623 break;
624
625 case '?':
626 default:
627 errflag++;
628 break;
629 }
630 }
631
632 if (errflag || optind != argc-1) {
633 fprintf(stderr,
634 "Usage: %s [options] archive\n\
635 \n\
636 Options:\n\
637 -c configfile file to load configuration from\n\
638 -h host metrics source is PMCD on host\n\
639 -l logfile redirect diagnostics and trace output\n\
640 -L linger, even if not primary logger instance and nothing to log\n\
641 -n pmnsfile use an alternative PMNS\n\
642 -P execute as primary logger instance\n\
643 -r report record sizes and archive growth rate\n\
644 -s endsize terminate after endsize has been accumulated\n\
645 -t interval default logging interval [default 60.0 seconds]\n\
646 -T endtime terminate at given time\n\
647 -u output is unbuffered\n\
648 -v volsize switch log volumes after volsize has been accumulated\n\
649 -V version generate version 1 or 2 archives (default is 2)\n\
650 -x fd control file descriptor for application launching pmlogger\n\
651 via pmRecordControl(3)\n",
652 pmProgname);
653 exit(1);
654 }
655
656 if (primary && pmcd_host != NULL) {
657 fprintf(stderr, "%s: -P and -h are mutually exclusive ... use -P only when running\n%s on the same (local) host as the PMCD to which it connects.\n", pmProgname, pmProgname);
658 exit(1);
659 }
660
661 __pmOpenLog("pmlogger", logfile, stderr, &sts);
662
663 /* base name for archive is here ... */
664 archBase = argv[optind];
665
666 if (pmcd_host == NULL || strcmp(pmcd_host, "localhost") == 0) {
667 (void)gethostname(local, MAXHOSTNAMELEN);
668 local[MAXHOSTNAMELEN-1] = '\0';
669 pmcd_host = local;
670 }
671
672 /* initialise access control */
673 if (__pmAccAddOp(PM_OP_LOG_ADV) < 0 ||
674 __pmAccAddOp(PM_OP_LOG_MAND) < 0 ||
675 __pmAccAddOp(PM_OP_LOG_ENQ) < 0) {
676 fprintf(stderr, "%s: access control initialisation failed\n", pmProgname);
677 exit(1);
678 }
679
680 if (pmnsfile != PM_NS_DEFAULT) {
681 if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
682 fprintf(stderr, "%s: Cannot load namespace from \"%s\": %s\n", pmProgname, pmnsfile, pmErrStr(sts));
683 exit(1);
684 }
685 }
686
687 if ((ctx = pmNewContext(PM_CONTEXT_HOST, pmcd_host)) < 0) {
688 fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n", pmProgname, pmcd_host, pmErrStr(ctx));
689 exit(1);
690 }
691
692 if (rsc_fd == -1) {
693 /* no -x, so register client id with pmcd */
694 __pmSetClientIdArgv(argc, argv);
695 }
696
697 /*
698 * discover fd for comms channel to PMCD ...
699 */
700 ctxp = __pmHandleToPtr(ctx);
701 pmcdfd = ctxp->c_pmcd->pc_fd;
702 pmcd_host = ctxp->c_pmcd->pc_hosts[0].name;
703
704 if (configfile != NULL) {
705 if ((yyin = fopen(configfile, "r")) == NULL) {
706 fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
707 pmProgname, configfile, osstrerror());
708 exit(1);
709 }
710 }
711 else {
712 /* **ANY** Lex would read from stdin automagically */
713 configfile = "<stdin>";
714 }
715
716 __pmOptFetchGetParams(&ocp);
717 ocp.c_scope = 1;
718 __pmOptFetchPutParams(&ocp);
719
720 /* prevent early timer events ... */
721 __pmAFblock();
722
723 if (yyparse() != 0)
724 exit(1);
725
726 if ( configfile != NULL ) {
727 fclose(yyin);
728 }
729
730 #ifdef PCP_DEBUG
731 fprintf(stderr, "Config parsed\n");
732 #endif
733
734 fprintf(stderr, "Starting %slogger for host \"%s\"\n",
735 primary ? "primary " : "", pmcd_host);
736
737 #ifdef PCP_DEBUG
738 if (pmDebug & DBG_TRACE_LOG) {
739 fprintf(stderr, "optFetch Cost Parameters: pmid=%d indom=%d fetch=%d scope=%d\n",
740 ocp.c_pmid, ocp.c_indom, ocp.c_fetch, ocp.c_scope);
741
742 fprintf(stderr, "\nAfter loading config ...\n");
743 for (tp = tasklist; tp != NULL; tp = tp->t_next) {
744 fprintf(stderr, " state: %sin log, %savail, %s, %s",
745 PMLC_GET_INLOG(tp->t_state) ? "" : "not ",
746 PMLC_GET_AVAIL(tp->t_state) ? "" : "un",
747 PMLC_GET_MAND(tp->t_state) ? "mand" : "adv",
748 PMLC_GET_ON(tp->t_state) ? "on" : "off");
749 fprintf(stderr, " delta: %ld usec",
750 (long)1000 * tp->t_delta.tv_sec + tp->t_delta.tv_usec);
751 fprintf(stderr, " numpmid: %d\n", tp->t_numpmid);
752 for (i = 0; i < tp->t_numpmid; i++) {
753 fprintf(stderr, " %s (%s):\n", pmIDStr(tp->t_pmidlist[i]), tp->t_namelist[i]);
754 }
755 __pmOptFetchDump(stderr, tp->t_fetch);
756 }
757 }
758 #endif
759
760 if (!primary && tasklist == NULL && !linger) {
761 fprintf(stderr, "Nothing to log, and not the primary logger instance ... good-bye\n");
762 exit(1);
763 }
764
765 if ((sts = __pmLogCreate(pmcd_host, archBase, archive_version, &logctl)) < 0) {
766 fprintf(stderr, "__pmLogCreate: %s\n", pmErrStr(sts));
767 exit(1);
768 }
769 else {
770 /*
771 * try and establish $TZ from the remote PMCD ...
772 * Note the label record has been set up, but not written yet
773 */
774 char *name = "pmcd.timezone";
775 pmID pmid;
776 pmResult *resp;
777
778 __pmtimevalNow(&epoch);
779 sts = pmUseContext(ctx);
780 if (sts >= 0)
781 sts = pmLookupName(1, &name, &pmid);
782 if (sts >= 0) {
783 sts = pmFetch(1, &pmid, &resp);
784 }
785 if (sts >= 0 && resp->vset[0]->numval > 0) {
786 strcpy(logctl.l_label.ill_tz, resp->vset[0]->vlist[0].value.pval->vbuf);
787 /* prefer to use remote time to avoid clock drift problems */
788 epoch = resp->timestamp; /* struct assignment */
789 pmFreeResult(resp);
790 pmNewZone(logctl.l_label.ill_tz);
791 }
792 #ifdef PCP_DEBUG
793 else if (pmDebug & DBG_TRACE_LOG) {
794 fprintf(stderr,
795 "main: Could not get timezone from host %s\n",
796 pmcd_host);
797 }
798 #endif
799 }
800
801 /* do ParseTimeWindow stuff for -T */
802 if (runtime) {
803 struct timeval res_end; /* time window end */
804 struct timeval start;
805 struct timeval end;
806 struct timeval last_delta;
807 char *err_msg; /* parsing error message */
808 time_t now;
809 struct timeval now_tv;
810
811 time(&now);
812 now_tv.tv_sec = now;
813 now_tv.tv_usec = 0;
814
815 start = now_tv;
816 end.tv_sec = INT_MAX;
817 end.tv_usec = INT_MAX;
818 sts = __pmParseTime(runtime, &start, &end, &res_end, &err_msg);
819 if (sts < 0) {
820 fprintf(stderr, "%s: illegal -T argument\n%s", pmProgname, err_msg);
821 exit(1);
822 }
823
824 last_delta = res_end;
825 tsub(&last_delta, &now_tv);
826 __pmAFregister(&last_delta, NULL, run_done_callback);
827
828 last_stamp = res_end;
829 }
830
831 fprintf(stderr, "Archive basename: %s\n", archBase);
832
833 #ifndef IS_MINGW
834 /* detach yourself from the launching process */
835 setpgid(getpid(), 0);
836 #endif
837
838 /* set up control port */
839 init_ports();
840 FD_ZERO(&fds);
841 FD_SET(ctlfd, &fds);
842 #ifndef IS_MINGW
843 FD_SET(pmcdfd, &fds);
844 #endif
845 if (rsc_fd != -1)
846 FD_SET(rsc_fd, &fds);
847 numfds = maxfd() + 1;
848
849 if ((sts = do_preamble()) < 0)
850 fprintf(stderr, "Warning: problem writing archive preamble: %s\n",
851 pmErrStr(sts));
852
853 sts = 0; /* default exit status */
854
855 parse_done = 1; /* enable callback processing */
856 __pmAFunblock();
857
858 for ( ; ; ) {
859 int nready;
860
861 memcpy(&readyfds, &fds, sizeof(readyfds));
862 nready = select(numfds, &readyfds, NULL, NULL, NULL);
863
864 if (wantflush) {
865 /*
866 * flush request via SIGUSR1
867 */
868 do_flush();
869 wantflush = 0;
870 }
871
872 if (nready > 0) {
873 /* block signals to simplify IO handling */
874 __pmAFblock();
875
876 /* handle request on control port */
877 if (FD_ISSET(ctlfd, &readyfds)) {
878 if (control_req()) {
879 /* new client has connected */
880 FD_SET(clientfd, &fds);
881 if (clientfd >= numfds)
882 numfds = clientfd + 1;
883 }
884 }
885 if (clientfd >= 0 && FD_ISSET(clientfd, &readyfds)) {
886 /* process request from client, save clientfd in case client
887 * closes connection, resetting clientfd to -1
888 */
889 int fd = clientfd;
890
891 if (client_req()) {
892 /* client closed connection */
893 FD_CLR(fd, &fds);
894 numfds = maxfd() + 1;
895 qa_case = 0;
896 }
897 }
898 #ifndef IS_MINGW
899 if (pmcdfd >= 0 && FD_ISSET(pmcdfd, &readyfds)) {
900 /*
901 * do not expect this, given synchronous commumication with the
902 * pmcd ... either pmcd has terminated, or bogus PDU ... or its
903 * Win32 and we are operating under the different conditions of
904 * our AF.c implementation there, which has to deal with a lack
905 * of signal support on Windows - race condition exists between
906 * this check and the async event timer callback.
907 */
908 __pmPDU *pb;
909 __pmPDUHdr *php;
910 sts = __pmGetPDU(pmcdfd, ANY_SIZE, TIMEOUT_NEVER, &pb);
911 if (sts <= 0) {
912 if (sts < 0)
913 fprintf(stderr, "Error: __pmGetPDU: %s\n", pmErrStr(sts));
914 disconnect(sts);
915 }
916 else {
917 php = (__pmPDUHdr *)pb;
918 fprintf(stderr, "Error: Unsolicited %s PDU from PMCD\n",
919 __pmPDUTypeStr(php->type));
920 disconnect(PM_ERR_IPC);
921 }
922 }
923 #endif
924 if (rsc_fd >= 0 && FD_ISSET(rsc_fd, &readyfds)) {
925 /*
926 * some action on the recording session control fd
927 * end-of-file means launcher has quit, otherwise we
928 * expect one of these commands
929 * V<number>\n - version
930 * F<folio>\n - folio name
931 * P<name>\n - launcher's name
932 * R\n - launcher can replay
933 * D\n - detach from launcher
934 * Q\n - quit pmlogger
935 */
936 char rsc_buf[MAXPATHLEN];
937 char *rp = rsc_buf;
938 char myc;
939 int fake_x = 0;
940
941 for (rp = rsc_buf; ; rp++) {
942 if (read(rsc_fd, &myc, 1) <= 0) {
943 #ifdef PCP_DEBUG
944 if (pmDebug & DBG_TRACE_APPL2)
945 fprintf(stderr, "recording session control: eof\n");
946 #endif
947 if (rp != rsc_buf) {
948 *rp = '\0';
949 fprintf(stderr, "Error: incomplete recording session control message: \"%s\"\n", rsc_buf);
950 }
951 fake_x = 1;
952 break;
953 }
954 if (rp >= &rsc_buf[MAXPATHLEN]) {
955 fprintf(stderr, "Error: absurd recording session control message: \"%100.100s ...\"\n", rsc_buf);
956 fake_x = 1;
957 break;
958 }
959 if (myc == '\n') {
960 *rp = '\0';
961 break;
962 }
963 *rp = myc;
964 }
965
966 #ifdef PCP_DEBUG
967 if (pmDebug & DBG_TRACE_APPL2) {
968 if (fake_x == 0)
969 fprintf(stderr, "recording session control: \"%s\"\n", rsc_buf);
970 }
971 #endif
972
973 if (fake_x)
974 do_dialog('X');
975 else if (strcmp(rsc_buf, "Q") == 0 ||
976 strcmp(rsc_buf, "D") == 0 ||
977 strcmp(rsc_buf, "?") == 0)
978 do_dialog(rsc_buf[0]);
979 else if (rsc_buf[0] == 'F')
980 folio_name = strdup(&rsc_buf[1]);
981 else if (rsc_buf[0] == 'P')
982 rsc_prog = strdup(&rsc_buf[1]);
983 else if (strcmp(rsc_buf, "R") == 0)
984 rsc_replay = 1;
985 else if (rsc_buf[0] == 'V' && rsc_buf[1] == '0') {
986 /*
987 * version 0 of the recording session control ...
988 * this is all we grok at the moment
989 */
990 ;
991 }
992 else {
993 fprintf(stderr, "Error: illegal recording session control message: \"%s\"\n", rsc_buf);
994 do_dialog('X');
995 }
996 }
997
998 __pmAFunblock();
999 }
1000 else if (nready < 0 && neterror() != EINTR)
1001 fprintf(stderr, "Error: select: %s\n", netstrerror());
1002 }
1003
1004 }
1005
1006
1007 int
1008 newvolume(int vol_switch_type)
1009 {
1010 FILE *newfp;
1011 int nextvol = logctl.l_curvol + 1;
1012 time_t now;
1013 static char *vol_sw_strs[] = {
1014 "SIGHUP", "pmlc request", "sample counter",
1015 "sample byte size", "sample time", "max data volume size"
1016 };
1017
1018 vol_samples_counter = 0;
1019 vol_bytes += ftell(logctl.l_mfp);
1020 if (exit_bytes != -1) {
1021 if (vol_bytes >= exit_bytes)
1022 run_done(0, "Byte limit reached");
1023 }
1024
1025 /*
1026 * If we are not part of a callback but instead a
1027 * volume switch from "pmlc" or a "SIGHUP" then
1028 * get rid of pending volume switch in event queue
1029 * as it will now be wrong, and schedule a new
1030 * volume switch event.
1031 */
1032 if (vol_switch_afid >= 0 && vol_switch_type != VOL_SW_TIME) {
1033 __pmAFunregister(vol_switch_afid);
1034 vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
1035 vol_switch_callback);
1036 }
1037
1038 if ((newfp = __pmLogNewFile(archBase, nextvol)) != NULL) {
1039 if (logctl.l_state == PM_LOG_STATE_NEW) {
1040 /*
1041 * nothing has been logged as yet, force out the label records
1042 */
1043 __pmtimevalNow(&last_stamp);
1044 logctl.l_label.ill_start.tv_sec = (__int32_t)last_stamp.tv_sec;
1045 logctl.l_label.ill_start.tv_usec = (__int32_t)last_stamp.tv_usec;
1046 logctl.l_label.ill_vol = PM_LOG_VOL_TI;
1047 __pmLogWriteLabel(logctl.l_tifp, &logctl.l_label);
1048 logctl.l_label.ill_vol = PM_LOG_VOL_META;
1049 __pmLogWriteLabel(logctl.l_mdfp, &logctl.l_label);
1050 logctl.l_label.ill_vol = 0;
1051 __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
1052 logctl.l_state = PM_LOG_STATE_INIT;
1053 }
1054 #if 0
1055 if (last_stamp.tv_sec != 0) {
1056 __pmTimeval tmp;
1057 tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
1058 tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
1059 __pmLogPutIndex(&logctl, &tmp);
1060 }
1061 #endif
1062 fclose(logctl.l_mfp);
1063 logctl.l_mfp = newfp;
1064 logctl.l_label.ill_vol = logctl.l_curvol = nextvol;
1065 __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
1066 fflush(logctl.l_mfp);
1067 time(&now);
1068 fprintf(stderr, "New log volume %d, via %s at %s",
1069 nextvol, vol_sw_strs[vol_switch_type], ctime(&now));
1070 return nextvol;
1071 }
1072 else
1073 return -oserror();
1074 }
1075
1076
1077 void
1078 disconnect(int sts)
1079 {
1080 time_t now;
1081
1082 time(&now);
1083 if (sts != 0)
1084 fprintf(stderr, "%s: Error: %s\n", pmProgname, pmErrStr(sts));
1085 fprintf(stderr, "%s: Lost connection to PMCD on \"%s\" at %s",
1086 pmProgname, ctxp->c_pmcd->pc_hosts[0].name, ctime(&now));
1087 #if CAN_RECONNECT
1088 if (primary) {
1089 fprintf(stderr, "This is fatal for the primary logger.");
1090 exit(1);
1091 }
1092 close(pmcdfd);
1093 FD_CLR(pmcdfd, &fds);
1094 pmcdfd = -1;
1095 numfds = maxfd() + 1;
1096 ctxp->c_pmcd->pc_fd = -1;
1097 #else
1098 exit(1);
1099 #endif
1100 }
1101
1102 #if CAN_RECONNECT
1103 int
1104 reconnect(void)
1105 {
1106 int sts;
1107
1108 sts = pmReconnectContext(ctx);
1109 if (sts >= 0) {
1110 time_t now;
1111 time(&now);
1112 fprintf(stderr, "%s: re-established connection to PMCD on \"%s\" at %s\n",
1113 pmProgname, ctxp->c_pmcd->pc_name, ctime(&now));
1114 pmcdfd = ctxp->c_pmcd->pc_fd;
1115 FD_SET(pmcdfd, &fds);
1116 numfds = maxfd() + 1;
1117 }
1118 return sts;
1119 }
1120 #endif
1121
1122