2 * sadf: system activity data formatter
3 * (C) 1999-2016 by Sebastien GODARD (sysstat <at> orange.fr)
5 ***************************************************************************
6 * This program is free software; you can redistribute it and/or modify it *
7 * under the terms of the GNU General Public License as published by the *
8 * Free Software Foundation; either version 2 of the License, or (at your *
9 * option) any later version. *
11 * This program is distributed in the hope that it will be useful, but *
12 * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY *
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License *
16 * You should have received a copy of the GNU General Public License along *
17 * with this program; if not, write to the Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA *
19 ***************************************************************************
36 # include <locale.h> /* For setlocale() */
39 # define _(string) gettext(string)
41 # define _(string) (string)
44 #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__
45 char *sccsid(void) { return (SCCSID); }
47 long interval = -1, count = 0;
49 unsigned int flags = 0;
50 unsigned int dm_major; /* Device-mapper major number */
51 unsigned int format = 0; /* Output format */
52 unsigned int f_position = 0; /* Output format position in array */
55 struct file_header file_hdr;
59 * This array must always be entirely filled (even with trailing zeros).
61 unsigned int id_seq[NR_ACT];
62 /* Total number of SVG graphs for each activity */
65 /* Current record header */
66 struct record_header record_hdr[3];
68 /* Contain the date specified by -s and -e options */
69 struct tstamp tm_start, tm_end;
70 char *args[MAX_ARGV_NR];
72 extern struct activity *act[];
73 extern struct report_format *fmt[];
76 ***************************************************************************
77 * Print usage and exit.
80 * @progname Name of sysstat command.
81 ***************************************************************************
83 void usage(char *progname)
86 _("Usage: %s [ options ] [ <interval> [ <count> ] ] [ <datafile> | -[0-9]+ ]\n"),
89 fprintf(stderr, _("Options are:\n"
90 "[ -C ] [ -c | -d | -g | -j | -p | -x ] [ -H ] [ -h ] [ -T | -t | -U ] [ -V ]\n"
91 "[ -O <opts> [,...] ] [ -P { <cpu> [,...] | ALL } ]\n"
92 "[ -s [ <hh:mm[:ss]> ] ] [ -e [ <hh:mm[:ss]> ] ]\n"
93 "[ -- <sar_options> ]\n"));
98 ***************************************************************************
100 ***************************************************************************
102 void init_structures(void)
106 for (i = 0; i < 3; i++) {
107 memset(&record_hdr[i], 0, RECORD_HEADER_SIZE);
112 ***************************************************************************
113 * Look for output format in array.
116 * @fmt Array of output formats.
117 * @format Output format to look for.
120 * Position of output format in array.
121 ***************************************************************************
123 int get_format_position(struct report_format *fmt[], unsigned int format)
127 for (i = 0; i < NR_FMT; i++) {
128 if (fmt[i]->id == format)
133 /* Should never happen */
140 ***************************************************************************
141 * Check that options entered on the command line are consistent with
142 * selected output format. If no output format has been explicitly entered,
143 * then select a default one.
144 ***************************************************************************
146 void check_format_options(void)
149 /* Select output format if none has been selected */
150 if (DISPLAY_HDR_ONLY(flags)) {
151 format = F_HEADER_OUTPUT;
154 format = F_PPC_OUTPUT;
158 /* Get format position in array */
159 f_position = get_format_position(fmt, format);
161 /* Check options consistency wrt output format */
162 if (!ACCEPT_HEADER_ONLY(fmt[f_position]->options)) {
163 /* Remove option -H */
164 flags &= ~S_F_HDR_ONLY;
166 if (!ACCEPT_HORIZONTALLY(fmt[f_position]->options)) {
167 /* Remove option -h */
168 flags &= ~S_F_HORIZONTALLY;
170 if (!ACCEPT_LOCAL_TIME(fmt[f_position]->options)) {
171 /* Remove options -T and -t */
172 flags &= ~(S_F_LOCAL_TIME + S_F_TRUE_TIME);
174 if (!ACCEPT_SEC_EPOCH(fmt[f_position]->options)) {
175 /* Remove option -U */
176 flags &= ~S_F_SEC_EPOCH;
178 if (REJECT_TRUE_TIME(fmt[f_position]->options)) {
179 /* Remove option -t */
180 flags &= ~S_F_TRUE_TIME;
185 ***************************************************************************
186 * Save or restore number of items for all known activities.
189 * @save_act_nr Array containing number of items to restore for each
191 * @action DO_SAVE to save number of items, or DO_RESTORE to restore.
194 * @save_act_nr Array containing number of items saved for each activity.
195 ***************************************************************************
197 void sr_act_nr(__nr_t save_act_nr[], int action)
201 if (action == DO_SAVE) {
202 /* Save number of items for all activities */
203 for (i = 0; i < NR_ACT; i++) {
204 save_act_nr[i] = act[i]->nr;
207 else if (action == DO_RESTORE) {
209 * Restore number of items for all activities
210 * and reallocate structures accordingly.
212 for (i = 0; i < NR_ACT; i++) {
213 if (save_act_nr[i] > 0) {
214 reallocate_vol_act_structures(act, save_act_nr[i],
222 ***************************************************************************
223 * Read next sample statistics. If it's a special record (R_RESTART or
224 * R_COMMENT) then display it if requested. Also fill timestamps structures.
227 * @ifd File descriptor
228 * @action Flags indicating if special records should be displayed or
230 * @curr Index in array for current sample statistics.
231 * @file System activity data file name (name of file being read).
232 * @tab Number of tabulations to print.
233 * @file_actlst List of (known or unknown) activities in file.
234 * @file_magic System activity file magic header.
235 * @rectime Structure where timestamp (expressed in local time or in UTC
236 * depending on whether options -T/-t have been used or not) can
237 * be saved for current record.
238 * @loctime Structure where timestamp (expressed in local time) can be
239 * saved for current record.
242 * @rtype Type of record read (R_RESTART, R_COMMENT, etc.)
243 * @rectime Structure where timestamp (expressed in local time or in UTC
244 * depending on options used) has been saved for current record.
245 * If current record was a special one (RESTART or COMMENT) and
246 * noted to be ignored, then the timestamp is saved only if
247 * explicitly told to do so with the SET_TIMESTAMPS action flag.
248 * @loctime Structure where timestamp (expressed in local time) has been
249 * saved for current record.
250 * If current record was a special one (RESTART or COMMENT) and
251 * noted to be ignored, then the timestamp is saved only if
252 * explicitly told to do so with the SET_TIMESTAMPS action flag.
255 * TRUE if end of file has been reached.
256 ***************************************************************************
258 int read_next_sample(int ifd, int action, int curr, char *file, int *rtype, int tab,
259 struct file_magic *file_magic, struct file_activity *file_actlst,
260 struct tm *rectime, struct tm *loctime)
264 /* Read current record */
265 eosaf = sa_fread(ifd, &record_hdr[curr], RECORD_HEADER_SIZE, SOFT_SIZE);
266 *rtype = record_hdr[curr].record_type;
269 if (*rtype == R_COMMENT) {
270 if (action & IGNORE_COMMENT) {
271 /* Ignore COMMENT record */
272 if (lseek(ifd, MAX_COMMENT_LEN, SEEK_CUR) < MAX_COMMENT_LEN) {
275 if (action & SET_TIMESTAMPS) {
276 sa_get_record_timestamp_struct(flags, &record_hdr[curr],
281 /* Display COMMENT record */
282 print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end,
283 *rtype, ifd, rectime, loctime, file, tab,
284 file_magic, &file_hdr, act, fmt[f_position]);
287 else if (*rtype == R_RESTART) {
288 if (action & IGNORE_RESTART) {
290 * Ignore RESTART record (don't display it)
291 * but anyway we have to reallocate volatile
292 * activities structures (unless we don't want to
295 if (!(action & DONT_READ_VOLATILE)) {
296 read_vol_act_structures(ifd, act, file, file_magic,
297 file_hdr.sa_vol_act_nr);
299 if (action & SET_TIMESTAMPS) {
300 sa_get_record_timestamp_struct(flags, &record_hdr[curr],
305 /* Display RESTART record */
306 print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end,
307 *rtype, ifd, rectime, loctime, file, tab,
308 file_magic, &file_hdr, act, fmt[f_position]);
313 * OK: Previous record was not a special one.
314 * So read now the extra fields.
316 read_file_stat_bunch(act, curr, ifd, file_hdr.sa_act_nr,
318 sa_get_record_timestamp_struct(flags, &record_hdr[curr], rectime, loctime);
326 ***************************************************************************
327 * Display the field list (used eg. in database format).
330 * @act_d Activity to display, or ~0 for all.
331 ***************************************************************************
333 void list_fields(unsigned int act_id)
338 char hline[HEADER_LINE_LEN] = "";
340 printf("# hostname;interval;timestamp");
342 for (i = 0; i < NR_ACT; i++) {
344 if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id))
347 if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) {
348 if (!HAS_MULTIPLE_OUTPUTS(act[i]->options)) {
349 printf(";%s", act[i]->hdr_line);
350 if ((act[i]->nr > 1) && DISPLAY_HORIZONTALLY(flags)) {
356 strncpy(hline, act[i]->hdr_line, HEADER_LINE_LEN - 1);
357 hline[HEADER_LINE_LEN - 1] = '\0';
358 for (hl = strtok(hline, "|"); hl; hl = strtok(NULL, "|"), msk <<= 1) {
359 if ((hl != NULL) && ((act[i]->opt_flags & 0xff) & msk)) {
360 if (strchr(hl, '&')) {
361 j = strcspn(hl, "&");
362 if ((act[i]->opt_flags & 0xff00) & (msk << 8)) {
363 /* Display whole header line */
368 /* Display only the first part of the header line */
377 if ((act[i]->nr > 1) && DISPLAY_HORIZONTALLY(flags)) {
389 ***************************************************************************
390 * Determine the time (expressed in seconds since the epoch) used as the
391 * origin on X axis for SVG graphs. If S_F_SVG_ONE_DAY is set, then origin
392 * will be the beginning of current day (00:00:00) else it will be the time
393 * of the first sample collected.
396 * Time origin on X axis (expressed in seconds since the epoch).
397 ***************************************************************************
399 time_t get_time_ref(void)
404 if (DISPLAY_ONE_DAY(flags)) {
405 ltm = localtime((time_t *) &(record_hdr[2].ust_time));
407 /* Move back to midnight */
408 ltm->tm_sec = ltm->tm_min = ltm->tm_hour = 0;
415 return record_hdr[2].ust_time;
419 ***************************************************************************
420 * Compute the number of SVG graphs to display. Each activity selected may
421 * have several graphs. Moreover we have to take into account volatile
422 * activities (eg. CPU) for which the number of graphs will depend on the
423 * highest number of items (eg. maximum number of CPU) saved in the file.
424 * This number may be higher than the real number of graphs that will be
425 * displayed since some items have a preallocation constant.
428 * @ifd File descriptor of input file.
429 * @file Name of file being read.
430 * @file_magic file_magic structure filled with file magic header data.
431 * @file_actlst List of (known or unknown) activities in file.
432 * @rectime Structure where timestamp (expressed in local time or in UTC
433 * depending on whether options -T/-t have been used or not) can
434 * be saved for current record.
435 * @loctime Structure where timestamp (expressed in local time) can be
436 * saved for current record.
439 * Total number of graphs to display, taking into account only activities
440 * to be displayed, and selected period of time (options -s/-e).
441 ***************************************************************************
443 int get_svg_graph_nr(int ifd, char *file, struct file_magic *file_magic,
444 struct file_activity *file_actlst, struct tm *rectime,
448 int rtype, new_tot_g_nr, tot_g_nr = 0;
450 __nr_t save_act_nr[NR_ACT] = {0};
452 /* Save current file position and items number */
453 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
457 sr_act_nr(save_act_nr, DO_SAVE);
459 /* Init total number of graphs for each activity */
460 for (i = 0; i < NR_ACT; i++) {
464 /* Look for the first record that will be displayed */
466 eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
467 0, file, &rtype, 0, file_magic, file_actlst,
470 /* No record to display => no graph too */
473 while ((tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
474 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)));
479 for (i = 0; i < NR_ACT; i++) {
483 p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND);
484 if (!IS_SELECTED(act[p]->options))
487 if (ONE_GRAPH_PER_ITEM(act[p]->options)) {
488 n = act[p]->g_nr * act[p]->nr;
494 if (n > id_g_nr[i]) {
500 if (new_tot_g_nr > tot_g_nr) {
501 tot_g_nr = new_tot_g_nr;
505 eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
506 0, file, &rtype, 0, file_magic, file_actlst,
509 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)))
510 /* End of data file or end time exceeded */
513 while (rtype != R_RESTART);
516 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)))
518 * End of file, or end time exceeded:
519 * Current number of graphs is up-to-date.
524 * If we have found a RESTART record then we have also read the list of volatile
525 * activities following it, reallocated the structures and changed the number of
526 * items (act[p]->nr) for those volatile activities. So loop again to compute
527 * the new total number of graphs.
530 while (rtype == R_RESTART);
532 /* Rewind file and restore items number */
533 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
537 sr_act_nr(save_act_nr, DO_RESTORE);
542 ***************************************************************************
543 * Display *one* sample of statistics for one or several activities,
544 * checking that all conditions are met before printing (time start, time
545 * end, interval). Current record should be a record of statistics (R_STATS),
546 * not a special one (R_RESTART or R_COMMENT).
549 * @curr Index in array for current sample statistics.
550 * @use_tm_start Set to TRUE if option -s has been used.
551 * @use_tm_end Set to TRUE if option -e has been used.
552 * @reset Set to TRUE if last_uptime should be reinitialized
553 * (used in next_slice() function).
554 * @parm Pointer on parameters depending on output format
555 * (eg.: number of tabulations to print).
556 * @cpu_nr Number of processors.
557 * @rectime Structure where timestamp (expressed in local time
558 * or in UTC depending on whether options -T/-t have
559 * been used or not) has been saved for current record.
560 * @loctime Structure where timestamp (expressed in local time)
561 * has been saved for current record.
562 * @reset_cd TRUE if static cross_day variable should be reset.
563 * @act_id Activity to display (only for formats where
564 * activities are displayed one at a time) or
565 * ALL_ACTIVITIES for all.
568 * @cnt Set to 0 to indicate that no other lines of stats
569 * should be displayed.
572 * 1 if stats have been successfully displayed.
573 ***************************************************************************
575 int generic_write_stats(int curr, int use_tm_start, int use_tm_end, int reset,
576 long *cnt, void *parm, __nr_t cpu_nr, struct tm *rectime,
577 struct tm *loctime, int reset_cd, unsigned int act_id)
580 unsigned long long dt, itv, g_itv;
581 char cur_date[TIMESTAMP_LEN], cur_time[TIMESTAMP_LEN], *pre = NULL;
582 static int cross_day = FALSE;
587 * NB: Reseting cross_day is needed only if datafile
588 * may be rewinded (eg. in db or ppc output formats).
595 * For this first check, we use the time interval entered on
596 * the command line. This is equivalent to sar's option -i which
597 * selects records at seconds as close as possible to the number
598 * specified by the interval parameter.
600 if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0,
602 /* Not close enough to desired interval */
605 /* Check if we are beginning a new day */
606 if (use_tm_start && record_hdr[!curr].ust_time &&
607 (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) &&
608 (record_hdr[curr].hour < record_hdr[!curr].hour)) {
614 * This is necessary if we want to properly handle something like:
615 * sar -s time_start -e time_end with
616 * time_start(day D) > time_end(day D+1)
618 loctime->tm_hour += 24;
622 if (use_tm_start && (datecmp(loctime, &tm_start) < 0))
623 /* it's too soon... */
626 /* Get interval values */
627 get_itv_value(&record_hdr[curr], &record_hdr[!curr],
628 cpu_nr, &itv, &g_itv);
631 if (use_tm_end && (datecmp(loctime, &tm_end) > 0)) {
632 /* It's too late... */
638 /* Correct rounding error for dt */
639 if ((itv % HZ) >= (HZ / 2)) {
643 /* Set date and time strings for current record */
644 set_record_timestamp_string(flags, &record_hdr[curr],
645 cur_date, cur_time, TIMESTAMP_LEN, rectime);
647 if (*fmt[f_position]->f_timestamp) {
648 pre = (char *) (*fmt[f_position]->f_timestamp)(parm, F_BEGIN, cur_date, cur_time,
649 dt, &file_hdr, flags);
652 /* Display statistics */
653 for (i = 0; i < NR_ACT; i++) {
655 if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id))
658 if ((TEST_MARKUP(fmt[f_position]->options) && CLOSE_MARKUP(act[i]->options)) ||
659 (IS_SELECTED(act[i]->options) && (act[i]->nr > 0))) {
661 if (format == F_JSON_OUTPUT) {
663 int *tab = (int *) parm;
665 if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) {
667 if (*fmt[f_position]->f_timestamp) {
668 (*fmt[f_position]->f_timestamp)(tab, F_MAIN, cur_date, cur_time,
669 dt, &file_hdr, flags);
672 (*act[i]->f_json_print)(act[i], curr, *tab, NEED_GLOBAL_ITV(act[i]->options) ?
676 else if (format == F_XML_OUTPUT) {
678 int *tab = (int *) parm;
680 (*act[i]->f_xml_print)(act[i], curr, *tab, NEED_GLOBAL_ITV(act[i]->options) ?
684 else if (format == F_SVG_OUTPUT) {
686 struct svg_parm *svg_p = (struct svg_parm *) parm;
688 svg_p->dt = (unsigned long) dt;
689 (*act[i]->f_svg_print)(act[i], curr, F_MAIN, svg_p,
690 NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv,
695 /* Other output formats: db, ppc */
696 (*act[i]->f_render)(act[i], (format == F_DB_OUTPUT), pre, curr,
697 NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv);
702 if (*fmt[f_position]->f_timestamp) {
703 (*fmt[f_position]->f_timestamp)(parm, F_END, cur_date, cur_time,
704 dt, &file_hdr, flags);
711 ***************************************************************************
712 * Read stats for current activity from file and print them.
713 * Display at most <count> lines of stats (and possibly comments inserted
714 * in file) located between two LINUX RESTART messages.
717 * @ifd File descriptor of input file.
718 * @fpos Position in file where reading must start.
719 * @curr Index in array for current sample statistics.
720 * @act_id Activity to display, or ~0 for all.
721 * @file_actlst List of (known or unknown) activities in file.
722 * @cpu_nr Number of processors for current activity data file.
723 * @rectime Structure where timestamp (expressed in local time or in UTC
724 * depending on whether options -T/-t have been used or not) can
725 * be saved for current record.
726 * @loctime Structure where timestamp (expressed in local time) can be
727 * saved for current record.
728 * @file Name of file being read.
729 * @file_magic file_magic structure filled with file magic header data.
732 * @curr Index in array for next sample statistics.
733 * @cnt Number of lines of stats remaining to write.
734 * @eosaf Set to TRUE if EOF (end of file) has been reached.
735 * @reset Set to TRUE if last_uptime variable should be
736 * reinitialized (used in next_slice() function).
737 ***************************************************************************
739 void rw_curr_act_stats(int ifd, off_t fpos, int *curr, long *cnt, int *eosaf,
740 unsigned int act_id, int *reset, struct file_activity *file_actlst,
741 __nr_t cpu_nr, struct tm *rectime, struct tm *loctime,
742 char *file, struct file_magic *file_magic)
747 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
752 if (DISPLAY_FIELD_LIST(fmt[f_position]->options)) {
753 /* Print field list */
758 * Restore the first stats collected.
759 * Used to compute the rate displayed on the first line.
761 copy_structures(act, id_seq, record_hdr, !*curr, 2);
767 /* Display <count> lines of stats */
768 *eosaf = read_next_sample(ifd, IGNORE_RESTART | DONT_READ_VOLATILE,
769 *curr, file, &rtype, 0, file_magic,
770 file_actlst, rectime, loctime);
772 if (!*eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) {
773 next = generic_write_stats(*curr, tm_start.use, tm_end.use, *reset, cnt,
774 NULL, cpu_nr, rectime, loctime, reset_cd, act_id);
779 * next is set to 1 when we were close enough to desired interval.
780 * In this case, the call to generic_write_stats() has actually
781 * displayed a line of stats.
791 while (*cnt && !*eosaf && (rtype != R_RESTART));
797 ***************************************************************************
798 * Read stats for current activity from file and display its SVG graphs.
799 * At most <count> lines of stats are taken into account.
802 * @ifd File descriptor of input file.
803 * @fpos Position in file where reading must start.
804 * @curr Index in array for current sample statistics.
805 * @p Current activity position.
806 * @file_actlst List of (known or unknown) activities in file.
807 * @cpu_nr Number of processors for current activity data file.
808 * @rectime Structure where timestamp (expressed in local time or in UTC
809 * depending on whether options -T/-t have been used or not) can
810 * be saved for current record.
811 * @loctime Structure where timestamp (expressed in local time) can be
812 * saved for current record.
813 * @file Name of file being read.
814 * @file_magic file_magic structure filled with file magic header data.
815 * @save_act_nr Array where the number of volatile activities are saved
816 * for current position in file.
817 * @g_nr Number of graphs already displayed (for all activities).
820 * @cnt Number of lines of stats remaining to write.
821 * @eosaf Set to TRUE if EOF (end of file) has been reached.
822 * @reset Set to TRUE if last_uptime variable should be
823 * reinitialized (used in next_slice() function).
824 * @g_nr Total number of views displayed (including current activity).
825 ***************************************************************************
827 void display_curr_act_graphs(int ifd, off_t fpos, int *curr, long *cnt, int *eosaf,
828 int p, int *reset, struct file_activity *file_actlst,
829 __nr_t cpu_nr, struct tm *rectime, struct tm *loctime,
830 char *file, struct file_magic *file_magic,
831 __nr_t save_act_nr[], int *g_nr)
833 struct svg_parm parm;
838 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
843 * ... and restore number of items for volatile activities
844 * for this position in file.
846 sr_act_nr(save_act_nr, DO_RESTORE);
849 * Restore the first stats collected.
850 * Used to compute the rate displayed on the first line.
852 copy_structures(act, id_seq, record_hdr, !*curr, 2);
854 parm.graph_no = *g_nr;
855 parm.ust_time_ref = get_time_ref();
856 parm.ust_time_first = record_hdr[2].ust_time;
862 /* Allocate graphs arrays */
863 (*act[p]->f_svg_print)(act[p], !*curr, F_BEGIN, &parm, 0, &record_hdr[!*curr]);
866 *eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
867 *curr, file, &rtype, 0, file_magic,
868 file_actlst, rectime, loctime);
870 if (!*eosaf && (rtype != R_COMMENT) && (rtype != R_RESTART)) {
872 next = generic_write_stats(*curr, tm_start.use, tm_end.use, *reset, cnt,
873 &parm, cpu_nr, rectime, loctime, reset_cd, act[p]->id);
877 * next is set to 1 when we were close enough to desired interval.
878 * In this case, the call to generic_write_stats() has actually
879 * displayed a line of stats.
881 parm.restart = FALSE;
889 if (!*eosaf && (rtype == R_RESTART)) {
892 /* Go to next statistics record, if possible */
894 *eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
895 *curr, file, &rtype, 0, file_magic,
896 file_actlst, rectime, loctime);
898 while (!*eosaf && ((rtype == R_RESTART) || (rtype == R_COMMENT)));
907 /* Determine X axis end value */
908 if (DISPLAY_ONE_DAY(flags) &&
909 (parm.ust_time_ref + (3600 * 24) > record_hdr[!*curr].ust_time)) {
910 parm.ust_time_end = parm.ust_time_ref + (3600 * 24);
913 parm.ust_time_end = record_hdr[!*curr].ust_time;
916 /* Actually display graphs for current activity */
917 (*act[p]->f_svg_print)(act[p], *curr, F_END, &parm, 0, &record_hdr[!*curr]);
919 /* Update total number of graphs already displayed */
920 *g_nr = parm.graph_no;
924 ***************************************************************************
925 * Display file contents in selected format (logic #1).
926 * Logic #1: Grouped by record type. Sorted by timestamp.
930 * @ifd File descriptor of input file.
931 * @file_actlst List of (known or unknown) activities in file.
932 * @file System activity data file name (name of file being read).
933 * @file_magic System activity file magic header.
934 * @cpu_nr Number of processors for current activity data file.
935 * @rectime Structure where timestamp (expressed in local time or in UTC
936 * depending on whether options -T/-t have been used or not) can
937 * be saved for current record.
938 * @loctime Structure where timestamp (expressed in local time) can be
939 * saved for current record.
940 ***************************************************************************
942 void logic1_display_loop(int ifd, struct file_activity *file_actlst, char *file,
943 struct file_magic *file_magic, __nr_t cpu_nr,
944 struct tm *rectime, struct tm *loctime)
946 int curr, tab = 0, rtype;
947 int eosaf, next, reset = FALSE;
948 __nr_t save_act_nr[NR_ACT] = {0};
952 /* Save current file position */
953 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
957 /* Save number of activities items for current file position */
958 sr_act_nr(save_act_nr, DO_SAVE);
960 /* Print header (eg. XML file header) */
961 if (*fmt[f_position]->f_header) {
962 (*fmt[f_position]->f_header)(&tab, F_BEGIN, file, file_magic,
963 &file_hdr, cpu_nr, act, id_seq);
966 /* Process activities */
967 if (*fmt[f_position]->f_statistics) {
968 (*fmt[f_position]->f_statistics)(&tab, F_BEGIN);
973 * If this record is a special (RESTART or COMMENT) one,
974 * skip it and try to read the next record in file.
977 eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, 0,
978 file, &rtype, tab, file_magic, file_actlst,
981 while (!eosaf && ((rtype == R_RESTART) || (rtype == R_COMMENT) ||
982 (tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
983 (tm_end.use && (datecmp(loctime, &tm_end) >= 0))));
985 /* Save the first stats collected. Used for example in next_slice() function */
986 copy_structures(act, id_seq, record_hdr, 2, 0);
994 eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, curr,
995 file, &rtype, tab, file_magic, file_actlst,
998 if (!eosaf && (rtype != R_COMMENT) && (rtype != R_RESTART)) {
999 if (*fmt[f_position]->f_statistics) {
1000 (*fmt[f_position]->f_statistics)(&tab, F_MAIN);
1003 /* next is set to 1 when we were close enough to desired interval */
1004 next = generic_write_stats(curr, tm_start.use, tm_end.use, reset,
1005 &cnt, &tab, cpu_nr, rectime, loctime,
1006 FALSE, ALL_ACTIVITIES);
1017 while (cnt && !eosaf && (rtype != R_RESTART));
1020 /* Go to next Linux restart, if possible */
1022 eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, curr,
1023 file, &rtype, tab, file_magic, file_actlst,
1026 while (!eosaf && (rtype != R_RESTART));
1033 if (*fmt[f_position]->f_statistics) {
1034 (*fmt[f_position]->f_statistics)(&tab, F_END);
1037 /* Rewind file... */
1038 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
1043 * ... and restore number of items for volatile activities
1044 * for this position in file.
1046 sr_act_nr(save_act_nr, DO_RESTORE);
1048 /* Process now RESTART entries to display restart messages */
1049 if (*fmt[f_position]->f_restart) {
1050 (*fmt[f_position]->f_restart)(&tab, F_BEGIN, NULL, NULL, FALSE,
1055 eosaf = read_next_sample(ifd, IGNORE_COMMENT, 0,
1056 file, &rtype, tab, file_magic, file_actlst,
1061 if (*fmt[f_position]->f_restart) {
1062 (*fmt[f_position]->f_restart)(&tab, F_END, NULL, NULL, FALSE, &file_hdr, 0);
1065 /* Rewind file... */
1066 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
1071 * ... and restore number of items for volatile activities
1072 * for this position in file.
1074 sr_act_nr(save_act_nr, DO_RESTORE);
1076 /* Last, process COMMENT entries to display comments */
1077 if (DISPLAY_COMMENT(flags)) {
1078 if (*fmt[f_position]->f_comment) {
1079 (*fmt[f_position]->f_comment)(&tab, F_BEGIN, NULL, NULL, 0, NULL,
1083 eosaf = read_next_sample(ifd, IGNORE_RESTART, 0,
1084 file, &rtype, tab, file_magic, file_actlst,
1089 if (*fmt[f_position]->f_comment) {
1090 (*fmt[f_position]->f_comment)(&tab, F_END, NULL, NULL, 0, NULL,
1095 /* Print header trailer */
1096 if (*fmt[f_position]->f_header) {
1097 (*fmt[f_position]->f_header)(&tab, F_END, file, file_magic,
1098 &file_hdr, cpu_nr, act, id_seq);
1103 ***************************************************************************
1104 * Display file contents in selected format (logic #2).
1105 * Logic #2: Grouped by activity. Sorted by timestamp. Stop on RESTART
1110 * @ifd File descriptor of input file.
1111 * @file_actlst List of (known or unknown) activities in file.
1112 * @cpu_nr Number of processors for current activity data file.
1113 * @rectime Structure where timestamp (expressed in local time or in UTC
1114 * depending on whether options -T/-t have been used or not) can
1115 * be saved for current record.
1116 * @loctime Structure where timestamp (expressed in local time) can be
1117 * saved for current record.
1118 * @file Name of file being read.
1119 * @file_magic file_magic structure filled with file magic header data.
1120 ***************************************************************************
1122 void logic2_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr,
1123 struct tm *rectime, struct tm *loctime, char *file,
1124 struct file_magic *file_magic)
1127 int curr = 1, rtype;
1128 int eosaf = TRUE, reset = FALSE;
1132 /* Read system statistics from file */
1135 * If this record is a special (RESTART or COMMENT) one, print it and
1136 * (try to) get another one.
1139 if (read_next_sample(ifd, IGNORE_NOTHING, 0,
1140 file, &rtype, 0, file_magic, file_actlst,
1142 /* End of sa data file */
1145 while ((rtype == R_RESTART) || (rtype == R_COMMENT) ||
1146 (tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
1147 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)));
1149 /* Save the first stats collected. Used for example in next_slice() function */
1150 copy_structures(act, id_seq, record_hdr, 2, 0);
1152 /* Set flag to reset last_uptime variable. Should be done after a LINUX RESTART record */
1155 /* Save current file position */
1156 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
1161 /* Read and write stats located between two possible Linux restarts */
1163 if (DISPLAY_HORIZONTALLY(flags)) {
1165 * If stats are displayed horizontally, then all activities
1166 * are printed on the same line.
1168 rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf,
1169 ALL_ACTIVITIES, &reset, file_actlst,
1170 cpu_nr, rectime, loctime, file, file_magic);
1173 /* For each requested activity... */
1174 for (i = 0; i < NR_ACT; i++) {
1179 p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND);
1180 if (!IS_SELECTED(act[p]->options))
1183 if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) {
1184 rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf,
1185 act[p]->id, &reset, file_actlst,
1186 cpu_nr, rectime, loctime, file,
1190 unsigned int optf, msk;
1192 optf = act[p]->opt_flags;
1194 for (msk = 1; msk < 0x100; msk <<= 1) {
1195 if ((act[p]->opt_flags & 0xff) & msk) {
1196 act[p]->opt_flags &= (0xffffff00 + msk);
1198 rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf,
1199 act[p]->id, &reset, file_actlst,
1200 cpu_nr, rectime, loctime, file,
1202 act[p]->opt_flags = optf;
1210 /* Go to next Linux restart, if possible */
1212 eosaf = read_next_sample(ifd, IGNORE_RESTART | DONT_READ_VOLATILE,
1213 curr, file, &rtype, 0, file_magic,
1214 file_actlst, rectime, loctime);
1216 while (!eosaf && (rtype != R_RESTART));
1220 * The last record we read was a RESTART one: Print it.
1221 * NB: Unlike COMMENTS records (which are displayed for each
1222 * activity), RESTART ones are only displayed once.
1224 if (!eosaf && (record_hdr[curr].record_type == R_RESTART)) {
1225 print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end,
1226 R_RESTART, ifd, rectime, loctime, file, 0,
1227 file_magic, &file_hdr, act, fmt[f_position]);
1234 ***************************************************************************
1235 * Display file contents in selected format (logic #3).
1236 * Logic #3: Special logic for SVG output format.
1240 * @ifd File descriptor of input file.
1241 * @file_actlst List of (known or unknown) activities in file.
1242 * @cpu_nr Number of processors for current activity data file.
1243 * @rectime Structure where timestamp (expressed in local time or in UTC
1244 * depending on whether options -T/-t have been used or not) can
1245 * be saved for current record.
1246 * @loctime Structure where timestamp (expressed in local time) can be
1247 * saved for current record.
1248 * @file Name of file being read.
1249 * @file_magic file_magic structure filled with file magic header data.
1250 ***************************************************************************
1252 void logic3_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr,
1253 struct tm *rectime, struct tm *loctime, char *file,
1254 struct file_magic *file_magic)
1257 int curr = 1, rtype, g_nr = 0;
1258 int eosaf = TRUE, reset = TRUE;
1262 __nr_t save_act_nr[NR_ACT] = {0};
1264 /* Use a decimal point to make SVG code locale independent */
1265 setlocale(LC_NUMERIC, "C");
1267 /* Calculate the number of graphs to display */
1268 graph_nr = get_svg_graph_nr(ifd, file, file_magic,
1269 file_actlst, rectime, loctime);
1271 /* No graph to display */
1274 /* Print SVG header */
1275 if (*fmt[f_position]->f_header) {
1276 (*fmt[f_position]->f_header)(&graph_nr, F_BEGIN + F_MAIN, file, file_magic,
1277 &file_hdr, cpu_nr, act, id_seq);
1281 * If this record is a special (RESTART or COMMENT) one, ignore it and
1282 * (try to) get another one.
1285 if (read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT, 0,
1286 file, &rtype, 0, file_magic, file_actlst,
1288 /* End of sa data file */
1291 while ((rtype == R_RESTART) || (rtype == R_COMMENT) ||
1292 (tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
1293 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)));
1295 /* Save the first stats collected. Used for example in next_slice() function */
1296 copy_structures(act, id_seq, record_hdr, 2, 0);
1298 /* Save current file position */
1299 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
1303 /* Save number of activities items for current file position */
1304 sr_act_nr(save_act_nr, DO_SAVE);
1306 /* For each requested activity, display graphs */
1307 for (i = 0; i < NR_ACT; i++) {
1312 p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND);
1313 if (!IS_SELECTED(act[p]->options) || !act[p]->g_nr)
1316 if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) {
1317 display_curr_act_graphs(ifd, fpos, &curr, &cnt, &eosaf,
1318 p, &reset, file_actlst,
1319 cpu_nr, rectime, loctime, file,
1320 file_magic, save_act_nr, &g_nr);
1323 unsigned int optf, msk;
1325 optf = act[p]->opt_flags;
1327 for (msk = 1; msk < 0x100; msk <<= 1) {
1328 if ((act[p]->opt_flags & 0xff) & msk) {
1329 act[p]->opt_flags &= (0xffffff00 + msk);
1330 display_curr_act_graphs(ifd, fpos, &curr, &cnt, &eosaf,
1331 p, &reset, file_actlst,
1332 cpu_nr, rectime, loctime, file,
1333 file_magic, save_act_nr, &g_nr);
1334 act[p]->opt_flags = optf;
1340 /* Print SVG trailer */
1341 if (*fmt[f_position]->f_header) {
1342 (*fmt[f_position]->f_header)(&graph_nr, F_END, file, file_magic,
1343 &file_hdr, cpu_nr, act, id_seq);
1348 ***************************************************************************
1349 * Check system activity datafile contents before displaying stats.
1350 * Display file header if option -H has been entered, else call function
1351 * corresponding to selected output format.
1354 * @dfile System activity data file name.
1355 ***************************************************************************
1357 void read_stats_from_file(char dfile[])
1359 struct file_magic file_magic;
1360 struct file_activity *file_actlst = NULL;
1361 struct tm rectime, loctime;
1362 int ifd, ignore, tab = 0;
1365 /* Prepare file for reading and read its headers */
1366 ignore = ACCEPT_BAD_FILE_FORMAT(fmt[f_position]->options);
1367 check_file_actlst(&ifd, dfile, act, &file_magic, &file_hdr,
1368 &file_actlst, id_seq, ignore);
1370 /* Now pick up number of proc for this file */
1371 cpu_nr = act[get_activity_position(act, A_CPU, EXIT_IF_NOT_FOUND)]->nr;
1373 if (DISPLAY_HDR_ONLY(flags)) {
1374 if (*fmt[f_position]->f_header) {
1375 /* Display only data file header then exit */
1376 (*fmt[f_position]->f_header)(&tab, F_BEGIN + F_END, dfile, &file_magic,
1377 &file_hdr, cpu_nr, act, id_seq);
1382 /* Perform required allocations */
1383 allocate_structures(act);
1385 /* Call function corresponding to selected output format */
1386 if (format == F_SVG_OUTPUT) {
1387 logic3_display_loop(ifd, file_actlst, cpu_nr,
1388 &rectime, &loctime, dfile, &file_magic);
1390 else if (DISPLAY_GROUPED_STATS(fmt[f_position]->options)) {
1391 logic2_display_loop(ifd, file_actlst, cpu_nr,
1392 &rectime, &loctime, dfile, &file_magic);
1395 logic1_display_loop(ifd, file_actlst, dfile,
1396 &file_magic, cpu_nr, &rectime, &loctime);
1402 free_structures(act);
1406 ***************************************************************************
1407 * Main entry to the sadf program
1408 ***************************************************************************
1410 int main(int argc, char **argv)
1412 int opt = 1, sar_options = 0;
1415 char dfile[MAX_FILE_LEN];
1421 /* Compute page shift in kB */
1427 /* Init National Language Support */
1431 tm_start.use = tm_end.use = FALSE;
1433 /* Allocate and init activity bitmaps */
1434 allocate_bitmaps(act);
1436 /* Init some structures */
1439 /* Process options */
1440 while (opt < argc) {
1442 if (!strcmp(argv[opt], "-I")) {
1443 if (argv[++opt] && sar_options) {
1444 if (parse_sar_I_opt(argv, &opt, act)) {
1453 else if (!strcmp(argv[opt], "-P")) {
1454 if (parse_sa_P_opt(argv, &opt, &flags, act)) {
1459 else if (!strcmp(argv[opt], "-s")) {
1460 /* Get time start */
1461 if (parse_timestamp(argv, &opt, &tm_start, DEF_TMSTART)) {
1466 else if (!strcmp(argv[opt], "-e")) {
1468 if (parse_timestamp(argv, &opt, &tm_end, DEF_TMEND)) {
1473 else if (!strcmp(argv[opt], "-O")) {
1474 /* Parse SVG options */
1475 if (!argv[++opt] || sar_options) {
1478 for (t = strtok(argv[opt], ","); t; t = strtok(NULL, ",")) {
1479 if (!strcmp(t, K_SKIP_EMPTY)) {
1480 flags |= S_F_SVG_SKIP;
1482 else if (!strcmp(t, K_AUTOSCALE)) {
1483 flags |= S_F_SVG_AUTOSCALE;
1485 else if (!strcmp(t, K_ONEDAY)) {
1486 flags |= S_F_SVG_ONE_DAY;
1495 else if ((strlen(argv[opt]) > 1) &&
1496 (strlen(argv[opt]) < 4) &&
1497 !strncmp(argv[opt], "-", 1) &&
1498 (strspn(argv[opt] + 1, DIGITS) == (strlen(argv[opt]) - 1))) {
1499 if (dfile[0] || day_offset) {
1500 /* File already specified */
1503 day_offset = atoi(argv[opt++] + 1);
1506 else if (!strcmp(argv[opt], "--")) {
1511 else if (!strcmp(argv[opt], "-m")) {
1512 if (argv[++opt] && sar_options) {
1513 /* Parse sar's option -m */
1514 if (parse_sar_m_opt(argv, &opt, act)) {
1523 else if (!strcmp(argv[opt], "-n")) {
1524 if (argv[++opt] && sar_options) {
1525 /* Parse sar's option -n */
1526 if (parse_sar_n_opt(argv, &opt, act)) {
1535 else if (!strncmp(argv[opt], "-", 1)) {
1536 /* Other options not previously tested */
1538 if ((rc = parse_sar_opt(argv, &opt, act, &flags, C_SADF)) != 0) {
1547 for (i = 1; *(argv[opt] + i); i++) {
1549 switch (*(argv[opt] + i)) {
1552 flags |= S_F_COMMENT;
1559 format = F_CONV_OUTPUT;
1566 format = F_DB_OUTPUT;
1573 format = F_SVG_OUTPUT;
1577 flags |= S_F_HORIZONTALLY;
1581 flags |= S_F_HDR_ONLY;
1588 format = F_JSON_OUTPUT;
1595 format = F_PPC_OUTPUT;
1599 flags |= S_F_LOCAL_TIME;
1603 flags |= S_F_TRUE_TIME;
1607 flags |= S_F_SEC_EPOCH;
1614 format = F_XML_OUTPUT;
1629 /* Get data file name */
1630 else if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) {
1631 if (dfile[0] || day_offset) {
1632 /* File already specified */
1635 if (!strcmp(argv[opt], "-")) {
1636 /* File name set to '-' */
1637 set_default_file(dfile, 0, -1);
1640 else if (!strncmp(argv[opt], "-", 1)) {
1645 /* Write data to file */
1646 strncpy(dfile, argv[opt++], MAX_FILE_LEN);
1647 dfile[MAX_FILE_LEN - 1] = '\0';
1648 /* Check if this is an alternate directory for sa files */
1649 check_alt_sa_dir(dfile, 0, -1);
1653 else if (interval < 0) {
1655 if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) {
1658 interval = atol(argv[opt++]);
1659 if (interval <= 0) {
1665 /* Get count value */
1666 if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) {
1670 /* Count parameter already set */
1673 count = atol(argv[opt++]);
1678 count = -1; /* To generate a report continuously */
1683 /* sadf reads current daily data file by default */
1685 set_default_file(dfile, day_offset, -1);
1688 if (tm_start.use && tm_end.use && (tm_end.tm_hour < tm_start.tm_hour)) {
1689 tm_end.tm_hour += 24;
1692 if (USE_PRETTY_OPTION(flags)) {
1693 dm_major = get_devmap_major();
1696 /* Options -T, -t and -U are mutually exclusive */
1697 if ((PRINT_LOCAL_TIME(flags) + PRINT_TRUE_TIME(flags) +
1698 PRINT_SEC_EPOCH(flags)) > 1) {
1703 * Display all the contents of the daily data file if the count parameter
1704 * was not set on the command line.
1710 /* Default is CPU activity */
1711 select_default_activity(act);
1713 /* Check options consistency with selected output format. Default is PPC display */
1714 check_format_options();
1720 if (format == F_CONV_OUTPUT) {
1721 /* Convert file to current format */
1722 convert_file(dfile, act);
1725 /* Read stats from file */
1726 read_stats_from_file(dfile);