/* *************************************************************************** * Print system statistics. * * IN: * @curr Index in array for current sample statistics. * @read_from_file Set to TRUE if stats are read from a system activity * data file. * @use_tm_start Set to TRUE if option -s has been used. * @use_tm_end Set to TRUE if option -e has been used. * @reset Set to TRUE if last_uptime variable should be * reinitialized (used in next_slice() function). * @act_id Activity that can be displayed or ~0 for all. * Remember that when reading stats from a file, only * one activity can be displayed at a time. * * OUT: * @cnt Number of remaining lines to display. * * RETURNS: * 1 if stats have been successfully displayed, and 0 otherwise. *************************************************************************** */ int write_stats(int curr, int read_from_file, long *cnt, int use_tm_start, int use_tm_end, int reset, unsigned int act_id) { int i; unsigned long long itv, g_itv; static int cross_day = 0; static __nr_t cpu_nr = -1; if (cpu_nr < 0) cpu_nr = act[get_activity_position(act, A_CPU)]->nr; /* Check time (1) */ if (read_from_file) { if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0, reset, interval)) /* Not close enough to desired interval */ return 0; } /* Set previous timestamp */ if (set_record_timestamp_string(!curr, timestamp[!curr], 16)) return 0; /* Set current timestamp */ if (set_record_timestamp_string(curr, timestamp[curr], 16)) return 0; /* Check if we are beginning a new day */ if (use_tm_start && record_hdr[!curr].ust_time && (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) && (record_hdr[curr].hour < record_hdr[!curr].hour)) { cross_day = 1; } if (cross_day) { /* * This is necessary if we want to properly handle something like: * sar -s time_start -e time_end with * time_start(day D) > time_end(day D+1) */ rectime.tm_hour +=24; } /* Check time (2) */ if (use_tm_start && (datecmp(&rectime, &tm_start) < 0)) /* it's too soon... */ return 0; /* Get interval values */ get_itv_value(&record_hdr[curr], &record_hdr[!curr], cpu_nr, &itv, &g_itv); /* Check time (3) */ if (use_tm_end && (datecmp(&rectime, &tm_end) > 0)) { /* It's too late... */ *cnt = 0; return 0; } avg_count++; /* Test stdout */ TEST_STDOUT(STDOUT_FILENO); for (i = 0; i < NR_ACT; i++) { if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id)) continue; if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) { /* Display current activity statistics */ (*act[i]->f_print)(act[i], !curr, curr, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } } return 1; }
/* *************************************************************************** * Print system statistics. * * IN: * @curr Index in array for current sample statistics. * @read_from_file Set to TRUE if stats are read from a system activity * data file. * @use_tm_start Set to TRUE if option -s has been used. * @use_tm_end Set to TRUE if option -e has been used. * @reset Set to TRUE if last_uptime variable should be * reinitialized (used in next_slice() function). * @act_id Activity that can be displayed or ~0 for all. * Remember that when reading stats from a file, only * one activity can be displayed at a time. * @reset_cd TRUE if static cross_day variable should be reset * (see below). * * OUT: * @cnt Number of remaining lines to display. * * RETURNS: * 1 if stats have been successfully displayed, and 0 otherwise. *************************************************************************** */ int write_stats(int curr, int read_from_file, long *cnt, int use_tm_start, int use_tm_end, int reset, unsigned int act_id, int reset_cd) { int i; unsigned long long itv, g_itv; static int cross_day = 0; static __nr_t cpu_nr = -1; if (cpu_nr < 0) cpu_nr = act[get_activity_position(act, A_CPU, EXIT_IF_NOT_FOUND)]->nr; if (reset_cd) { /* * cross_day is a static variable that is set to 1 when the first * record of stats from a new day is read from a unique data file * (in the case where the file contains data from two consecutive * days). When set to 1, every following records timestamp will * have its hour value increased by 24. * Yet when a new activity (being read from the file) is going to * be displayed, we start reading the file from the beginning * again, and so cross_day should be reset in this case. */ cross_day = 0; } /* Check time (1) */ if (read_from_file) { if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0, reset, interval)) /* Not close enough to desired interval */ return 0; } /* Set previous timestamp */ if (set_record_timestamp_string(!curr, timestamp[!curr], 16)) return 0; /* Set current timestamp */ if (set_record_timestamp_string(curr, timestamp[curr], 16)) return 0; /* Check if we are beginning a new day */ if (use_tm_start && record_hdr[!curr].ust_time && (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) && (record_hdr[curr].hour < record_hdr[!curr].hour)) { cross_day = 1; } if (cross_day) { /* * This is necessary if we want to properly handle something like: * sar -s time_start -e time_end with * time_start(day D) > time_end(day D+1) */ rectime.tm_hour +=24; } /* Check time (2) */ if (use_tm_start && (datecmp(&rectime, &tm_start) < 0)) /* it's too soon... */ return 0; /* Get interval values */ get_itv_value(&record_hdr[curr], &record_hdr[!curr], cpu_nr, &itv, &g_itv); /* Check time (3) */ if (use_tm_end && (datecmp(&rectime, &tm_end) > 0)) { /* It's too late... */ *cnt = 0; return 0; } avg_count++; /* Test stdout */ TEST_STDOUT(STDOUT_FILENO); for (i = 0; i < NR_ACT; i++) { if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id)) continue; if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) { /* Display current activity statistics */ (*act[i]->f_print)(act[i], !curr, curr, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } } return 1; }
/* *************************************************************************** * Read statistics from a system activity data file * * IN: * @from_file Input file name. *************************************************************************** */ void read_stats_from_file(char from_file[]) { struct file_magic file_magic; struct file_activity *file_actlst = NULL; int curr = 1, i, p; int ifd, rtype; int rows, eosaf = TRUE, reset = FALSE; long cnt = 1; off_t fpos; /* Get window size */ rows = get_win_height(); /* Read file headers and activity list */ check_file_actlst(&ifd, from_file, act, &file_magic, &file_hdr, &file_actlst, id_seq, FALSE); /* Perform required allocations */ allocate_structures(act); /* Print report header */ print_report_hdr(flags, &rectime, &file_hdr); /* Read system statistics from file */ do { /* * If this record is a special (RESTART or COMMENT) one, print it and * (try to) get another one. */ do { if (sa_fread(ifd, &record_hdr[0], RECORD_HEADER_SIZE, SOFT_SIZE)) /* End of sa data file */ return; rtype = record_hdr[0].record_type; if ((rtype == R_RESTART) || (rtype == R_COMMENT)) { sar_print_special(0, tm_start.use, tm_end.use, rtype, ifd); } else { /* * Ok: previous record was not a special one. * So read now the extra fields. */ read_file_stat_bunch(act, 0, ifd, file_hdr.sa_nr_act, file_actlst); sar_set_rectime(0); } } while ((rtype == R_RESTART) || (rtype == R_COMMENT) || (tm_start.use && (datecmp(&rectime, &tm_start) < 0)) || (tm_end.use && (datecmp(&rectime, &tm_end) >=0))); /* Save the first stats collected. Will be used to compute the average */ copy_structures(act, id_seq, record_hdr, 2, 0); reset = TRUE; /* Set flag to reset last_uptime variable */ /* Save current file position */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } /* Read and write stats located between two possible Linux restarts */ for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; if ((p = get_activity_position(act, id_seq[i])) < 0) { /* Should never happen */ PANIC(1); } if (!IS_SELECTED(act[p]->options)) continue; if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) { handle_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, rows, act[p]->id, &reset, file_actlst); } else { unsigned int optf, msk; optf = act[p]->opt_flags; for (msk = 1; msk < 0x10; msk <<= 1) { if (act[p]->opt_flags & msk) { act[p]->opt_flags &= msk; handle_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, rows, act[p]->id, &reset, file_actlst); act[p]->opt_flags = optf; } } } } if (!cnt) { /* Go to next Linux restart, if possible */ do { eosaf = sa_fread(ifd, &record_hdr[curr], RECORD_HEADER_SIZE, SOFT_SIZE); rtype = record_hdr[curr].record_type; if (!eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) { read_file_stat_bunch(act, curr, ifd, file_hdr.sa_nr_act, file_actlst); } } while (!eosaf && (rtype != R_RESTART)); } /* The last record we read was a RESTART one: Print it */ if (!eosaf && (record_hdr[curr].record_type == R_RESTART)) { sar_print_special(curr, tm_start.use, tm_end.use, R_RESTART, ifd); } } while (!eosaf); close(ifd); }
/* *************************************************************************** * Display activity records for textual (XML-like) formats. * * IN: * @curr Index in array for current sample statistics. * @use_tm_start Set to TRUE if option -s has been used. * @use_tm_end Set to TRUE if option -e has been used. * @reset Set to TRUE if last_uptime should be reinitialized * (used in next_slice() function). * @tab Number of tabulations to print. * @cpu_nr Number of processors. * @rectime Structure where timestamp (expressed in local time * or in UTC depending on whether option -t has been * used or not) can be saved for current record. * @loctime Structure where timestamp (expressed in local time) * can be saved for current record. * * OUT: * @cnt Set to 0 to indicate that no other lines of stats * should be displayed. * * RETURNS: * 1 if stats have been successfully displayed. *************************************************************************** */ int write_textual_stats(int curr, int use_tm_start, int use_tm_end, int reset, long *cnt, int tab, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime) { int i; unsigned long long dt, itv, g_itv; char cur_date[32], cur_time[32]; static int cross_day = FALSE; /* Fill timestamp structure (rectime) for current record */ sadf_get_record_timestamp_struct(curr, rectime, loctime); /* * Check time (1). * For this first check, we use the time interval entered on * the command line. This is equivalent to sar's option -i which * selects records at seconds as close as possible to the number * specified by the interval parameter. */ if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0, reset, interval)) /* Not close enough to desired interval */ return 0; /* Check if we are beginning a new day */ if (use_tm_start && record_hdr[!curr].ust_time && (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) && (record_hdr[curr].hour < record_hdr[!curr].hour)) { cross_day = TRUE; } if (cross_day) { /* * This is necessary if we want to properly handle something like: * sar -s time_start -e time_end with * time_start(day D) > time_end(day D+1) */ loctime->tm_hour += 24; } /* Check time (2) */ if (use_tm_start && (datecmp(loctime, &tm_start) < 0)) /* it's too soon... */ return 0; /* Get interval values */ get_itv_value(&record_hdr[curr], &record_hdr[!curr], cpu_nr, &itv, &g_itv); /* Check time (3) */ if (use_tm_end && (datecmp(loctime, &tm_end) > 0)) { /* It's too late... */ *cnt = 0; return 0; } dt = itv / HZ; /* Correct rounding error for dt */ if ((itv % HZ) >= (HZ / 2)) { dt++; } /* Set date and time strings for current record */ set_record_timestamp_string(curr, cur_date, cur_time, 32, rectime); if (*fmt[f_position]->f_timestamp) { (*fmt[f_position]->f_timestamp)(&tab, F_BEGIN, cur_date, cur_time, !PRINT_TRUE_TIME(flags), dt); } if (format == F_XML_OUTPUT) { tab++; } /* Display textual statistics */ for (i = 0; i < NR_ACT; i++) { /* This code is not generic at all...! */ if (format == F_JSON_OUTPUT) { /* JSON output */ if (CLOSE_MARKUP(act[i]->options) || (IS_SELECTED(act[i]->options) && (act[i]->nr > 0))) { if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) { printf(","); if (*fmt[f_position]->f_timestamp) { (*fmt[f_position]->f_timestamp)(&tab, F_MAIN, cur_date, cur_time, !PRINT_TRUE_TIME(flags), dt); } } (*act[i]->f_json_print)(act[i], curr, tab, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } } else { /* XML output */ if (CLOSE_MARKUP(act[i]->options) || (IS_SELECTED(act[i]->options) && (act[i]->nr > 0))) { (*act[i]->f_xml_print)(act[i], curr, tab, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } } } if (*fmt[f_position]->f_timestamp) { (*fmt[f_position]->f_timestamp)(&tab, F_END, cur_date, cur_time, !PRINT_TRUE_TIME(flags), dt); } return 1; }
/* *************************************************************************** * Display activities for textual (XML-like) formats. * * IN: * @ifd File descriptor of input file. * @file_actlst List of (known or unknown) activities in file. * @dfile System activity data file name. * @file_magic System activity file magic header. * @cpu_nr Number of processors for current activity data file. * @rectime Structure where timestamp (expressed in local time or in UTC * depending on whether option -t has been used or not) can be * saved for current record. * @loctime Structure where timestamp (expressed in local time) can be * saved for current record. *************************************************************************** */ void textual_display_loop(int ifd, struct file_activity *file_actlst, char *dfile, struct file_magic *file_magic, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime) { int curr, tab = 0, rtype; int eosaf = TRUE, next, reset = FALSE; long cnt = 1; off_t fpos; /* Save current file position */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } /* Print header (eg. XML file header) */ if (*fmt[f_position]->f_header) { (*fmt[f_position]->f_header)(&tab, F_BEGIN, dfile, file_magic, &file_hdr, cpu_nr, act, id_seq); } /* Process activities */ if (*fmt[f_position]->f_statistics) { (*fmt[f_position]->f_statistics)(&tab, F_BEGIN); } do { /* * If this record is a special (RESTART or COMMENT) one, * skip it and try to read the next record in file. */ do { eosaf = sa_fread(ifd, &record_hdr[0], RECORD_HEADER_SIZE, SOFT_SIZE); rtype = record_hdr[0].record_type; if (!eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) { /* * OK: Previous record was not a special one. * So read now the extra fields. */ read_file_stat_bunch(act, 0, ifd, file_hdr.sa_nr_act, file_actlst); sadf_get_record_timestamp_struct(0, rectime, loctime); } if (!eosaf && (rtype == R_COMMENT)) { /* * Ignore COMMENT record. * (Unlike RESTART records, COMMENT records have an additional * comment field). */ if (lseek(ifd, MAX_COMMENT_LEN, SEEK_CUR) < MAX_COMMENT_LEN) { perror("lseek"); } } } while (!eosaf && ((rtype == R_RESTART) || (rtype == R_COMMENT) || (tm_start.use && (datecmp(loctime, &tm_start) < 0)) || (tm_end.use && (datecmp(loctime, &tm_end) >=0)))); /* Save the first stats collected. Used for example in next_slice() function */ copy_structures(act, id_seq, record_hdr, 2, 0); curr = 1; cnt = count; reset = TRUE; if (!eosaf) { do { eosaf = sa_fread(ifd, &record_hdr[curr], RECORD_HEADER_SIZE, SOFT_SIZE); rtype = record_hdr[curr].record_type; if (!eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) { /* Read the extra fields since it's not a special record */ read_file_stat_bunch(act, curr, ifd, file_hdr.sa_nr_act, file_actlst); if (*fmt[f_position]->f_statistics) { (*fmt[f_position]->f_statistics)(&tab, F_MAIN); } /* next is set to 1 when we were close enough to desired interval */ next = write_textual_stats(curr, tm_start.use, tm_end.use, reset, &cnt, tab, cpu_nr, rectime, loctime); if (next) { curr ^= 1; if (cnt > 0) { cnt--; } } reset = FALSE; } if (!eosaf && (rtype == R_COMMENT)) { /* Ignore COMMENT record */ if (lseek(ifd, MAX_COMMENT_LEN, SEEK_CUR) < MAX_COMMENT_LEN) { perror("lseek"); } } } while (cnt && !eosaf && (rtype != R_RESTART)); if (!cnt) { /* Go to next Linux restart, if possible */ do { eosaf = sa_fread(ifd, &record_hdr[curr], RECORD_HEADER_SIZE, SOFT_SIZE); rtype = record_hdr[curr].record_type; if (!eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) { read_file_stat_bunch(act, curr, ifd, file_hdr.sa_nr_act, file_actlst); } else if (!eosaf && (rtype == R_COMMENT)) { /* Ignore COMMENT record */ if (lseek(ifd, MAX_COMMENT_LEN, SEEK_CUR) < MAX_COMMENT_LEN) { perror("lseek"); } } } while (!eosaf && (rtype != R_RESTART)); } reset = TRUE; } } while (!eosaf); if (*fmt[f_position]->f_statistics) { (*fmt[f_position]->f_statistics)(&tab, F_END); } /* Rewind file */ if (lseek(ifd, fpos, SEEK_SET) < fpos) { perror("lseek"); exit(2); } /* Process now RESTART entries to display restart messages */ if (*fmt[f_position]->f_restart) { (*fmt[f_position]->f_restart)(&tab, F_BEGIN, NULL, NULL, FALSE, &file_hdr); } do { if ((eosaf = sa_fread(ifd, &record_hdr[0], RECORD_HEADER_SIZE, SOFT_SIZE)) == 0) { rtype = record_hdr[0].record_type; if ((rtype != R_RESTART) && (rtype != R_COMMENT)) { read_file_stat_bunch(act, 0, ifd, file_hdr.sa_nr_act, file_actlst); } if (rtype == R_RESTART) { write_textual_restarts(0, tm_start.use, tm_end.use, tab, rectime, loctime); } else if (rtype == R_COMMENT) { /* Ignore COMMENT record */ if (lseek(ifd, MAX_COMMENT_LEN, SEEK_CUR) < MAX_COMMENT_LEN) { perror("lseek"); } } } } while (!eosaf); if (*fmt[f_position]->f_restart) { (*fmt[f_position]->f_restart)(&tab, F_END, NULL, NULL, FALSE, &file_hdr); } /* Rewind file */ if (lseek(ifd, fpos, SEEK_SET) < fpos) { perror("lseek"); exit(2); } /* Last, process COMMENT entries to display comments */ if (DISPLAY_COMMENT(flags)) { if (*fmt[f_position]->f_comment) { (*fmt[f_position]->f_comment)(&tab, F_BEGIN, NULL, NULL, 0, NULL, &file_hdr); } do { if ((eosaf = sa_fread(ifd, &record_hdr[0], RECORD_HEADER_SIZE, SOFT_SIZE)) == 0) { rtype = record_hdr[0].record_type; if ((rtype != R_RESTART) && (rtype != R_COMMENT)) { read_file_stat_bunch(act, 0, ifd, file_hdr.sa_nr_act, file_actlst); } if (rtype == R_COMMENT) { write_textual_comments(0, tm_start.use, tm_end.use, tab, ifd, rectime, loctime); } } } while (!eosaf); if (*fmt[f_position]->f_comment) { (*fmt[f_position]->f_comment)(&tab, F_END, NULL, NULL, 0, NULL, &file_hdr); } } /* Print header trailer */ if (*fmt[f_position]->f_header) { (*fmt[f_position]->f_header)(&tab, F_END, dfile, file_magic, &file_hdr, cpu_nr, act, id_seq); } }
/* *************************************************************************** * Display activities for non textual formats. * * IN: * @ifd File descriptor of input file. * @file_actlst List of (known or unknown) activities in file. * @cpu_nr Number of processors for current activity data file. * @rectime Structure where timestamp (expressed in local time or in UTC * depending on whether option -t has been used or not) can be * saved for current record. * @loctime Structure where timestamp (expressed in local time) can be * saved for current record. *************************************************************************** */ void main_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime) { int i, p; int curr = 1, rtype; int eosaf = TRUE, reset = FALSE; long cnt = 1; off_t fpos; /* Read system statistics from file */ do { /* * If this record is a special (RESTART or COMMENT) one, print it and * (try to) get another one. */ do { if (sa_fread(ifd, &record_hdr[0], RECORD_HEADER_SIZE, SOFT_SIZE)) /* End of sa data file */ return; rtype = record_hdr[0].record_type; if ((rtype == R_RESTART) || (rtype == R_COMMENT)) { sadf_print_special(0, tm_start.use, tm_end.use, rtype, ifd, rectime, loctime); } else { /* * OK: Previous record was not a special one. * So read now the extra fields. */ read_file_stat_bunch(act, 0, ifd, file_hdr.sa_nr_act, file_actlst); sadf_get_record_timestamp_struct(0, rectime, loctime); } } while ((rtype == R_RESTART) || (rtype == R_COMMENT) || (tm_start.use && (datecmp(loctime, &tm_start) < 0)) || (tm_end.use && (datecmp(loctime, &tm_end) >= 0))); /* Save the first stats collected. Will be used to compute the average */ copy_structures(act, id_seq, record_hdr, 2, 0); /* Set flag to reset last_uptime variable. Should be done after a LINUX RESTART record */ reset = TRUE; /* Save current file position */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } /* Read and write stats located between two possible Linux restarts */ if (DISPLAY_HORIZONTALLY(flags)) { /* * If stats are displayed horizontally, then all activities * are printed on the same line. */ rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, ALL_ACTIVITIES, &reset, file_actlst, cpu_nr, rectime, loctime); } else { /* For each requested activity... */ for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; if ((p = get_activity_position(act, id_seq[i])) < 0) { /* Should never happen */ PANIC(1); } if (!IS_SELECTED(act[p]->options)) continue; if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) { rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, act[p]->id, &reset, file_actlst, cpu_nr, rectime, loctime); } else { unsigned int optf, msk; optf = act[p]->opt_flags; for (msk = 1; msk < 0x10; msk <<= 1) { if (act[p]->opt_flags & msk) { act[p]->opt_flags &= msk; rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, act[p]->id, &reset, file_actlst, cpu_nr, rectime, loctime); act[p]->opt_flags = optf; } } } } } if (!cnt) { /* Go to next Linux restart, if possible */ do { eosaf = sa_fread(ifd, &record_hdr[curr], RECORD_HEADER_SIZE, SOFT_SIZE); rtype = record_hdr[curr].record_type; if (!eosaf && (rtype != R_RESTART) && (rtype != R_COMMENT)) { read_file_stat_bunch(act, curr, ifd, file_hdr.sa_nr_act, file_actlst); } else if (!eosaf && (rtype == R_COMMENT)) { /* This was a COMMENT record: print it */ sadf_print_special(curr, tm_start.use, tm_end.use, R_COMMENT, ifd, rectime, loctime); } } while (!eosaf && (rtype != R_RESTART)); } /* The last record we read was a RESTART one: Print it */ if (!eosaf && (record_hdr[curr].record_type == R_RESTART)) { sadf_print_special(curr, tm_start.use, tm_end.use, R_RESTART, ifd, rectime, loctime); } } while (!eosaf); }
/* *************************************************************************** * Write system statistics. * * IN: * @curr Index in array for current sample statistics. * @reset Set to TRUE if last_uptime variable should be * reinitialized (used in next_slice() function). * @use_tm_start Set to TRUE if option -s has been used. * @use_tm_end Set to TRUE if option -e has been used. * @act_id Activities to display. * @cpu_nr Number of processors for current activity data file. * @rectime Structure where timestamp (expressed in local time * or in UTC depending on whether option -t has been * used or not) can be saved for current record. * @loctime Structure where timestamp (expressed in local time) * can be saved for current record. * * OUT: * @cnt Set to 0 to indicate that no other lines of stats * should be displayed. * * RETURNS: * 1 if a line of stats has been displayed, and 0 otherwise. *************************************************************************** */ int write_parsable_stats(int curr, int reset, long *cnt, int use_tm_start, int use_tm_end, unsigned int act_id, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime) { unsigned long long dt, itv, g_itv; char cur_date[32], cur_time[32]; static int cross_day = FALSE; /* * Check time (1). * For this first check, we use the time interval entered on * the command line. This is equivalent to sar's option -i which * selects records at seconds as close as possible to the number * specified by the interval parameter. */ if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0, reset, interval)) /* Not close enough to desired interval */ return 0; /* Fill timestamp structure for current record */ sadf_get_record_timestamp_struct(curr, rectime, loctime); /* Check if we are beginning a new day */ if (use_tm_start && record_hdr[!curr].ust_time && (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) && (record_hdr[curr].hour < record_hdr[!curr].hour)) { cross_day = TRUE; } if (cross_day) { /* * This is necessary if we want to properly handle something like: * sar -s time_start -e time_end with * time_start(day D) > time_end(day D+1) */ loctime->tm_hour += 24; } /* Check time (2) */ if (use_tm_start && (datecmp(loctime, &tm_start) < 0)) /* it's too soon... */ return 0; /* Get interval values */ get_itv_value(&record_hdr[curr], &record_hdr[!curr], cpu_nr, &itv, &g_itv); /* Check time (3) */ if (use_tm_end && (datecmp(loctime, &tm_end) > 0)) { /* It's too late... */ *cnt = 0; return 0; } dt = itv / HZ; /* Correct rounding error for dt */ if ((itv % HZ) >= (HZ / 2)) { dt++; } /* Set current timestamp string */ set_record_timestamp_string(curr, cur_date, cur_time, 32, rectime); write_mech_stats(curr, dt, itv, g_itv, cur_date, cur_time, act_id); return 1; }
/* *************************************************************************** * Display file contents in selected format (logic #1). * Logic #1: Grouped by record type. Sorted by timestamp. * Formats: XML, JSON * * IN: * @ifd File descriptor of input file. * @file_actlst List of (known or unknown) activities in file. * @file System activity data file name (name of file being read). * @file_magic System activity file magic header. * @cpu_nr Number of processors for current activity data file. * @rectime Structure where timestamp (expressed in local time or in UTC * depending on whether options -T/-t have been used or not) can * be saved for current record. * @loctime Structure where timestamp (expressed in local time) can be * saved for current record. *************************************************************************** */ void logic1_display_loop(int ifd, struct file_activity *file_actlst, char *file, struct file_magic *file_magic, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime) { int curr, tab = 0, rtype; int eosaf, next, reset = FALSE; __nr_t save_act_nr[NR_ACT] = {0}; long cnt = 1; off_t fpos; /* Save current file position */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } /* Save number of activities items for current file position */ sr_act_nr(save_act_nr, DO_SAVE); /* Print header (eg. XML file header) */ if (*fmt[f_position]->f_header) { (*fmt[f_position]->f_header)(&tab, F_BEGIN, file, file_magic, &file_hdr, cpu_nr, act, id_seq); } /* Process activities */ if (*fmt[f_position]->f_statistics) { (*fmt[f_position]->f_statistics)(&tab, F_BEGIN); } do { /* * If this record is a special (RESTART or COMMENT) one, * skip it and try to read the next record in file. */ do { eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, 0, file, &rtype, tab, file_magic, file_actlst, rectime, loctime); } while (!eosaf && ((rtype == R_RESTART) || (rtype == R_COMMENT) || (tm_start.use && (datecmp(loctime, &tm_start) < 0)) || (tm_end.use && (datecmp(loctime, &tm_end) >= 0)))); /* Save the first stats collected. Used for example in next_slice() function */ copy_structures(act, id_seq, record_hdr, 2, 0); curr = 1; cnt = count; reset = TRUE; if (!eosaf) { do { eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, curr, file, &rtype, tab, file_magic, file_actlst, rectime, loctime); if (!eosaf && (rtype != R_COMMENT) && (rtype != R_RESTART)) { if (*fmt[f_position]->f_statistics) { (*fmt[f_position]->f_statistics)(&tab, F_MAIN); } /* next is set to 1 when we were close enough to desired interval */ next = generic_write_stats(curr, tm_start.use, tm_end.use, reset, &cnt, &tab, cpu_nr, rectime, loctime, FALSE, ALL_ACTIVITIES); if (next) { curr ^= 1; if (cnt > 0) { cnt--; } } reset = FALSE; } } while (cnt && !eosaf && (rtype != R_RESTART)); if (!cnt) { /* Go to next Linux restart, if possible */ do { eosaf = read_next_sample(ifd, IGNORE_COMMENT | IGNORE_RESTART, curr, file, &rtype, tab, file_magic, file_actlst, rectime, loctime); } while (!eosaf && (rtype != R_RESTART)); } reset = TRUE; } } while (!eosaf); if (*fmt[f_position]->f_statistics) { (*fmt[f_position]->f_statistics)(&tab, F_END); } /* Rewind file... */ if (lseek(ifd, fpos, SEEK_SET) < fpos) { perror("lseek"); exit(2); } /* * ... and restore number of items for volatile activities * for this position in file. */ sr_act_nr(save_act_nr, DO_RESTORE); /* Process now RESTART entries to display restart messages */ if (*fmt[f_position]->f_restart) { (*fmt[f_position]->f_restart)(&tab, F_BEGIN, NULL, NULL, FALSE, &file_hdr, 0); } do { eosaf = read_next_sample(ifd, IGNORE_COMMENT, 0, file, &rtype, tab, file_magic, file_actlst, rectime, loctime); } while (!eosaf); if (*fmt[f_position]->f_restart) { (*fmt[f_position]->f_restart)(&tab, F_END, NULL, NULL, FALSE, &file_hdr, 0); } /* Rewind file... */ if (lseek(ifd, fpos, SEEK_SET) < fpos) { perror("lseek"); exit(2); } /* * ... and restore number of items for volatile activities * for this position in file. */ sr_act_nr(save_act_nr, DO_RESTORE); /* Last, process COMMENT entries to display comments */ if (DISPLAY_COMMENT(flags)) { if (*fmt[f_position]->f_comment) { (*fmt[f_position]->f_comment)(&tab, F_BEGIN, NULL, NULL, 0, NULL, &file_hdr); } do { eosaf = read_next_sample(ifd, IGNORE_RESTART, 0, file, &rtype, tab, file_magic, file_actlst, rectime, loctime); } while (!eosaf); if (*fmt[f_position]->f_comment) { (*fmt[f_position]->f_comment)(&tab, F_END, NULL, NULL, 0, NULL, &file_hdr); } } /* Print header trailer */ if (*fmt[f_position]->f_header) { (*fmt[f_position]->f_header)(&tab, F_END, file, file_magic, &file_hdr, cpu_nr, act, id_seq); } }
/* *************************************************************************** * Display *one* sample of statistics for one or several activities, * checking that all conditions are met before printing (time start, time * end, interval). Current record should be a record of statistics (R_STATS), * not a special one (R_RESTART or R_COMMENT). * * IN: * @curr Index in array for current sample statistics. * @use_tm_start Set to TRUE if option -s has been used. * @use_tm_end Set to TRUE if option -e has been used. * @reset Set to TRUE if last_uptime should be reinitialized * (used in next_slice() function). * @parm Pointer on parameters depending on output format * (eg.: number of tabulations to print). * @cpu_nr Number of processors. * @rectime Structure where timestamp (expressed in local time * or in UTC depending on whether options -T/-t have * been used or not) has been saved for current record. * @loctime Structure where timestamp (expressed in local time) * has been saved for current record. * @reset_cd TRUE if static cross_day variable should be reset. * @act_id Activity to display (only for formats where * activities are displayed one at a time) or * ALL_ACTIVITIES for all. * * OUT: * @cnt Set to 0 to indicate that no other lines of stats * should be displayed. * * RETURNS: * 1 if stats have been successfully displayed. *************************************************************************** */ int generic_write_stats(int curr, int use_tm_start, int use_tm_end, int reset, long *cnt, void *parm, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime, int reset_cd, unsigned int act_id) { int i; unsigned long long dt, itv, g_itv; char cur_date[32], cur_time[32], *pre = NULL; static int cross_day = FALSE; if (reset_cd) { /* * See note in sar.c. * NB: Reseting cross_day is needed only if datafile * may be rewinded (eg. in db or ppc output formats). */ cross_day = 0; } /* * Check time (1). * For this first check, we use the time interval entered on * the command line. This is equivalent to sar's option -i which * selects records at seconds as close as possible to the number * specified by the interval parameter. */ if (!next_slice(record_hdr[2].uptime0, record_hdr[curr].uptime0, reset, interval)) /* Not close enough to desired interval */ return 0; /* Check if we are beginning a new day */ if (use_tm_start && record_hdr[!curr].ust_time && (record_hdr[curr].ust_time > record_hdr[!curr].ust_time) && (record_hdr[curr].hour < record_hdr[!curr].hour)) { cross_day = TRUE; } if (cross_day) { /* * This is necessary if we want to properly handle something like: * sar -s time_start -e time_end with * time_start(day D) > time_end(day D+1) */ loctime->tm_hour += 24; } /* Check time (2) */ if (use_tm_start && (datecmp(loctime, &tm_start) < 0)) /* it's too soon... */ return 0; /* Get interval values */ get_itv_value(&record_hdr[curr], &record_hdr[!curr], cpu_nr, &itv, &g_itv); /* Check time (3) */ if (use_tm_end && (datecmp(loctime, &tm_end) > 0)) { /* It's too late... */ *cnt = 0; return 0; } dt = itv / HZ; /* Correct rounding error for dt */ if ((itv % HZ) >= (HZ / 2)) { dt++; } /* Set date and time strings for current record */ set_record_timestamp_string(flags, &record_hdr[curr], cur_date, cur_time, 32, rectime); if (*fmt[f_position]->f_timestamp) { pre = (char *) (*fmt[f_position]->f_timestamp)(parm, F_BEGIN, cur_date, cur_time, dt, &file_hdr, flags); } /* Display statistics */ for (i = 0; i < NR_ACT; i++) { if ((act_id != ALL_ACTIVITIES) && (act[i]->id != act_id)) continue; if ((TEST_MARKUP(fmt[f_position]->options) && CLOSE_MARKUP(act[i]->options)) || (IS_SELECTED(act[i]->options) && (act[i]->nr > 0))) { if (format == F_JSON_OUTPUT) { /* JSON output */ int *tab = (int *) parm; if (IS_SELECTED(act[i]->options) && (act[i]->nr > 0)) { if (*fmt[f_position]->f_timestamp) { (*fmt[f_position]->f_timestamp)(tab, F_MAIN, cur_date, cur_time, dt, &file_hdr, flags); } } (*act[i]->f_json_print)(act[i], curr, *tab, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } else if (format == F_XML_OUTPUT) { /* XML output */ int *tab = (int *) parm; (*act[i]->f_xml_print)(act[i], curr, *tab, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } else if (format == F_SVG_OUTPUT) { /* SVG output */ struct svg_parm *svg_p = (struct svg_parm *) parm; svg_p->dt = (unsigned long) dt; (*act[i]->f_svg_print)(act[i], curr, F_MAIN, svg_p, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv, &record_hdr[curr]); } else { /* Other output formats: db, ppc */ (*act[i]->f_render)(act[i], (format == F_DB_OUTPUT), pre, curr, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } } } if (*fmt[f_position]->f_timestamp) { (*fmt[f_position]->f_timestamp)(parm, F_END, cur_date, cur_time, dt, &file_hdr, flags); } return 1; }
/* *************************************************************************** * Compute the number of SVG graphs to display. Each activity selected may * have several graphs. Moreover we have to take into account volatile * activities (eg. CPU) for which the number of graphs will depend on the * highest number of items (eg. maximum number of CPU) saved in the file. * This number may be higher than the real number of graphs that will be * displayed since some items have a preallocation constant. * * IN: * @ifd File descriptor of input file. * @file Name of file being read. * @file_magic file_magic structure filled with file magic header data. * @file_actlst List of (known or unknown) activities in file. * @rectime Structure where timestamp (expressed in local time or in UTC * depending on whether options -T/-t have been used or not) can * be saved for current record. * @loctime Structure where timestamp (expressed in local time) can be * saved for current record. * * RETURNS: * Total number of graphs to display, taking into account only activities * to be displayed, and selected period of time (options -s/-e). *************************************************************************** */ int get_svg_graph_nr(int ifd, char *file, struct file_magic *file_magic, struct file_activity *file_actlst, struct tm *rectime, struct tm *loctime) { int i, n, p, eosaf; int rtype, new_tot_g_nr, tot_g_nr = 0; off_t fpos; __nr_t save_act_nr[NR_ACT] = {0}; /* Save current file position and items number */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } sr_act_nr(save_act_nr, DO_SAVE); /* Init total number of graphs for each activity */ for (i = 0; i < NR_ACT; i++) { id_g_nr[i] = 0; } /* Look for the first record that will be displayed */ do { eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS, 0, file, &rtype, 0, file_magic, file_actlst, rectime, loctime); if (eosaf) /* No record to display => no graph too */ return 0; } while ((tm_start.use && (datecmp(loctime, &tm_start) < 0)) || (tm_end.use && (datecmp(loctime, &tm_end) >= 0))); do { new_tot_g_nr = 0; for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND); if (!IS_SELECTED(act[p]->options)) continue; if (ONE_GRAPH_PER_ITEM(act[p]->options)) { n = act[p]->g_nr * act[p]->nr; } else { n = act[p]->g_nr; } if (n > id_g_nr[i]) { id_g_nr[i] = n; } new_tot_g_nr += n; } if (new_tot_g_nr > tot_g_nr) { tot_g_nr = new_tot_g_nr; } do { eosaf = read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT | SET_TIMESTAMPS, 0, file, &rtype, 0, file_magic, file_actlst, rectime, loctime); if (eosaf || (tm_end.use && (datecmp(loctime, &tm_end) >= 0))) /* End of data file or end time exceeded */ break; } while (rtype != R_RESTART); if (eosaf || (tm_end.use && (datecmp(loctime, &tm_end) >= 0))) /* * End of file, or end time exceeded: * Current number of graphs is up-to-date. */ break; /* * If we have found a RESTART record then we have also read the list of volatile * activities following it, reallocated the structures and changed the number of * items (act[p]->nr) for those volatile activities. So loop again to compute * the new total number of graphs. */ } while (rtype == R_RESTART); /* Rewind file and restore items number */ if (lseek(ifd, fpos, SEEK_SET) < fpos) { perror("lseek"); exit(2); } sr_act_nr(save_act_nr, DO_RESTORE); return tot_g_nr; }
/* *************************************************************************** * Display file contents in selected format (logic #3). * Logic #3: Special logic for SVG output format. * Formats: SVG * * IN: * @ifd File descriptor of input file. * @file_actlst List of (known or unknown) activities in file. * @cpu_nr Number of processors for current activity data file. * @rectime Structure where timestamp (expressed in local time or in UTC * depending on whether options -T/-t have been used or not) can * be saved for current record. * @loctime Structure where timestamp (expressed in local time) can be * saved for current record. * @file Name of file being read. * @file_magic file_magic structure filled with file magic header data. *************************************************************************** */ void logic3_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime, char *file, struct file_magic *file_magic) { int i, p; int curr = 1, rtype, g_nr = 0; int eosaf = TRUE, reset = TRUE; long cnt = 1; off_t fpos; int graph_nr = 0; __nr_t save_act_nr[NR_ACT] = {0}; /* Use a decimal point to make SVG code locale independent */ setlocale(LC_NUMERIC, "C"); /* Calculate the number of graphs to display */ graph_nr = get_svg_graph_nr(ifd, file, file_magic, file_actlst, rectime, loctime); if (!graph_nr) /* No graph to display */ return; /* Print SVG header */ if (*fmt[f_position]->f_header) { (*fmt[f_position]->f_header)(&graph_nr, F_BEGIN + F_MAIN, file, file_magic, &file_hdr, cpu_nr, act, id_seq); } /* * If this record is a special (RESTART or COMMENT) one, ignore it and * (try to) get another one. */ do { if (read_next_sample(ifd, IGNORE_RESTART | IGNORE_COMMENT, 0, file, &rtype, 0, file_magic, file_actlst, rectime, loctime)) /* End of sa data file */ return; } while ((rtype == R_RESTART) || (rtype == R_COMMENT) || (tm_start.use && (datecmp(loctime, &tm_start) < 0)) || (tm_end.use && (datecmp(loctime, &tm_end) >= 0))); /* Save the first stats collected. Used for example in next_slice() function */ copy_structures(act, id_seq, record_hdr, 2, 0); /* Save current file position */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } /* Save number of activities items for current file position */ sr_act_nr(save_act_nr, DO_SAVE); /* For each requested activity, display graphs */ for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND); if (!IS_SELECTED(act[p]->options) || !act[p]->g_nr) continue; if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) { display_curr_act_graphs(ifd, fpos, &curr, &cnt, &eosaf, p, &reset, file_actlst, cpu_nr, rectime, loctime, file, file_magic, save_act_nr, &g_nr); } else { unsigned int optf, msk; optf = act[p]->opt_flags; for (msk = 1; msk < 0x100; msk <<= 1) { if ((act[p]->opt_flags & 0xff) & msk) { act[p]->opt_flags &= (0xffffff00 + msk); display_curr_act_graphs(ifd, fpos, &curr, &cnt, &eosaf, p, &reset, file_actlst, cpu_nr, rectime, loctime, file, file_magic, save_act_nr, &g_nr); act[p]->opt_flags = optf; } } } } /* Print SVG trailer */ if (*fmt[f_position]->f_header) { (*fmt[f_position]->f_header)(&graph_nr, F_END, file, file_magic, &file_hdr, cpu_nr, act, id_seq); } }
/* *************************************************************************** * Display file contents in selected format (logic #2). * Logic #2: Grouped by activity. Sorted by timestamp. Stop on RESTART * records. * Formats: ppc, CSV * * IN: * @ifd File descriptor of input file. * @file_actlst List of (known or unknown) activities in file. * @cpu_nr Number of processors for current activity data file. * @rectime Structure where timestamp (expressed in local time or in UTC * depending on whether options -T/-t have been used or not) can * be saved for current record. * @loctime Structure where timestamp (expressed in local time) can be * saved for current record. * @file Name of file being read. * @file_magic file_magic structure filled with file magic header data. *************************************************************************** */ void logic2_display_loop(int ifd, struct file_activity *file_actlst, __nr_t cpu_nr, struct tm *rectime, struct tm *loctime, char *file, struct file_magic *file_magic) { int i, p; int curr = 1, rtype; int eosaf = TRUE, reset = FALSE; long cnt = 1; off_t fpos; /* Read system statistics from file */ do { /* * If this record is a special (RESTART or COMMENT) one, print it and * (try to) get another one. */ do { if (read_next_sample(ifd, IGNORE_NOTHING, 0, file, &rtype, 0, file_magic, file_actlst, rectime, loctime)) /* End of sa data file */ return; } while ((rtype == R_RESTART) || (rtype == R_COMMENT) || (tm_start.use && (datecmp(loctime, &tm_start) < 0)) || (tm_end.use && (datecmp(loctime, &tm_end) >= 0))); /* Save the first stats collected. Used for example in next_slice() function */ copy_structures(act, id_seq, record_hdr, 2, 0); /* Set flag to reset last_uptime variable. Should be done after a LINUX RESTART record */ reset = TRUE; /* Save current file position */ if ((fpos = lseek(ifd, 0, SEEK_CUR)) < 0) { perror("lseek"); exit(2); } /* Read and write stats located between two possible Linux restarts */ if (DISPLAY_HORIZONTALLY(flags)) { /* * If stats are displayed horizontally, then all activities * are printed on the same line. */ rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, ALL_ACTIVITIES, &reset, file_actlst, cpu_nr, rectime, loctime, file, file_magic); } else { /* For each requested activity... */ for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND); if (!IS_SELECTED(act[p]->options)) continue; if (!HAS_MULTIPLE_OUTPUTS(act[p]->options)) { rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, act[p]->id, &reset, file_actlst, cpu_nr, rectime, loctime, file, file_magic); } else { unsigned int optf, msk; optf = act[p]->opt_flags; for (msk = 1; msk < 0x100; msk <<= 1) { if ((act[p]->opt_flags & 0xff) & msk) { act[p]->opt_flags &= (0xffffff00 + msk); rw_curr_act_stats(ifd, fpos, &curr, &cnt, &eosaf, act[p]->id, &reset, file_actlst, cpu_nr, rectime, loctime, file, file_magic); act[p]->opt_flags = optf; } } } } } if (!cnt) { /* Go to next Linux restart, if possible */ do { eosaf = read_next_sample(ifd, IGNORE_RESTART | DONT_READ_VOLATILE, curr, file, &rtype, 0, file_magic, file_actlst, rectime, loctime); } while (!eosaf && (rtype != R_RESTART)); } /* * The last record we read was a RESTART one: Print it. * NB: Unlike COMMENTS records (which are displayed for each * activity), RESTART ones are only displayed once. */ if (!eosaf && (record_hdr[curr].record_type == R_RESTART)) { print_special_record(&record_hdr[curr], flags, &tm_start, &tm_end, R_RESTART, ifd, rectime, loctime, file, 0, file_magic, &file_hdr, act, fmt[f_position]); } } while (!eosaf); }