/* *************************************************************************** * Read the various statistics sent by the data collector (sadc). * * IN: * @curr Index in array for current sample statistics. *************************************************************************** */ void read_sadc_stat_bunch(int curr) { int i, p; /* Read record header (type is always R_STATS since it is read from sadc) */ if (sa_read(&record_hdr[curr], RECORD_HEADER_SIZE)) { /* * SIGINT (sent by sadc) is likely to be received * while we are stuck in sa_read(). * If this happens then no data have to be read. */ if (sigint_caught) return; print_read_error(); } 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 (sa_read(act[p]->buf[curr], act[p]->fsize * act[p]->nr * act[p]->nr2)) { print_read_error(); } } }
/* *************************************************************************** * Check system activity datafile contents before displaying stats. * Display file header if option -H has been entered, else call function * corresponding to selected output format. * * IN: * @dfile System activity data file name. *************************************************************************** */ void read_stats_from_file(char dfile[]) { struct file_magic file_magic; struct file_activity *file_actlst = NULL; struct tm rectime, loctime; int ifd, ignore, tab = 0; __nr_t cpu_nr; /* Prepare file for reading and read its headers */ ignore = ACCEPT_BAD_FILE_FORMAT(fmt[f_position]->options); check_file_actlst(&ifd, dfile, act, &file_magic, &file_hdr, &file_actlst, id_seq, ignore); /* Now pick up number of proc for this file */ cpu_nr = act[get_activity_position(act, A_CPU, EXIT_IF_NOT_FOUND)]->nr; if (DISPLAY_HDR_ONLY(flags)) { if (*fmt[f_position]->f_header) { /* Display only data file header then exit */ (*fmt[f_position]->f_header)(&tab, F_BEGIN + F_END, dfile, &file_magic, &file_hdr, cpu_nr, act, id_seq); } exit(0); } /* Perform required allocations */ allocate_structures(act); /* Call function corresponding to selected output format */ if (format == F_SVG_OUTPUT) { logic3_display_loop(ifd, file_actlst, cpu_nr, &rectime, &loctime, dfile, &file_magic); } else if (DISPLAY_GROUPED_STATS(fmt[f_position]->options)) { logic2_display_loop(ifd, file_actlst, cpu_nr, &rectime, &loctime, dfile, &file_magic); } else { logic1_display_loop(ifd, file_actlst, dfile, &file_magic, cpu_nr, &rectime, &loctime); } close(ifd); free(file_actlst); free_structures(act); }
/* *************************************************************************** * Display data file header. * * IN: * @tab Number of tabulations (unused here). * @action Action expected from current function. * @dfile Name of system activity data file. * @file_magic System activity file magic header. * @file_hdr System activity file standard header. * @cpu_nr Number of processors for current daily data file. * @act Array of activities. * @id_seq Activity sequence. *************************************************************************** */ __printf_funct_t print_hdr_header(int *tab, int action, char *dfile, struct file_magic *file_magic, struct file_header *file_hdr, __nr_t cpu_nr, struct activity *act[], unsigned int id_seq[]) { int i, p; /* Actions F_BEGIN and F_END ignored */ if (action & F_BEGIN) { printf(_("System activity data file: %s (%#x)\n"), dfile, file_magic->format_magic); display_sa_file_version(stdout, file_magic); if (file_magic->format_magic != FORMAT_MAGIC) { return; } printf(_("Host: ")); print_gal_header(localtime((const time_t *) &(file_hdr->sa_ust_time)), file_hdr->sa_sysname, file_hdr->sa_release, file_hdr->sa_nodename, file_hdr->sa_machine, cpu_nr > 1 ? cpu_nr - 1 : 1); printf(_("Size of a long int: %d\n"), file_hdr->sa_sizeof_long); printf(_("List of activities:\n")); for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; if ((p = get_activity_position(act, id_seq[i])) < 0) { PANIC(id_seq[i]); } printf("%02d: %s\t(x%d)", act[p]->id, act[p]->name, act[p]->nr); if (act[p]->f_count2 || (act[p]->nr2 > 1)) { printf("\t(x%d)", act[p]->nr2); } if (act[p]->magic == ACTIVITY_MAGIC_UNKNOWN) { printf(_("\t[Unknown activity format]")); } printf("\n"); } } }
/* *************************************************************************** * Print statistics average. * * 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. * @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. *************************************************************************** */ void write_stats_avg(int curr, int read_from_file, unsigned int act_id) { int i; unsigned long long itv, g_itv; static __nr_t cpu_nr = -1; if (cpu_nr < 0) cpu_nr = act[get_activity_position(act, A_CPU)]->nr; /* Interval value in jiffies */ g_itv = get_interval(record_hdr[2].uptime, record_hdr[curr].uptime); if (cpu_nr > 1) itv = get_interval(record_hdr[2].uptime0, record_hdr[curr].uptime0); else itv = g_itv; strncpy(timestamp[curr], _("Average:"), TIMESTAMP_LEN); timestamp[curr][TIMESTAMP_LEN - 1] = '\0'; strcpy(timestamp[!curr], timestamp[curr]); /* 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 average activity statistics */ (*act[i]->f_print_avg)(act[i], 2, curr, NEED_GLOBAL_ITV(act[i]->options) ? g_itv : itv); } } if (read_from_file) { /* * Reset number of lines printed only if we read stats * from a system activity file. */ avg_count = 0; } }
/* *************************************************************************** * Print statistics average * * 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. * @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. *************************************************************************** */ void write_stats_avg(int curr, int read_from_file, unsigned int act_id) { int i; unsigned long long itv, g_itv; static __nr_t cpu_nr = -1; if (cpu_nr < 0) cpu_nr = act[get_activity_position(act, A_CPU)]->nr; /* Interval value in jiffies */ g_itv = get_interval(record_hdr[2].uptime, record_hdr[curr].uptime); if (cpu_nr > 1) itv = get_interval(record_hdr[2].uptime0, record_hdr[curr].uptime0); else itv = g_itv; strcpy(timestamp[curr], _("Average:")); strcpy(timestamp[!curr], timestamp[curr]); /* 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 average activity statistics */ if (NEEDS_GLOBAL_ITV(act[i]->options)) (*act[i]->f_print_avg)(act[i], 2, curr, g_itv); else (*act[i]->f_print_avg)(act[i], 2, curr, itv); } } if (read_from_file) { /* Reset counters only if we read stats from a system activity file */ memset(&asum, 0, STATS_SUM_SIZE); } }
/* *************************************************************************** * Read the various statistics sent by the data collector (sadc). * * IN: * @curr Index in array for current sample statistics. *************************************************************************** */ void read_sadc_stat_bunch(int curr) { int i, p; /* Read record header (type is always R_STATS since it is read from sadc) */ if (sa_read(&record_hdr[curr], RECORD_HEADER_SIZE)) { print_read_error(); } 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 (sa_read(act[p]->buf[curr], act[p]->fsize * act[p]->nr * act[p]->nr2)) { print_read_error(); } } }
/* *************************************************************************** * 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 data file header. * * IN: * @parm Specific parameter (unused here). * @action Action expected from current function. * @dfile Name of system activity data file. * @file_magic System activity file magic header. * @file_hdr System activity file standard header. * @cpu_nr Number of processors for current daily data file. * @act Array of activities. * @id_seq Activity sequence. *************************************************************************** */ __printf_funct_t print_hdr_header(void *parm, int action, char *dfile, struct file_magic *file_magic, struct file_header *file_hdr, __nr_t cpu_nr, struct activity *act[], unsigned int id_seq[]) { int i, p; struct tm rectime, *loc_t; char cur_time[TIMESTAMP_LEN]; /* Actions F_MAIN and F_END ignored */ if (action & F_BEGIN) { printf(_("System activity data file: %s (%#x)\n"), dfile, file_magic->format_magic); display_sa_file_version(stdout, file_magic); if (file_magic->format_magic != FORMAT_MAGIC) { return; } printf(_("Genuine sa datafile: %s (%x)\n"), file_magic->upgraded ? _("no") : _("yes"), file_magic->upgraded); printf(_("Host: ")); print_gal_header(localtime((const time_t *) &(file_hdr->sa_ust_time)), file_hdr->sa_sysname, file_hdr->sa_release, file_hdr->sa_nodename, file_hdr->sa_machine, cpu_nr > 1 ? cpu_nr - 1 : 1, PLAIN_OUTPUT); printf(_("Number of CPU for last samples in file: %u\n"), file_hdr->sa_last_cpu_nr > 1 ? file_hdr->sa_last_cpu_nr - 1 : 1); /* Fill file timestmap structure (rectime) */ get_file_timestamp_struct(flags, &rectime, file_hdr); strftime(cur_time, sizeof(cur_time), "%Y-%m-%d", &rectime); printf(_("File date: %s\n"), cur_time); if ((loc_t = gmtime((const time_t *) &file_hdr->sa_ust_time)) != NULL) { printf(_("File time: ")); strftime(cur_time, sizeof(cur_time), "%T", loc_t); printf("%s UTC\n", cur_time); } printf(_("Size of a long int: %d\n"), file_hdr->sa_sizeof_long); /* Number of activities (number of volatile activities) in file */ printf("sa_act_nr (sa_vol_act_nr): %u (%u)\n", file_hdr->sa_act_nr, file_hdr->sa_vol_act_nr); printf(_("List of activities:\n")); for (i = 0; i < NR_ACT; i++) { if (!id_seq[i]) continue; p = get_activity_position(act, id_seq[i], EXIT_IF_NOT_FOUND); printf("%02d: %s\t(x%d)", act[p]->id, act[p]->name, act[p]->nr); if (act[p]->f_count2 || (act[p]->nr2 > 1)) { printf("\t(x%d)", act[p]->nr2); } if (act[p]->magic == ACTIVITY_MAGIC_UNKNOWN) { printf(_("\t[Unknown activity format]")); } printf("\n"); } } }
/* *************************************************************************** * Read header data sent by sadc. *************************************************************************** */ void read_header_data(void) { struct file_magic file_magic; struct file_activity file_act; int rc, i, p; char version[16]; /* Read magic header */ rc = sa_read(&file_magic, FILE_MAGIC_SIZE); sprintf(version, "%d.%d.%d.%d", file_magic.sysstat_version, file_magic.sysstat_patchlevel, file_magic.sysstat_sublevel, file_magic.sysstat_extraversion); if (!file_magic.sysstat_extraversion) { version[strlen(version) - 2] = '\0'; } if (rc || (file_magic.sysstat_magic != SYSSTAT_MAGIC) || (file_magic.format_magic != FORMAT_MAGIC) || strcmp(version, VERSION)) { /* sar and sadc commands are not consistent */ fprintf(stderr, _("Invalid data format\n")); if (!rc && (file_magic.sysstat_magic == SYSSTAT_MAGIC)) { fprintf(stderr, _("Using a wrong data collector from a different sysstat version\n")); } exit(3); } /* * Read header data. * No need to take into account file_magic.header_size. We are sure that * sadc and sar are from the same version (we have checked FORMAT_MAGIC * but also VERSION above) and thus the size of file_header is FILE_HEADER_SIZE. */ if (sa_read(&file_hdr, FILE_HEADER_SIZE)) { print_read_error(); } /* Read activity list */ for (i = 0; i < file_hdr.sa_act_nr; i++) { if (sa_read(&file_act, FILE_ACTIVITY_SIZE)) { print_read_error(); } p = get_activity_position(act, file_act.id); if ((p < 0) || (act[p]->fsize != file_act.size) || !file_act.nr || !file_act.nr2 || (act[p]->magic != file_act.magic)) { /* Remember that we are reading data from sadc and not from a file... */ fprintf(stderr, _("Inconsistent input data\n")); exit(3); } id_seq[i] = file_act.id; /* We necessarily have "i < NR_ACT" */ act[p]->nr = file_act.nr; act[p]->nr2 = file_act.nr2; } while (i < NR_ACT) { id_seq[i++] = 0; } /* Check that all selected activties are actually sent by sadc */ reverse_check_act(file_hdr.sa_act_nr); }
/* *************************************************************************** * 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, act[get_activity_position(act, A_CPU)]->nr); /* 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, from_file, &file_magic); } 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_act_nr, file_actlst); if (sar_get_record_timestamp_struct(0)) /* * An error was detected. * The timestamp hasn't been updated. */ continue; } } 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. * Activities that should be displayed are saved in id_seq[] array. */ 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, from_file, &file_magic); } 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, from_file, &file_magic); 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_act_nr, file_actlst); } else if (!eosaf && (rtype == R_COMMENT)) { /* This was a COMMENT record: print it */ sar_print_special(curr, tm_start.use, tm_end.use, R_COMMENT, ifd, from_file, &file_magic); } } 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, from_file, &file_magic); } } while (!eosaf); close(ifd); free(file_actlst); }
/* *************************************************************************** * 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; }
/* *************************************************************************** * Read stats for current activity from file and display them. * * IN: * @ifd Input file descriptor. * @fpos Position in file where reading must start. * @curr Index in array for current sample statistics. * @rows Number of rows of screen. * @act_id Activity to display. * @file_actlst List of activities in file. * @file Name of file being read. * @file_magic file_magic structure filled with file magic header data. * * OUT: * @curr Index in array for next sample statistics. * @cnt Number of remaining lines of stats to write. * @eosaf Set to TRUE if EOF (end of file) has been reached. * @reset Set to TRUE if last_uptime variable should be * reinitialized (used in next_slice() function). *************************************************************************** */ void handle_curr_act_stats(int ifd, off_t fpos, int *curr, long *cnt, int *eosaf, int rows, unsigned int act_id, int *reset, struct file_activity *file_actlst, char *file, struct file_magic *file_magic) { int p; unsigned long lines = 0; unsigned char rtype; int davg = 0, next, inc = -2; if (lseek(ifd, fpos, SEEK_SET) < fpos) { perror("lseek"); exit(2); } /* * Restore the first stats collected. * Used to compute the rate displayed on the first line. */ copy_structures(act, id_seq, record_hdr, !*curr, 2); *cnt = count; /* Assess number of lines printed */ if ((p = get_activity_position(act, act_id)) >= 0) { if (act[p]->bitmap) { inc = count_bits(act[p]->bitmap->b_array, BITMAP_SIZE(act[p]->bitmap->b_size)); } else { inc = act[p]->nr; } } if (inc < 0) { /* Should never happen */ PANIC(inc); } do { /* Display count lines of stats */ *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_act_nr, file_actlst); } if ((lines >= rows) || !lines) { lines = 0; dis = 1; } else dis = 0; if (!*eosaf && (rtype != R_RESTART)) { if (rtype == R_COMMENT) { /* Display comment */ next = sar_print_special(*curr, tm_start.use, tm_end.use, R_COMMENT, ifd, file, file_magic); if (next) { /* A line of comment was actually displayed */ lines++; } continue; } /* next is set to 1 when we were close enough to desired interval */ next = write_stats(*curr, USE_SA_FILE, cnt, tm_start.use, tm_end.use, *reset, act_id); if (next && (*cnt > 0)) { (*cnt)--; } if (next) { davg++; *curr ^=1; lines += inc; } *reset = FALSE; } } while (*cnt && !*eosaf && (rtype != R_RESTART)); if (davg) { write_stats_avg(!*curr, USE_SA_FILE, act_id); } *reset = TRUE; }
/* *************************************************************************** * 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; }
/* *************************************************************************** * Read statistics sent by sadc, the data collector. *************************************************************************** */ void read_stats(void) { int curr = 1; unsigned long lines; unsigned int rows = 23; int dis_hdr = 0; /* Don't buffer data if redirected to a pipe... */ setbuf(stdout, NULL); /* Read stats header */ read_header_data(); if (!get_activity_nr(act, AO_SELECTED, COUNT_ACTIVITIES)) { fprintf(stderr, _("Requested activities not available\n")); exit(1); } /* Determine if a stat line header has to be displayed */ dis_hdr = check_line_hdr(); lines = rows = get_win_height(); /* Perform required allocations */ allocate_structures(act); /* Print report header */ print_report_hdr(flags, &rectime, &file_hdr, act[get_activity_position(act, A_CPU)]->nr); /* Read system statistics sent by the data collector */ read_sadc_stat_bunch(0); if (!interval) { /* Display stats since boot time and exit */ write_stats_startup(0); } /* Save the first stats collected. Will be used to compute the average */ copy_structures(act, id_seq, record_hdr, 2, 0); /* Set a handler for SIGINT */ memset(&int_act, 0, sizeof(int_act)); int_act.sa_handler = (void *) int_handler; int_act.sa_flags = SA_RESTART; sigaction(SIGINT, &int_act, NULL); /* Main loop */ do { /* Get stats */ read_sadc_stat_bunch(curr); /* Print results */ if (!dis_hdr) { dis = lines / rows; if (dis) { lines %= rows; } lines++; } write_stats(curr, USE_SADC, &count, NO_TM_START, tm_end.use, NO_RESET, ALL_ACTIVITIES); if (record_hdr[curr].record_type == R_LAST_STATS) { /* File rotation is happening: Re-read header data sent by sadc */ read_header_data(); allocate_structures(act); } if (count > 0) { count--; } if (count) { if (sigint_caught) { /* SIGINT signal caught => Display average stats */ count = 0; } else { curr ^= 1; } } } while (count); /* Print statistics average */ dis = dis_hdr; write_stats_avg(curr, USE_SADC, ALL_ACTIVITIES); }
/* *************************************************************************** * 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); }
/* *************************************************************************** * 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); }
/* *************************************************************************** * 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; }