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 ***************************************************************************
189 * @nr_tab Number of tabs to print.
190 ***************************************************************************
192 void prtab(int nr_tab)
196 for (i = 0; i < nr_tab; i++) {
202 ***************************************************************************
203 * printf() function modified for logic #1 (XML-like) display. Don't print a
204 * CR at the end of the line.
207 * @nr_tab Number of tabs to print.
208 * @fmtf printf() format.
209 ***************************************************************************
211 void xprintf0(int nr_tab, const char *fmtf, ...)
213 static char buf[1024];
216 va_start(args, fmtf);
217 vsnprintf(buf, sizeof(buf), fmtf, args);
225 ***************************************************************************
226 * printf() function modified for logic #1 (XML-like) display. Print a CR
227 * at the end of the line.
230 * @nr_tab Number of tabs to print.
231 * @fmtf printf() format.
232 ***************************************************************************
234 void xprintf(int nr_tab, const char *fmtf, ...)
236 static char buf[1024];
239 va_start(args, fmtf);
240 vsnprintf(buf, sizeof(buf), fmtf, args);
248 ***************************************************************************
249 * Save or restore number of items for all known activities.
252 * @save_act_nr Array containing number of items to restore for each
254 * @action DO_SAVE to save number of items, or DO_RESTORE to restore.
257 * @save_act_nr Array containing number of items saved for each activity.
258 ***************************************************************************
260 void sr_act_nr(__nr_t save_act_nr[], int action)
264 if (action == DO_SAVE) {
265 /* Save number of items for all activities */
266 for (i = 0; i < NR_ACT; i++) {
267 save_act_nr[i] = act[i]->nr;
270 else if (action == DO_RESTORE) {
272 * Restore number of items for all activities
273 * and reallocate structures accordingly.
275 for (i = 0; i < NR_ACT; i++) {
276 if (save_act_nr[i] > 0) {
277 reallocate_vol_act_structures(act, save_act_nr[i],
285 ***************************************************************************
286 * Read next sample statistics. If it's a special record (R_RESTART or
287 * R_COMMENT) then display it if requested. Also fill timestamps structures.
290 * @ifd File descriptor
291 * @action Flags indicating if special records should be displayed or
293 * @curr Index in array for current sample statistics.
294 * @file System activity data file name (name of file being read).
295 * @tab Number of tabulations to print.
296 * @file_actlst List of (known or unknown) activities in file.
297 * @file_magic System activity file magic header.
298 * @rectime Structure where timestamp (expressed in local time or in UTC
299 * depending on whether options -T/-t have been used or not) can
300 * be saved for current record.
301 * @loctime Structure where timestamp (expressed in local time) can be
302 * saved for current record.
305 * @rtype Type of record read (R_RESTART, R_COMMENT, etc.)
306 * @rectime Structure where timestamp (expressed in local time or in UTC
307 * depending on options used) has been saved for current record.
308 * If current record was a special one (RESTART or COMMENT) and
309 * noted to be ignored, then the timestamp is saved only if
310 * explicitly told to do so with the SET_TIMESTAMPS action flag.
311 * @loctime Structure where timestamp (expressed in local time) has been
312 * saved for current record.
313 * If current record was a special one (RESTART or COMMENT) and
314 * noted to be ignored, then the timestamp is saved only if
315 * explicitly told to do so with the SET_TIMESTAMPS action flag.
318 * TRUE if end of file has been reached.
319 ***************************************************************************
321 int read_next_sample(int ifd, int action, int curr, char *file, int *rtype, int tab,
322 struct file_magic *file_magic, struct file_activity *file_actlst,
323 struct tm *rectime, struct tm *loctime)
327 /* Read current record */
328 eosaf = sa_fread(ifd, &record_hdr[curr], RECORD_HEADER_SIZE, SOFT_SIZE);
329 *rtype = record_hdr[curr].record_type;
332 if (*rtype == R_COMMENT) {
333 if (action & IGNORE_COMMENT) {
334 /* Ignore COMMENT record */
335 if (lseek(ifd, MAX_COMMENT_LEN, SEEK_CUR) < MAX_COMMENT_LEN) {
338 if (action & SET_TIMESTAMPS) {
339 sa_get_record_timestamp_struct(flags, &record_hdr[curr],
344 /* Display COMMENT record */
345 print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end,
346 *rtype, ifd, rectime, loctime, file, tab,
347 file_magic, &file_hdr, act, fmt[f_position]);
350 else if (*rtype == R_RESTART) {
351 if (action & IGNORE_RESTART) {
353 * Ignore RESTART record (don't display it)
354 * but anyway we have to reallocate volatile
355 * activities structures (unless we don't want to
358 if (!(action & DONT_READ_VOLATILE)) {
359 read_vol_act_structures(ifd, act, file, file_magic,
360 file_hdr.sa_vol_act_nr);
362 if (action & SET_TIMESTAMPS) {
363 sa_get_record_timestamp_struct(flags, &record_hdr[curr],
368 /* Display RESTART record */
369 print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end,
370 *rtype, ifd, rectime, loctime, file, tab,
371 file_magic, &file_hdr, act, fmt[f_position]);
376 * OK: Previous record was not a special one.
377 * So read now the extra fields.
379 read_file_stat_bunch(act, curr, ifd, file_hdr.sa_act_nr,
381 sa_get_record_timestamp_struct(flags, &record_hdr[curr], rectime, loctime);
389 ***************************************************************************
390 * Display the field list (used eg. in database format).
393 * @act_d Activity to display, or ~0 for all.
394 ***************************************************************************
396 void list_fields(unsigned int act_id)
401 char hline[HEADER_LINE_LEN] = "";
403 printf("# hostname;interval;timestamp");
405 for (i = 0; i < NR_ACT; i++) {
407 if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id))
410 if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) {
411 if (!HAS_MULTIPLE_OUTPUTS(act[i]->options)) {
412 printf(";%s", act[i]->hdr_line);
413 if ((act[i]->nr > 1) && DISPLAY_HORIZONTALLY(flags)) {
419 strncpy(hline, act[i]->hdr_line, HEADER_LINE_LEN - 1);
420 hline[HEADER_LINE_LEN - 1] = '\0';
421 for (hl = strtok(hline, "|"); hl; hl = strtok(NULL, "|"), msk <<= 1) {
422 if ((hl != NULL) && ((act[i]->opt_flags & 0xff) & msk)) {
423 if (strchr(hl, '&')) {
424 j = strcspn(hl, "&");
425 if ((act[i]->opt_flags & 0xff00) & (msk << 8)) {
426 /* Display whole header line */
431 /* Display only the first part of the header line */
440 if ((act[i]->nr > 1) && DISPLAY_HORIZONTALLY(flags)) {
452 ***************************************************************************
453 * Determine the time (expressed in seconds since the epoch) used as the
454 * origin on X axis for SVG graphs. If S_F_SVG_ONE_DAY is set, then origin
455 * will be the beginning of current day (00:00:00) else it will be the time
456 * of the first sample collected.
459 * Time origin on X axis (expressed in seconds since the epoch).
460 ***************************************************************************
462 time_t get_time_ref(void)
467 if (DISPLAY_ONE_DAY(flags)) {
468 ltm = localtime((time_t *) &(record_hdr[2].ust_time));
470 /* Move back to midnight */
471 ltm->tm_sec = ltm->tm_min = ltm->tm_hour = 0;
478 return record_hdr[2].ust_time;
482 ***************************************************************************
483 * Compute the number of SVG graphs to display. Each activity selected may
484 * have several graphs. Moreover we have to take into account volatile
485 * activities (eg. CPU) for which the number of graphs will depend on the
486 * highest number of items (eg. maximum number of CPU) saved in the file.
487 * This number may be higher than the real number of graphs that will be
488 * displayed since some items have a preallocation constant.
491 * @ifd File descriptor of input file.
492 * @file Name of file being read.
493 * @file_magic file_magic structure filled with file magic header data.
494 * @file_actlst List of (known or unknown) activities in file.
495 * @rectime Structure where timestamp (expressed in local time or in UTC
496 * depending on whether options -T/-t have been used or not) can
497 * be saved for current record.
498 * @loctime Structure where timestamp (expressed in local time) can be
499 * saved for current record.
502 * Total number of graphs to display, taking into account only activities
503 * to be displayed, and selected period of time (options -s/-e).
504 ***************************************************************************
506 int get_svg_graph_nr(int ifd, char *file, struct file_magic *file_magic,
507 struct file_activity *file_actlst, struct tm *rectime,
511 int rtype, new_tot_g_nr, tot_g_nr = 0;
513 __nr_t save_act_nr[NR_ACT] = {0};
515 /* Save current file position and items number */
516 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
520 sr_act_nr(save_act_nr, DO_SAVE);
522 /* Init total number of graphs for each activity */
523 for (i = 0; i < NR_ACT; i++) {
527 /* Look for the first record that will be displayed */
529 eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
530 0, file, &rtype, 0, file_magic, file_actlst,
533 /* No record to display => no graph too */
536 while ((tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
537 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)));
542 for (i = 0; i < NR_ACT; i++) {
546 p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND);
547 if (!IS_SELECTED(act[p]->options))
550 if (ONE_GRAPH_PER_ITEM(act[p]->options)) {
551 n = act[p]->g_nr * act[p]->nr;
557 if (n > id_g_nr[i]) {
563 if (new_tot_g_nr > tot_g_nr) {
564 tot_g_nr = new_tot_g_nr;
568 eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
569 0, file, &rtype, 0, file_magic, file_actlst,
572 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)))
573 /* End of data file or end time exceeded */
576 while (rtype != R_RESTART);
579 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)))
581 * End of file, or end time exceeded:
582 * Current number of graphs is up-to-date.
587 * If we have found a RESTART record then we have also read the list of volatile
588 * activities following it, reallocated the structures and changed the number of
589 * items (act[p]->nr) for those volatile activities. So loop again to compute
590 * the new total number of graphs.
593 while (rtype == R_RESTART);
595 /* Rewind file and restore items number */
596 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
600 sr_act_nr(save_act_nr, DO_RESTORE);
605 ***************************************************************************
606 * Display *one* sample of statistics for one or several activities,
607 * checking that all conditions are met before printing (time start, time
608 * end, interval). Current record should be a record of statistics (R_STATS),
609 * not a special one (R_RESTART or R_COMMENT).
612 * @curr Index in array for current sample statistics.
613 * @use_tm_start Set to TRUE if option -s has been used.
614 * @use_tm_end Set to TRUE if option -e has been used.
615 * @reset Set to TRUE if last_uptime should be reinitialized
616 * (used in next_slice() function).
617 * @parm Pointer on parameters depending on output format
618 * (eg.: number of tabulations to print).
619 * @cpu_nr Number of processors.
620 * @rectime Structure where timestamp (expressed in local time
621 * or in UTC depending on whether options -T/-t have
622 * been used or not) has been saved for current record.
623 * @loctime Structure where timestamp (expressed in local time)
624 * has been saved for current record.
625 * @reset_cd TRUE if static cross_day variable should be reset.
626 * @act_id Activity to display (only for formats where
627 * activities are displayed one at a time) or
628 * ALL_ACTIVITIES for all.
631 * @cnt Set to 0 to indicate that no other lines of stats
632 * should be displayed.
635 * 1 if stats have been successfully displayed.
636 ***************************************************************************
638 int generic_write_stats(int curr, int use_tm_start, int use_tm_end, int reset,
639 long *cnt, void *parm, __nr_t cpu_nr, struct tm *rectime,
640 struct tm *loctime, int reset_cd, unsigned int act_id)
643 unsigned long long dt, itv, g_itv;
644 char cur_date[32], cur_time[32], *pre = NULL;
645 static int cross_day = FALSE;
650 * NB: Reseting cross_day is needed only if datafile
651 * may be rewinded (eg. in db or ppc output formats).
658 * For this first check, we use the time interval entered on
659 * the command line. This is equivalent to sar's option -i which
660 * selects records at seconds as close as possible to the number
661 * specified by the interval parameter.
663 if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0,
665 /* Not close enough to desired interval */
668 /* Check if we are beginning a new day */
669 if (use_tm_start && record_hdr[!curr].ust_time &&
670 (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) &&
671 (record_hdr[curr].hour < record_hdr[!curr].hour)) {
677 * This is necessary if we want to properly handle something like:
678 * sar -s time_start -e time_end with
679 * time_start(day D) > time_end(day D+1)
681 loctime->tm_hour += 24;
685 if (use_tm_start && (datecmp(loctime, &tm_start) < 0))
686 /* it's too soon... */
689 /* Get interval values */
690 get_itv_value(&record_hdr[curr], &record_hdr[!curr],
691 cpu_nr, &itv, &g_itv);
694 if (use_tm_end && (datecmp(loctime, &tm_end) > 0)) {
695 /* It's too late... */
701 /* Correct rounding error for dt */
702 if ((itv % HZ) >= (HZ / 2)) {
706 /* Set date and time strings for current record */
707 set_record_timestamp_string(flags, &record_hdr[curr],
708 cur_date, cur_time, 32, rectime);
710 if (*fmt[f_position]->f_timestamp) {
711 pre = (char *) (*fmt[f_position]->f_timestamp)(parm, F_BEGIN, cur_date, cur_time,
712 dt, &file_hdr, flags);
715 /* Display statistics */
716 for (i = 0; i < NR_ACT; i++) {
718 if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id))
721 if ((TEST_MARKUP(fmt[f_position]->options) && CLOSE_MARKUP(act[i]->options)) ||
722 (IS_SELECTED(act[i]->options) && (act[i]->nr > 0))) {
724 if (format == F_JSON_OUTPUT) {
726 int *tab = (int *) parm;
728 if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) {
730 if (*fmt[f_position]->f_timestamp) {
731 (*fmt[f_position]->f_timestamp)(tab, F_MAIN, cur_date, cur_time,
732 dt, &file_hdr, flags);
735 (*act[i]->f_json_print)(act[i], curr, *tab, NEED_GLOBAL_ITV(act[i]->options) ?
739 else if (format == F_XML_OUTPUT) {
741 int *tab = (int *) parm;
743 (*act[i]->f_xml_print)(act[i], curr, *tab, NEED_GLOBAL_ITV(act[i]->options) ?
747 else if (format == F_SVG_OUTPUT) {
749 struct svg_parm *svg_p = (struct svg_parm *) parm;
751 svg_p->dt = (unsigned long) dt;
752 (*act[i]->f_svg_print)(act[i], curr, F_MAIN, svg_p,
753 NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv,
758 /* Other output formats: db, ppc */
759 (*act[i]->f_render)(act[i], (format == F_DB_OUTPUT), pre, curr,
760 NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv);
765 if (*fmt[f_position]->f_timestamp) {
766 (*fmt[f_position]->f_timestamp)(parm, F_END, cur_date, cur_time,
767 dt, &file_hdr, flags);
774 ***************************************************************************
775 * Read stats for current activity from file and print them.
776 * Display at most <count> lines of stats (and possibly comments inserted
777 * in file) located between two LINUX RESTART messages.
780 * @ifd File descriptor of input file.
781 * @fpos Position in file where reading must start.
782 * @curr Index in array for current sample statistics.
783 * @act_id Activity to display, or ~0 for all.
784 * @file_actlst List of (known or unknown) activities in file.
785 * @cpu_nr Number of processors for current activity data file.
786 * @rectime Structure where timestamp (expressed in local time or in UTC
787 * depending on whether options -T/-t have been used or not) can
788 * be saved for current record.
789 * @loctime Structure where timestamp (expressed in local time) can be
790 * saved for current record.
791 * @file Name of file being read.
792 * @file_magic file_magic structure filled with file magic header data.
795 * @curr Index in array for next sample statistics.
796 * @cnt Number of lines of stats remaining to write.
797 * @eosaf Set to TRUE if EOF (end of file) has been reached.
798 * @reset Set to TRUE if last_uptime variable should be
799 * reinitialized (used in next_slice() function).
800 ***************************************************************************
802 void rw_curr_act_stats(int ifd, off_t fpos, int *curr, long *cnt, int *eosaf,
803 unsigned int act_id, int *reset, struct file_activity *file_actlst,
804 __nr_t cpu_nr, struct tm *rectime, struct tm *loctime,
805 char *file, struct file_magic *file_magic)
810 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
815 if (DISPLAY_FIELD_LIST(fmt[f_position]->options)) {
816 /* Print field list */
821 * Restore the first stats collected.
822 * Used to compute the rate displayed on the first line.
824 copy_structures(act, id_seq, record_hdr, !*curr, 2);
830 /* Display <count> lines of stats */
831 *eosaf = read_next_sample(ifd, IGNORE_RESTART | DONT_READ_VOLATILE,
832 *curr, file, &rtype, 0, file_magic,
833 file_actlst, rectime, loctime);
835 if (!*eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) {
836 next = generic_write_stats(*curr, tm_start.use, tm_end.use, *reset, cnt,
837 NULL, cpu_nr, rectime, loctime, reset_cd, act_id);
842 * next is set to 1 when we were close enough to desired interval.
843 * In this case, the call to generic_write_stats() has actually
844 * displayed a line of stats.
854 while (*cnt && !*eosaf && (rtype != R_RESTART));
860 ***************************************************************************
861 * Read stats for current activity from file and display its SVG graphs.
862 * At most <count> lines of stats are taken into account.
865 * @ifd File descriptor of input file.
866 * @fpos Position in file where reading must start.
867 * @curr Index in array for current sample statistics.
868 * @p Current activity position.
869 * @file_actlst List of (known or unknown) activities in file.
870 * @cpu_nr Number of processors for current activity data file.
871 * @rectime Structure where timestamp (expressed in local time or in UTC
872 * depending on whether options -T/-t have been used or not) can
873 * be saved for current record.
874 * @loctime Structure where timestamp (expressed in local time) can be
875 * saved for current record.
876 * @file Name of file being read.
877 * @file_magic file_magic structure filled with file magic header data.
878 * @save_act_nr Array where the number of volatile activities are saved
879 * for current position in file.
880 * @g_nr Number of graphs already displayed (for all activities).
883 * @cnt Number of lines of stats remaining to write.
884 * @eosaf Set to TRUE if EOF (end of file) has been reached.
885 * @reset Set to TRUE if last_uptime variable should be
886 * reinitialized (used in next_slice() function).
887 * @g_nr Total number of views displayed (including current activity).
888 ***************************************************************************
890 void display_curr_act_graphs(int ifd, off_t fpos, int *curr, long *cnt, int *eosaf,
891 int p, int *reset, struct file_activity *file_actlst,
892 __nr_t cpu_nr, struct tm *rectime, struct tm *loctime,
893 char *file, struct file_magic *file_magic,
894 __nr_t save_act_nr[], int *g_nr)
896 struct svg_parm parm;
901 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
906 * ... and restore number of items for volatile activities
907 * for this position in file.
909 sr_act_nr(save_act_nr, DO_RESTORE);
912 * Restore the first stats collected.
913 * Used to compute the rate displayed on the first line.
915 copy_structures(act, id_seq, record_hdr, !*curr, 2);
917 parm.graph_no = *g_nr;
918 parm.ust_time_ref = get_time_ref();
919 parm.ust_time_first = record_hdr[2].ust_time;
925 /* Allocate graphs arrays */
926 (*act[p]->f_svg_print)(act[p], !*curr, F_BEGIN, &parm, 0, &record_hdr[!*curr]);
929 *eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
930 *curr, file, &rtype, 0, file_magic,
931 file_actlst, rectime, loctime);
933 if (!*eosaf && (rtype != R_COMMENT) && (rtype != R_RESTART)) {
935 next = generic_write_stats(*curr, tm_start.use, tm_end.use, *reset, cnt,
936 &parm, cpu_nr, rectime, loctime, reset_cd, act[p]->id);
940 * next is set to 1 when we were close enough to desired interval.
941 * In this case, the call to generic_write_stats() has actually
942 * displayed a line of stats.
944 parm.restart = FALSE;
952 if (!*eosaf && (rtype == R_RESTART)) {
955 /* Go to next statistics record, if possible */
957 *eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS,
958 *curr, file, &rtype, 0, file_magic,
959 file_actlst, rectime, loctime);
961 while (!*eosaf && ((rtype == R_RESTART) || (rtype == R_COMMENT)));
970 /* Determine X axis end value */
971 if (DISPLAY_ONE_DAY(flags) &&
972 (parm.ust_time_ref + (3600 * 24) > record_hdr[!*curr].ust_time)) {
973 parm.ust_time_end = parm.ust_time_ref + (3600 * 24);
976 parm.ust_time_end = record_hdr[!*curr].ust_time;
979 /* Actually display graphs for current activity */
980 (*act[p]->f_svg_print)(act[p], *curr, F_END, &parm, 0, &record_hdr[!*curr]);
982 /* Update total number of graphs already displayed */
983 *g_nr = parm.graph_no;
987 ***************************************************************************
988 * Display file contents in selected format (logic #1).
989 * Logic #1: Grouped by record type. Sorted by timestamp.
993 * @ifd File descriptor of input file.
994 * @file_actlst List of (known or unknown) activities in file.
995 * @file System activity data file name (name of file being read).
996 * @file_magic System activity file magic header.
997 * @cpu_nr Number of processors for current activity data file.
998 * @rectime Structure where timestamp (expressed in local time or in UTC
999 * depending on whether options -T/-t have been used or not) can
1000 * be saved for current record.
1001 * @loctime Structure where timestamp (expressed in local time) can be
1002 * saved for current record.
1003 ***************************************************************************
1005 void logic1_display_loop(int ifd, struct file_activity *file_actlst, char *file,
1006 struct file_magic *file_magic, __nr_t cpu_nr,
1007 struct tm *rectime, struct tm *loctime)
1009 int curr, tab = 0, rtype;
1010 int eosaf, next, reset = FALSE;
1011 __nr_t save_act_nr[NR_ACT] = {0};
1015 /* Save current file position */
1016 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
1020 /* Save number of activities items for current file position */
1021 sr_act_nr(save_act_nr, DO_SAVE);
1023 /* Print header (eg. XML file header) */
1024 if (*fmt[f_position]->f_header) {
1025 (*fmt[f_position]->f_header)(&tab, F_BEGIN, file, file_magic,
1026 &file_hdr, cpu_nr, act, id_seq);
1029 /* Process activities */
1030 if (*fmt[f_position]->f_statistics) {
1031 (*fmt[f_position]->f_statistics)(&tab, F_BEGIN);
1036 * If this record is a special (RESTART or COMMENT) one,
1037 * skip it and try to read the next record in file.
1040 eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, 0,
1041 file, &rtype, tab, file_magic, file_actlst,
1044 while (!eosaf && ((rtype == R_RESTART) || (rtype == R_COMMENT) ||
1045 (tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
1046 (tm_end.use && (datecmp(loctime, &tm_end) >= 0))));
1048 /* Save the first stats collected. Used for example in next_slice() function */
1049 copy_structures(act, id_seq, record_hdr, 2, 0);
1057 eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, curr,
1058 file, &rtype, tab, file_magic, file_actlst,
1061 if (!eosaf && (rtype != R_COMMENT) && (rtype != R_RESTART)) {
1062 if (*fmt[f_position]->f_statistics) {
1063 (*fmt[f_position]->f_statistics)(&tab, F_MAIN);
1066 /* next is set to 1 when we were close enough to desired interval */
1067 next = generic_write_stats(curr, tm_start.use, tm_end.use, reset,
1068 &cnt, &tab, cpu_nr, rectime, loctime,
1069 FALSE, ALL_ACTIVITIES);
1080 while (cnt && !eosaf && (rtype != R_RESTART));
1083 /* Go to next Linux restart, if possible */
1085 eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, curr,
1086 file, &rtype, tab, file_magic, file_actlst,
1089 while (!eosaf && (rtype != R_RESTART));
1096 if (*fmt[f_position]->f_statistics) {
1097 (*fmt[f_position]->f_statistics)(&tab, F_END);
1100 /* Rewind file... */
1101 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
1106 * ... and restore number of items for volatile activities
1107 * for this position in file.
1109 sr_act_nr(save_act_nr, DO_RESTORE);
1111 /* Process now RESTART entries to display restart messages */
1112 if (*fmt[f_position]->f_restart) {
1113 (*fmt[f_position]->f_restart)(&tab, F_BEGIN, NULL, NULL, FALSE,
1118 eosaf = read_next_sample(ifd, IGNORE_COMMENT, 0,
1119 file, &rtype, tab, file_magic, file_actlst,
1124 if (*fmt[f_position]->f_restart) {
1125 (*fmt[f_position]->f_restart)(&tab, F_END, NULL, NULL, FALSE, &file_hdr, 0);
1128 /* Rewind file... */
1129 if (lseek(ifd, fpos, SEEK_SET) < fpos) {
1134 * ... and restore number of items for volatile activities
1135 * for this position in file.
1137 sr_act_nr(save_act_nr, DO_RESTORE);
1139 /* Last, process COMMENT entries to display comments */
1140 if (DISPLAY_COMMENT(flags)) {
1141 if (*fmt[f_position]->f_comment) {
1142 (*fmt[f_position]->f_comment)(&tab, F_BEGIN, NULL, NULL, 0, NULL,
1146 eosaf = read_next_sample(ifd, IGNORE_RESTART, 0,
1147 file, &rtype, tab, file_magic, file_actlst,
1152 if (*fmt[f_position]->f_comment) {
1153 (*fmt[f_position]->f_comment)(&tab, F_END, NULL, NULL, 0, NULL,
1158 /* Print header trailer */
1159 if (*fmt[f_position]->f_header) {
1160 (*fmt[f_position]->f_header)(&tab, F_END, file, file_magic,
1161 &file_hdr, cpu_nr, act, id_seq);
1166 ***************************************************************************
1167 * Display file contents in selected format (logic #2).
1168 * Logic #2: Grouped by activity. Sorted by timestamp. Stop on RESTART
1173 * @ifd File descriptor of input file.
1174 * @file_actlst List of (known or unknown) activities in file.
1175 * @cpu_nr Number of processors for current activity data file.
1176 * @rectime Structure where timestamp (expressed in local time or in UTC
1177 * depending on whether options -T/-t have been used or not) can
1178 * be saved for current record.
1179 * @loctime Structure where timestamp (expressed in local time) can be
1180 * saved for current record.
1181 * @file Name of file being read.
1182 * @file_magic file_magic structure filled with file magic header data.
1183 ***************************************************************************
1185 void logic2_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr,
1186 struct tm *rectime, struct tm *loctime, char *file,
1187 struct file_magic *file_magic)
1190 int curr = 1, rtype;
1191 int eosaf = TRUE, reset = FALSE;
1195 /* Read system statistics from file */
1198 * If this record is a special (RESTART or COMMENT) one, print it and
1199 * (try to) get another one.
1202 if (read_next_sample(ifd, IGNORE_NOTHING, 0,
1203 file, &rtype, 0, file_magic, file_actlst,
1205 /* End of sa data file */
1208 while ((rtype == R_RESTART) || (rtype == R_COMMENT) ||
1209 (tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
1210 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)));
1212 /* Save the first stats collected. Used for example in next_slice() function */
1213 copy_structures(act, id_seq, record_hdr, 2, 0);
1215 /* Set flag to reset last_uptime variable. Should be done after a LINUX RESTART record */
1218 /* Save current file position */
1219 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
1224 /* Read and write stats located between two possible Linux restarts */
1226 if (DISPLAY_HORIZONTALLY(flags)) {
1228 * If stats are displayed horizontally, then all activities
1229 * are printed on the same line.
1231 rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf,
1232 ALL_ACTIVITIES, &reset, file_actlst,
1233 cpu_nr, rectime, loctime, file, file_magic);
1236 /* For each requested activity... */
1237 for (i = 0; i < NR_ACT; i++) {
1242 p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND);
1243 if (!IS_SELECTED(act[p]->options))
1246 if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) {
1247 rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf,
1248 act[p]->id, &reset, file_actlst,
1249 cpu_nr, rectime, loctime, file,
1253 unsigned int optf, msk;
1255 optf = act[p]->opt_flags;
1257 for (msk = 1; msk < 0x100; msk <<= 1) {
1258 if ((act[p]->opt_flags & 0xff) & msk) {
1259 act[p]->opt_flags &= (0xffffff00 + msk);
1261 rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf,
1262 act[p]->id, &reset, file_actlst,
1263 cpu_nr, rectime, loctime, file,
1265 act[p]->opt_flags = optf;
1273 /* Go to next Linux restart, if possible */
1275 eosaf = read_next_sample(ifd, IGNORE_RESTART | DONT_READ_VOLATILE,
1276 curr, file, &rtype, 0, file_magic,
1277 file_actlst, rectime, loctime);
1279 while (!eosaf && (rtype != R_RESTART));
1283 * The last record we read was a RESTART one: Print it.
1284 * NB: Unlike COMMENTS records (which are displayed for each
1285 * activity), RESTART ones are only displayed once.
1287 if (!eosaf && (record_hdr[curr].record_type == R_RESTART)) {
1288 print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end,
1289 R_RESTART, ifd, rectime, loctime, file, 0,
1290 file_magic, &file_hdr, act, fmt[f_position]);
1297 ***************************************************************************
1298 * Display file contents in selected format (logic #3).
1299 * Logic #3: Special logic for SVG output format.
1303 * @ifd File descriptor of input file.
1304 * @file_actlst List of (known or unknown) activities in file.
1305 * @cpu_nr Number of processors for current activity data file.
1306 * @rectime Structure where timestamp (expressed in local time or in UTC
1307 * depending on whether options -T/-t have been used or not) can
1308 * be saved for current record.
1309 * @loctime Structure where timestamp (expressed in local time) can be
1310 * saved for current record.
1311 * @file Name of file being read.
1312 * @file_magic file_magic structure filled with file magic header data.
1313 ***************************************************************************
1315 void logic3_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr,
1316 struct tm *rectime, struct tm *loctime, char *file,
1317 struct file_magic *file_magic)
1320 int curr = 1, rtype, g_nr = 0;
1321 int eosaf = TRUE, reset = TRUE;
1325 __nr_t save_act_nr[NR_ACT] = {0};
1327 /* Use a decimal point to make SVG code locale independent */
1328 setlocale(LC_NUMERIC, "C");
1330 /* Calculate the number of graphs to display */
1331 graph_nr = get_svg_graph_nr(ifd, file, file_magic,
1332 file_actlst, rectime, loctime);
1334 /* No graph to display */
1337 /* Print SVG header */
1338 if (*fmt[f_position]->f_header) {
1339 (*fmt[f_position]->f_header)(&graph_nr, F_BEGIN + F_MAIN, file, file_magic,
1340 &file_hdr, cpu_nr, act, id_seq);
1344 * If this record is a special (RESTART or COMMENT) one, ignore it and
1345 * (try to) get another one.
1348 if (read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT, 0,
1349 file, &rtype, 0, file_magic, file_actlst,
1351 /* End of sa data file */
1354 while ((rtype == R_RESTART) || (rtype == R_COMMENT) ||
1355 (tm_start.use && (datecmp(loctime, &tm_start) < 0)) ||
1356 (tm_end.use && (datecmp(loctime, &tm_end) >= 0)));
1358 /* Save the first stats collected. Used for example in next_slice() function */
1359 copy_structures(act, id_seq, record_hdr, 2, 0);
1361 /* Save current file position */
1362 if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) {
1366 /* Save number of activities items for current file position */
1367 sr_act_nr(save_act_nr, DO_SAVE);
1369 /* For each requested activity, display graphs */
1370 for (i = 0; i < NR_ACT; i++) {
1375 p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND);
1376 if (!IS_SELECTED(act[p]->options) || !act[p]->g_nr)
1379 if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) {
1380 display_curr_act_graphs(ifd, fpos, &curr, &cnt, &eosaf,
1381 p, &reset, file_actlst,
1382 cpu_nr, rectime, loctime, file,
1383 file_magic, save_act_nr, &g_nr);
1386 unsigned int optf, msk;
1388 optf = act[p]->opt_flags;
1390 for (msk = 1; msk < 0x100; msk <<= 1) {
1391 if ((act[p]->opt_flags & 0xff) & msk) {
1392 act[p]->opt_flags &= (0xffffff00 + msk);
1393 display_curr_act_graphs(ifd, fpos, &curr, &cnt, &eosaf,
1394 p, &reset, file_actlst,
1395 cpu_nr, rectime, loctime, file,
1396 file_magic, save_act_nr, &g_nr);
1397 act[p]->opt_flags = optf;
1403 /* Print SVG trailer */
1404 if (*fmt[f_position]->f_header) {
1405 (*fmt[f_position]->f_header)(&graph_nr, F_END, file, file_magic,
1406 &file_hdr, cpu_nr, act, id_seq);
1411 ***************************************************************************
1412 * Check system activity datafile contents before displaying stats.
1413 * Display file header if option -H has been entered, else call function
1414 * corresponding to selected output format.
1417 * @dfile System activity data file name.
1418 ***************************************************************************
1420 void read_stats_from_file(char dfile[])
1422 struct file_magic file_magic;
1423 struct file_activity *file_actlst = NULL;
1424 struct tm rectime, loctime;
1425 int ifd, ignore, tab = 0;
1428 /* Prepare file for reading and read its headers */
1429 ignore = ACCEPT_BAD_FILE_FORMAT(fmt[f_position]->options);
1430 check_file_actlst(&ifd, dfile, act, &file_magic, &file_hdr,
1431 &file_actlst, id_seq, ignore);
1433 /* Now pick up number of proc for this file */
1434 cpu_nr = act[get_activity_position(act, A_CPU, EXIT_IF_NOT_FOUND)]->nr;
1436 if (DISPLAY_HDR_ONLY(flags)) {
1437 if (*fmt[f_position]->f_header) {
1438 /* Display only data file header then exit */
1439 (*fmt[f_position]->f_header)(&tab, F_BEGIN + F_END, dfile, &file_magic,
1440 &file_hdr, cpu_nr, act, id_seq);
1445 /* Perform required allocations */
1446 allocate_structures(act);
1448 /* Call function corresponding to selected output format */
1449 if (format == F_SVG_OUTPUT) {
1450 logic3_display_loop(ifd, file_actlst, cpu_nr,
1451 &rectime, &loctime, dfile, &file_magic);
1453 else if (DISPLAY_GROUPED_STATS(fmt[f_position]->options)) {
1454 logic2_display_loop(ifd, file_actlst, cpu_nr,
1455 &rectime, &loctime, dfile, &file_magic);
1458 logic1_display_loop(ifd, file_actlst, dfile,
1459 &file_magic, cpu_nr, &rectime, &loctime);
1465 free_structures(act);
1469 ***************************************************************************
1470 * Main entry to the sadf program
1471 ***************************************************************************
1473 int main(int argc, char **argv)
1475 int opt = 1, sar_options = 0;
1478 char dfile[MAX_FILE_LEN];
1484 /* Compute page shift in kB */
1490 /* Init National Language Support */
1494 tm_start.use = tm_end.use = FALSE;
1496 /* Allocate and init activity bitmaps */
1497 allocate_bitmaps(act);
1499 /* Init some structures */
1502 /* Process options */
1503 while (opt < argc) {
1505 if (!strcmp(argv[opt], "-I")) {
1506 if (argv[++opt] && sar_options) {
1507 if (parse_sar_I_opt(argv, &opt, act)) {
1516 else if (!strcmp(argv[opt], "-P")) {
1517 if (parse_sa_P_opt(argv, &opt, &flags, act)) {
1522 else if (!strcmp(argv[opt], "-s")) {
1523 /* Get time start */
1524 if (parse_timestamp(argv, &opt, &tm_start, DEF_TMSTART)) {
1529 else if (!strcmp(argv[opt], "-e")) {
1531 if (parse_timestamp(argv, &opt, &tm_end, DEF_TMEND)) {
1536 else if (!strcmp(argv[opt], "-O")) {
1537 /* Parse SVG options */
1538 if (!argv[++opt] || sar_options) {
1541 for (t = strtok(argv[opt], ","); t; t = strtok(NULL, ",")) {
1542 if (!strcmp(t, K_SKIP_EMPTY)) {
1543 flags |= S_F_SVG_SKIP;
1545 else if (!strcmp(t, K_AUTOSCALE)) {
1546 flags |= S_F_SVG_AUTOSCALE;
1548 else if (!strcmp(t, K_ONEDAY)) {
1549 flags |= S_F_SVG_ONE_DAY;
1558 else if ((strlen(argv[opt]) > 1) &&
1559 (strlen(argv[opt]) < 4) &&
1560 !strncmp(argv[opt], "-", 1) &&
1561 (strspn(argv[opt] + 1, DIGITS) == (strlen(argv[opt]) - 1))) {
1562 if (dfile[0] || day_offset) {
1563 /* File already specified */
1566 day_offset = atoi(argv[opt++] + 1);
1569 else if (!strcmp(argv[opt], "--")) {
1574 else if (!strcmp(argv[opt], "-m")) {
1575 if (argv[++opt] && sar_options) {
1576 /* Parse sar's option -m */
1577 if (parse_sar_m_opt(argv, &opt, act)) {
1586 else if (!strcmp(argv[opt], "-n")) {
1587 if (argv[++opt] && sar_options) {
1588 /* Parse sar's option -n */
1589 if (parse_sar_n_opt(argv, &opt, act)) {
1598 else if (!strncmp(argv[opt], "-", 1)) {
1599 /* Other options not previously tested */
1601 if ((rc = parse_sar_opt(argv, &opt, act, &flags, C_SADF)) != 0) {
1610 for (i = 1; *(argv[opt] + i); i++) {
1612 switch (*(argv[opt] + i)) {
1615 flags |= S_F_COMMENT;
1622 format = F_CONV_OUTPUT;
1629 format = F_DB_OUTPUT;
1636 format = F_SVG_OUTPUT;
1640 flags |= S_F_HORIZONTALLY;
1644 flags |= S_F_HDR_ONLY;
1651 format = F_JSON_OUTPUT;
1658 format = F_PPC_OUTPUT;
1662 flags |= S_F_LOCAL_TIME;
1666 flags |= S_F_TRUE_TIME;
1670 flags |= S_F_SEC_EPOCH;
1677 format = F_XML_OUTPUT;
1692 /* Get data file name */
1693 else if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) {
1694 if (dfile[0] || day_offset) {
1695 /* File already specified */
1698 if (!strcmp(argv[opt], "-")) {
1699 /* File name set to '-' */
1700 set_default_file(dfile, 0, -1);
1703 else if (!strncmp(argv[opt], "-", 1)) {
1708 /* Write data to file */
1709 strncpy(dfile, argv[opt++], MAX_FILE_LEN);
1710 dfile[MAX_FILE_LEN - 1] = '\0';
1711 /* Check if this is an alternate directory for sa files */
1712 check_alt_sa_dir(dfile, 0, -1);
1716 else if (interval < 0) {
1718 if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) {
1721 interval = atol(argv[opt++]);
1722 if (interval <= 0) {
1728 /* Get count value */
1729 if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) {
1733 /* Count parameter already set */
1736 count = atol(argv[opt++]);
1741 count = -1; /* To generate a report continuously */
1746 /* sadf reads current daily data file by default */
1748 set_default_file(dfile, day_offset, -1);
1751 if (tm_start.use && tm_end.use && (tm_end.tm_hour < tm_start.tm_hour)) {
1752 tm_end.tm_hour += 24;
1755 if (USE_PRETTY_OPTION(flags)) {
1756 dm_major = get_devmap_major();
1759 /* Options -T, -t and -U are mutually exclusive */
1760 if ((PRINT_LOCAL_TIME(flags) + PRINT_TRUE_TIME(flags) +
1761 PRINT_SEC_EPOCH(flags)) > 1) {
1766 * Display all the contents of the daily data file if the count parameter
1767 * was not set on the command line.
1773 /* Default is CPU activity */
1774 select_default_activity(act);
1776 /* Check options consistency with selected output format. Default is PPC display */
1777 check_format_options();
1783 if (format == F_CONV_OUTPUT) {
1784 /* Convert file to current format */
1785 convert_file(dfile, act);
1788 /* Read stats from file */
1789 read_stats_from_file(dfile);