/* * mpstat: per-processor statistics * (C) 2000-2024 by Sebastien GODARD (sysstat orange.fr) * Copyright (C) 2022 Oracle and/or its affiliates. * *************************************************************************** * This program is free software; you can redistribute it and/or modify it * * under the terms of the GNU General Public License as published by the * * Free Software Foundation; either version 2 of the License, or (at your * * option) any later version. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without the implied warranty of MERCHANTABILITY * * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * * for more details. * * * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA * *************************************************************************** */ #include #include #include #include #include #include #include #include #include #include "version.h" #include "mpstat.h" #include "count.h" #include /* For setlocale() */ #ifdef USE_NLS #include #define _(string) gettext(string) #else #define _(string) (string) #endif #ifdef USE_SCCSID #define SCCSID "@(#)sysstat-" VERSION ": " __FILE__ " compiled " __DATE__ " " __TIME__ char *sccsid(void) { return (SCCSID); } #endif #ifdef TEST extern int __env; #endif unsigned long long uptime_cs[3] = {0, 0, 0}; /* NOTE: Use array of _char_ for bitmaps to avoid endianness problems...*/ unsigned char *cpu_bitmap = NULL; /* Bit 0: Global; Bit 1: 1st proc; etc. */ unsigned char *node_bitmap = NULL; /* Bit 0: Global; Bit 1: 1st NUMA node; etc. */ /* Structures used to save CPU and NUMA nodes CPU stats */ struct stats_cpu *st_cpu[3] = {NULL, NULL, NULL}; struct stats_cpu *st_node[3] = {NULL, NULL, NULL}; /* * Structure used to save total number of interrupts received * among all CPU and for each CPU. */ struct stats_global_irq *st_irq[3] = {NULL, NULL, NULL}; /* * Structures used to save, for each interrupt, the number * received by each CPU. */ struct stats_irqcpu *st_irqcpu[3] = {NULL, NULL, NULL}; struct stats_irqcpu *st_softirqcpu[3] = {NULL, NULL, NULL}; /* * Number of CPU per node, e.g.: * cpu_per_node[0]: total nr of CPU (this is node "all") * cpu_per_node[1]: nr of CPU for node 0 * etc. */ int *cpu_per_node = NULL; /* * Node number the CPU belongs to, e.g.: * cpu2node[0]: node nr for CPU 0 */ int *cpu2node = NULL; /* CPU topology */ struct cpu_topology *st_cpu_topology = NULL; struct tm mp_tstamp[3]; /* Activity flag */ unsigned int actflags = 0; unsigned int flags = 0; /* Interval and count parameters */ long interval = -1, count = 0; /* Number of decimal places */ int dplaces_nr = -1; /* * Nb of processors on the machine. * A value of 2 means there are 2 processors (0 and 1). */ int cpu_nr = 0; /* * Highest NUMA node number found on the machine. * A value of 0 means node 0 (one node). * A value of -1 means no nodes found. * We have: node_nr < cpu_nr (see get_node_placement() function). */ int node_nr = -1; /* Nb of interrupts per processor */ int irqcpu_nr = 0; /* Nb of soft interrupts per processor */ int softirqcpu_nr = 0; struct sigaction alrm_act, int_act; int sigint_caught = 0; /* *************************************************************************** * Print usage and exit * * IN: * @progname Name of sysstat command *************************************************************************** */ void usage(char *progname) { fprintf(stderr, _("Usage: %s [ options ] [ [ ] ]\n"), progname); fprintf(stderr, _("Options are:\n" "[ -A ] [ -H ] [ -n ] [ -T ] [ -u ] [ -V ]\n" "[ -I { SUM | CPU | SCPU | ALL } ] [ -N { | ALL } ]\n" "[ --dec={ 0 | 1 | 2 } ] [ -o JSON ] [ -P { | ALL } ]\n")); exit(1); } /* *************************************************************************** * SIGALRM signal handler. No need to reset the handler here. * * IN: * @sig Signal number. *************************************************************************** */ void alarm_handler(int sig) { alarm(interval); } /* *************************************************************************** * SIGINT signal handler. * * IN: * @sig Signal number. ************************************************************************** */ void int_handler(int sig) { sigint_caught = 1; } /* *************************************************************************** * Allocate stats structures and cpu bitmap. Also do it for NUMA nodes * (although the machine may not be a NUMA one). Assume that the number of * nodes is lower or equal than that of CPU. * * IN: * @nr_cpus Number of CPUs. This is the real number of available CPUs + 1 * because we also have to allocate a structure for CPU 'all'. * @pos Indicate which structures should be initialized. When @pos is * non zero, it means that only the additional, newly allocated * structures should be initialized. *************************************************************************** */ void salloc_mp_struct(int nr_cpus, int pos) { int i; for (i = 0; i < 3; i++) { SREALLOC(st_cpu[i], struct stats_cpu, STATS_CPU_SIZE * nr_cpus); SREALLOC(st_node[i], struct stats_cpu, STATS_CPU_SIZE * nr_cpus); SREALLOC(st_irq[i], struct stats_global_irq, STATS_GLOBAL_IRQ_SIZE * nr_cpus); SREALLOC(st_irqcpu[i], struct stats_irqcpu, STATS_IRQCPU_SIZE * nr_cpus * irqcpu_nr); SREALLOC(st_softirqcpu[i], struct stats_irqcpu, STATS_IRQCPU_SIZE * nr_cpus * softirqcpu_nr); } SREALLOC(cpu_bitmap, unsigned char, (nr_cpus >> 3) + 1); SREALLOC(node_bitmap, unsigned char, (nr_cpus >> 3) + 1); SREALLOC(cpu_per_node, int, sizeof(int) * nr_cpus); SREALLOC(cpu2node, int, sizeof(int) * nr_cpus); SREALLOC(st_cpu_topology, struct cpu_topology, sizeof(struct cpu_topology) * nr_cpus); if (pos) { /* Init already done in SREALLOC macro if @pos == 0 */ for (i = 0; i < 3; i++) { memset(st_cpu[i] + pos, 0, STATS_CPU_SIZE * (nr_cpus - pos)); memset(st_node[i] + pos, 0, STATS_CPU_SIZE * (nr_cpus - pos)); memset(st_irq[i] + pos, 0, STATS_GLOBAL_IRQ_SIZE * (nr_cpus - pos)); memset(st_irqcpu[i] + pos, 0, STATS_IRQCPU_SIZE * (nr_cpus - pos) * irqcpu_nr); memset(st_softirqcpu[i] + pos, 0, STATS_IRQCPU_SIZE * (nr_cpus - pos) * softirqcpu_nr); } } else { memset(cpu_bitmap, 0, (nr_cpus >> 3) + 1); memset(node_bitmap, 0, (nr_cpus >> 3) + 1); } } /* *************************************************************************** * Free structures and bitmap. *************************************************************************** */ void sfree_mp_struct(void) { int i; for (i = 0; i < 3; i++) { free(st_cpu[i]); free(st_node[i]); free(st_irq[i]); free(st_irqcpu[i]); free(st_softirqcpu[i]); } free(cpu_bitmap); free(node_bitmap); free(cpu_per_node); free(cpu2node); } /* *************************************************************************** * Set interrupt values for current sample to those of previous sample. * * IN: * @st_ic Array for per-CPU interrupts statistics. * @c Fist CPU to process. * @last Last CPU to process. * @ic_nr Number of interrupts (hard or soft) per CPU. * @curr Position in array where current statistics will be saved. ************************************************************************** */ void fwd_irq_values(struct stats_irqcpu *st_ic[], unsigned int c, unsigned int last, int ic_nr, int curr) { struct stats_global_irq *st_irq_i, *st_irq_j; struct stats_irqcpu *p, *q; int j; while (c < last) { st_irq_i = st_irq[curr] + c + 1; st_irq_j = st_irq[!curr] + c + 1; st_irq_i->irq_nr = st_irq_j->irq_nr; for (j = 0; j < ic_nr; j++) { p = st_ic[curr] + c * ic_nr + j; q = st_ic[!curr] + c * ic_nr + j; p->interrupt = q->interrupt; } c++; } } /* *************************************************************************** * Get node placement (which node each CPU belongs to, and total number of * CPU that each node has). * * IN: * @nr_cpus Number of CPU on this machine. * * OUT: * @cpu_per_node Number of CPU per node. * @cpu2node The node the CPU belongs to. * * RETURNS: * Highest node number found (e.g., 0 means node 0). * A value of -1 means no nodes have been found. *************************************************************************** */ int get_node_placement(int nr_cpus, int cpu_per_node[], int cpu2node[]) { DIR *dir; struct dirent *drd; char line[MAX_PF_NAME]; int cpu, node, hi_node_nr = -1; /* Init number of CPU per node */ memset(cpu_per_node, 0, sizeof(int) * (nr_cpus + 1)); /* CPU belongs to no node by default */ memset(cpu2node, -1, sizeof(int) * nr_cpus); /* This is node "all" */ cpu_per_node[0] = nr_cpus; for (cpu = 0; cpu < nr_cpus; cpu++) { snprintf(line, sizeof(line), "%s/cpu%d", SYSFS_DEVCPU, cpu); line[sizeof(line) - 1] = '\0'; /* Open relevant /sys directory */ if ((dir = opendir(line)) == NULL) return -1; /* Get current file entry */ while ((drd = readdir(dir)) != NULL) { if (!strncmp(drd->d_name, "node", 4) && isdigit(drd->d_name[4])) { node = atoi(drd->d_name + 4); if ((node >= nr_cpus) || (node < 0)) { /* Assume we cannot have more nodes than CPU */ closedir(dir); return -1; } cpu_per_node[node + 1]++; cpu2node[cpu] = node; if (node > hi_node_nr) { hi_node_nr = node; } /* Node placement found for current CPU: Go to next CPU directory */ break; } } /* Close directory */ closedir(dir); } return hi_node_nr; } /* *************************************************************************** * Read system logical topology: Socket number for each logical core is read * from the /sys/devices/system/cpu/cpu{N}/topology/physical_package_id file, * and the logical core id number is the first number read from the * /sys/devices/system/cpu/cpu{N}/topology/thread_siblings_list file. * Don't use /sys/devices/system/cpu/cpu{N}/topology/core_id as this is the * physical core id (seems to be different from the number displayed by lscpu). * * IN: * @nr_cpus Number of CPU on this machine. * @cpu_topo Structures where socket and core id numbers will be saved. * * OUT: * @cpu_topo Structures where socket and core id numbers have been saved. *************************************************************************** */ void read_topology(int nr_cpus, struct cpu_topology *cpu_topo) { struct cpu_topology *cpu_topo_i; FILE *fp; char filename[MAX_PF_NAME]; int cpu, rc; /* Init system topology */ memset(st_cpu_topology, 0, sizeof(struct cpu_topology) * nr_cpus); for (cpu = 0; cpu < nr_cpus; cpu++) { cpu_topo_i = cpu_topo + cpu; /* Read current CPU's socket number */ snprintf(filename, sizeof(filename), "%s/cpu%d/%s", SYSFS_DEVCPU, cpu, PHYS_PACK_ID); filename[sizeof(filename) - 1] = '\0'; if ((fp = fopen(filename, "r")) != NULL) { rc = fscanf(fp, "%d", &cpu_topo_i->phys_package_id); fclose(fp); if (rc < 1) { cpu_topo_i->phys_package_id = -1; } } /* Read current CPU's logical core id number */ snprintf(filename, sizeof(filename), "%s/cpu%d/%s", SYSFS_DEVCPU, cpu, THREAD_SBL_LST); filename[sizeof(filename) - 1] = '\0'; if ((fp = fopen(filename, "r")) != NULL) { rc = fscanf(fp, "%d", &cpu_topo_i->logical_core_id); fclose(fp); if (rc < 1) { cpu_topo_i->logical_core_id = -1; } } } } /* *************************************************************************** * Compute node statistics: Split CPU statistics among nodes. * * IN: * @src Structure containing CPU stats to add. * * OUT: * @dest Structure containing global CPU stats. *************************************************************************** */ void add_cpu_stats(struct stats_cpu *dest, struct stats_cpu *src) { dest->cpu_user += src->cpu_user; dest->cpu_nice += src->cpu_nice; dest->cpu_sys += src->cpu_sys; dest->cpu_idle += src->cpu_idle; dest->cpu_iowait += src->cpu_iowait; dest->cpu_hardirq += src->cpu_hardirq; dest->cpu_softirq += src->cpu_softirq; dest->cpu_steal += src->cpu_steal; dest->cpu_guest += src->cpu_guest; dest->cpu_guest_nice += src->cpu_guest_nice; } /* *************************************************************************** * Compute node statistics: Split CPU statistics among nodes. * * IN: * @prev Index in array where stats used as reference are. * @curr Index in array for current sample statistics. * * OUT: * @st_node Array where CPU stats for each node have been saved. *************************************************************************** */ void set_node_cpu_stats(int prev, int curr) { int cpu; unsigned long long tot_jiffies_p; struct stats_cpu *scp, *scc, *snp, *snc; struct stats_cpu *scc_all = st_cpu[curr]; struct stats_cpu *scp_all = st_cpu[prev]; struct stats_cpu *snc_all = st_node[curr]; struct stats_cpu *snp_all = st_node[prev]; /* Reset structures */ memset(st_node[prev], 0, STATS_CPU_SIZE * (cpu_nr + 1)); memset(st_node[curr], 0, STATS_CPU_SIZE * (cpu_nr + 1)); /* Node 'all' is the same as CPU 'all' */ *snp_all = *scp_all; *snc_all = *scc_all; /* Individual nodes */ for (cpu = 0; cpu < cpu_nr; cpu++) { scc = st_cpu[curr] + cpu + 1; scp = st_cpu[prev] + cpu + 1; snp = st_node[prev] + cpu2node[cpu] + 1; snc = st_node[curr] + cpu2node[cpu] + 1; tot_jiffies_p = scp->cpu_user + scp->cpu_nice + scp->cpu_sys + scp->cpu_idle + scp->cpu_iowait + scp->cpu_hardirq + scp->cpu_steal + scp->cpu_softirq; if ((tot_jiffies_p == 0) && (interval != 0)) /* * CPU has just come back online with no ref from * previous iteration: Skip it. */ continue; add_cpu_stats(snp, scp); add_cpu_stats(snc, scc); } } /* *************************************************************************** * Compute global CPU statistics as the sum of individual CPU ones, and * calculate interval for global CPU. * Also identify offline CPU. * * IN: * @prev Index in array where stats used as reference are. * @curr Index in array for current sample statistics. * @offline_cpu_bitmap * CPU bitmap for offline CPU. * * OUT: * @offline_cpu_bitmap * CPU bitmap with offline CPU. * * RETURNS: * Interval for global CPU. *************************************************************************** */ unsigned long long get_global_cpu_mpstats(int prev, int curr, unsigned char offline_cpu_bitmap[]) { int i; unsigned long long tot_jiffies_c, tot_jiffies_p; unsigned long long deltot_jiffies = 0; struct stats_cpu *scc, *scp; struct stats_cpu *scc_all = st_cpu[curr]; struct stats_cpu *scp_all = st_cpu[prev]; /* * For UP machines we keep the values read from global CPU line in /proc/stat. * Also look for offline CPU: They won't be displayed, and some of their values may * have to be modified. */ if (cpu_nr > 1) { memset(scc_all, 0, sizeof(struct stats_cpu)); memset(scp_all, 0, sizeof(struct stats_cpu)); } else { /* This is a UP machine */ return get_per_cpu_interval(st_cpu[curr], st_cpu[prev]); } for (i = 1; i <= cpu_nr; i++) { scc = st_cpu[curr] + i; scp = st_cpu[prev] + i; /* * Compute the total number of jiffies spent by current processor. * NB: Don't add cpu_guest/cpu_guest_nice because cpu_user/cpu_nice * already include them. */ tot_jiffies_c = scc->cpu_user + scc->cpu_nice + scc->cpu_sys + scc->cpu_idle + scc->cpu_iowait + scc->cpu_hardirq + scc->cpu_steal + scc->cpu_softirq; tot_jiffies_p = scp->cpu_user + scp->cpu_nice + scp->cpu_sys + scp->cpu_idle + scp->cpu_iowait + scp->cpu_hardirq + scp->cpu_steal + scp->cpu_softirq; /* * If the CPU is offline then it is omitted from /proc/stat: * All the fields couldn't have been read and the sum of them is zero. */ if (tot_jiffies_c == 0) { /* * CPU is currently offline. * Set current struct fields (which have been set to zero) * to values from previous iteration. Hence their values won't * jump from zero when the CPU comes back online. * Note that this workaround no longer fully applies with recent kernels, * as I have noticed that when a CPU comes back online, some fields * restart from their previous value (e.g. user, nice, system) * whereas others restart from zero (idle, iowait)! To deal with this, * the get_per_cpu_interval() function will set these previous values * to zero if necessary. */ *scc = *scp; /* * Mark CPU as offline to not display it * (and thus it will not be confused with a tickless CPU). */ offline_cpu_bitmap[i >> 3] |= 1 << (i & 0x07); } if ((tot_jiffies_p == 0) && (interval != 0)) { /* * CPU has just come back online. * Unfortunately, no reference values are available * from a previous iteration, probably because it was * already offline when the first sample has been taken. * So don't display that CPU to prevent "jump-from-zero" * output syndrome, and don't take it into account for CPU "all". * NB: Test for interval != 0 to make sure we don't want stats * since boot time. */ offline_cpu_bitmap[i >> 3] |= 1 << (i & 0x07); continue; } /* * Get interval for current CPU and add it to global CPU. * Note: Previous idle and iowait values (saved in scp) may be modified here. */ deltot_jiffies += get_per_cpu_interval(scc, scp); add_cpu_stats(scc_all, scc); add_cpu_stats(scp_all, scp); } return deltot_jiffies; } /* *************************************************************************** * Display CPU statistics in plain format. * * IN: * @dis TRUE if a header line must be printed. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_plain_cpu_stats(int dis, unsigned long long deltot_jiffies, int prev, int curr, char *prev_string, char *curr_string, const unsigned char offline_cpu_bitmap[]) { int i; struct stats_cpu *scc, *scp; struct cpu_topology *cpu_topo_i; if (dis) { printf("\n%-11s CPU", prev_string); if (DISPLAY_TOPOLOGY(flags)) { printf(" CORE SOCK NODE"); } printf(" %%usr %%nice %%sys %%iowait %%irq " "%%soft %%steal %%guest %%gnice %%idle\n"); } /* * Now display CPU statistics (including CPU "all"), * except for offline CPU or CPU that the user doesn't want to see. */ for (i = 0; i <= cpu_nr; i++) { /* Check if we want stats about this proc */ if (!(*(cpu_bitmap + (i >> 3)) & (1 << (i & 0x07))) || offline_cpu_bitmap[i >> 3] & (1 << (i & 0x07))) continue; scc = st_cpu[curr] + i; scp = st_cpu[prev] + i; printf("%-11s", curr_string); if (i == 0) { /* This is CPU "all" */ cprintf_in(IS_STR, " %s", " all", 0); if (DISPLAY_TOPOLOGY(flags)) { printf(" "); } } else { cprintf_in(IS_INT, " %4d", "", i - 1); if (DISPLAY_TOPOLOGY(flags)) { cpu_topo_i = st_cpu_topology + i - 1; cprintf_in(IS_INT, " %4d", "", cpu_topo_i->logical_core_id); cprintf_in(IS_INT, " %4d", "", cpu_topo_i->phys_package_id); cprintf_in(IS_INT, " %4d", "", cpu2node[i - 1]); } /* Recalculate itv for current proc */ deltot_jiffies = get_per_cpu_interval(scc, scp); if (!deltot_jiffies) { /* * If the CPU is tickless then there is no change in CPU values * but the sum of values is not zero. */ cprintf_xpc(NO_UNIT, FALSE, 10, 7, 2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0); printf("\n"); continue; } } cprintf_xpc(NO_UNIT, XHIGH, 9, 7, 2, (scc->cpu_user - scc->cpu_guest) < (scp->cpu_user - scp->cpu_guest) ? 0.0 : ll_sp_value(scp->cpu_user - scp->cpu_guest, scc->cpu_user - scc->cpu_guest, deltot_jiffies), (scc->cpu_nice - scc->cpu_guest_nice) < (scp->cpu_nice - scp->cpu_guest_nice) ? 0.0 : ll_sp_value(scp->cpu_nice - scp->cpu_guest_nice, scc->cpu_nice - scc->cpu_guest_nice, deltot_jiffies), ll_sp_value(scp->cpu_sys, scc->cpu_sys, deltot_jiffies), ll_sp_value(scp->cpu_iowait, scc->cpu_iowait, deltot_jiffies), ll_sp_value(scp->cpu_hardirq, scc->cpu_hardirq, deltot_jiffies), ll_sp_value(scp->cpu_softirq, scc->cpu_softirq, deltot_jiffies), ll_sp_value(scp->cpu_steal, scc->cpu_steal, deltot_jiffies), ll_sp_value(scp->cpu_guest, scc->cpu_guest, deltot_jiffies), ll_sp_value(scp->cpu_guest_nice, scc->cpu_guest_nice, deltot_jiffies)); cprintf_xpc(NO_UNIT, XLOW, 1, 7, 2, (scc->cpu_idle < scp->cpu_idle) ? 0.0 : ll_sp_value(scp->cpu_idle, scc->cpu_idle, deltot_jiffies)); printf("\n"); } } /* *************************************************************************** * Display CPU statistics in JSON format. * * IN: * @tab Number of tabs to print. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_json_cpu_stats(int tab, unsigned long long deltot_jiffies, int prev, int curr, const unsigned char offline_cpu_bitmap[]) { int i, next = FALSE; char cpu_name[16], topology[1024] = ""; struct stats_cpu *scc, *scp; struct cpu_topology *cpu_topo_i; xprintf(tab++, "\"cpu-load\": ["); /* * Now display CPU statistics (including CPU "all"), * except for offline CPU or CPU that the user doesn't want to see. */ for (i = 0; i <= cpu_nr; i++) { /* Check if we want stats about this proc */ if (!(*(cpu_bitmap + (i >> 3)) & (1 << (i & 0x07))) || offline_cpu_bitmap[i >> 3] & (1 << (i & 0x07))) continue; scc = st_cpu[curr] + i; scp = st_cpu[prev] + i; if (next) { printf(",\n"); } next = TRUE; if (i == 0) { /* This is CPU "all" */ strcpy(cpu_name, K_LOWERALL); if (DISPLAY_TOPOLOGY(flags)) { snprintf(topology, sizeof(topology), ", \"core\": \"\", \"socket\": \"\", \"node\": \"\""); } } else { snprintf(cpu_name, sizeof(cpu_name), "%d", i - 1); cpu_name[sizeof(cpu_name) - 1] = '\0'; if (DISPLAY_TOPOLOGY(flags)) { cpu_topo_i = st_cpu_topology + i - 1; snprintf(topology, sizeof(topology), ", \"core\": \"%d\", \"socket\": \"%d\", \"node\": \"%d\"", cpu_topo_i->logical_core_id, cpu_topo_i->phys_package_id, cpu2node[i - 1]); } /* Recalculate itv for current proc */ deltot_jiffies = get_per_cpu_interval(scc, scp); if (!deltot_jiffies) { /* * If the CPU is tickless then there is no change in CPU values * but the sum of values is not zero. */ xprintf0(tab, "{\"cpu\": \"%d\"%s, \"usr\": 0.00, \"nice\": 0.00, " "\"sys\": 0.00, \"iowait\": 0.00, \"irq\": 0.00, " "\"soft\": 0.00, \"steal\": 0.00, \"guest\": 0.00, " "\"gnice\": 0.00, \"idle\": 100.00}", i - 1, topology); printf("\n"); continue; } } xprintf0(tab, "{\"cpu\": \"%s\"%s, \"usr\": %.2f, \"nice\": %.2f, \"sys\": %.2f, " "\"iowait\": %.2f, \"irq\": %.2f, \"soft\": %.2f, \"steal\": %.2f, " "\"guest\": %.2f, \"gnice\": %.2f, \"idle\": %.2f}", cpu_name, topology, (scc->cpu_user - scc->cpu_guest) < (scp->cpu_user - scp->cpu_guest) ? 0.0 : ll_sp_value(scp->cpu_user - scp->cpu_guest, scc->cpu_user - scc->cpu_guest, deltot_jiffies), (scc->cpu_nice - scc->cpu_guest_nice) < (scp->cpu_nice - scp->cpu_guest_nice) ? 0.0 : ll_sp_value(scp->cpu_nice - scp->cpu_guest_nice, scc->cpu_nice - scc->cpu_guest_nice, deltot_jiffies), ll_sp_value(scp->cpu_sys, scc->cpu_sys, deltot_jiffies), ll_sp_value(scp->cpu_iowait, scc->cpu_iowait, deltot_jiffies), ll_sp_value(scp->cpu_hardirq, scc->cpu_hardirq, deltot_jiffies), ll_sp_value(scp->cpu_softirq, scc->cpu_softirq, deltot_jiffies), ll_sp_value(scp->cpu_steal, scc->cpu_steal, deltot_jiffies), ll_sp_value(scp->cpu_guest, scc->cpu_guest, deltot_jiffies), ll_sp_value(scp->cpu_guest_nice, scc->cpu_guest_nice, deltot_jiffies), (scc->cpu_idle < scp->cpu_idle) ? 0.0 : ll_sp_value(scp->cpu_idle, scc->cpu_idle, deltot_jiffies)); } printf("\n"); xprintf0(--tab, "]"); } /* *************************************************************************** * Display CPU statistics in plain or JSON format. * * IN: * @dis TRUE if a header line must be printed. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @tab Number of tabs to print (JSON format only). * @next TRUE is a previous activity has been displayed (JSON format * only). * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_cpu_stats(int dis, unsigned long long deltot_jiffies, int prev, int curr, char *prev_string, char *curr_string, int tab, int *next, unsigned char offline_cpu_bitmap[]) { if (!deltot_jiffies) { /* CPU "all" cannot be tickless */ deltot_jiffies = 1; } if (DISPLAY_JSON_OUTPUT(flags)) { if (*next) { printf(",\n"); } *next = TRUE; write_json_cpu_stats(tab, deltot_jiffies, prev, curr, offline_cpu_bitmap); } else { write_plain_cpu_stats(dis, deltot_jiffies, prev, curr, prev_string, curr_string, offline_cpu_bitmap); } } /* *************************************************************************** * Display CPU statistics for NUMA nodes in plain format. * * IN: * @dis TRUE if a header line must be printed. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. *************************************************************************** */ void write_plain_node_stats(int dis, unsigned long long deltot_jiffies, int prev, int curr, char *prev_string, char *curr_string) { struct stats_cpu *snc, *snp, *scc, *scp; int cpu, node; if (dis) { printf("\n%-11s NODE %%usr %%nice %%sys %%iowait %%irq " "%%soft %%steal %%guest %%gnice %%idle\n", prev_string); } for (node = 0; node <= node_nr + 1; node++) { snc = st_node[curr] + node; snp = st_node[prev] + node; /* Check if we want stats about this node */ if (!(*(node_bitmap + (node >> 3)) & (1 << (node & 0x07)))) continue; if (!cpu_per_node[node]) /* No CPU in this node */ continue; printf("%-11s", curr_string); if (node == 0) { /* This is node "all", i.e. CPU "all" */ cprintf_in(IS_STR, " %s", " all", 0); } else { cprintf_in(IS_INT, " %4d", "", node - 1); /* Recalculate interval for current node */ deltot_jiffies = 0; for (cpu = 1; cpu <= cpu_nr; cpu++) { scc = st_cpu[curr] + cpu; scp = st_cpu[prev] + cpu; if ((scp->cpu_user + scp->cpu_nice + scp->cpu_sys + scp->cpu_idle + scp->cpu_iowait + scp->cpu_hardirq + scp->cpu_steal + scp->cpu_softirq == 0) && (interval != 0)) continue; if (cpu2node[cpu - 1] == node - 1) { deltot_jiffies += get_per_cpu_interval(scc, scp); } } if (!deltot_jiffies) { /* All CPU in node are tickless and/or offline */ cprintf_xpc(NO_UNIT, FALSE, 10, 7, 2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0); printf("\n"); continue; } } cprintf_xpc(NO_UNIT, XHIGH, 9, 7, 2, (snc->cpu_user - snc->cpu_guest) < (snp->cpu_user - snp->cpu_guest) ? 0.0 : ll_sp_value(snp->cpu_user - snp->cpu_guest, snc->cpu_user - snc->cpu_guest, deltot_jiffies), (snc->cpu_nice - snc->cpu_guest_nice) < (snp->cpu_nice - snp->cpu_guest_nice) ? 0.0 : ll_sp_value(snp->cpu_nice - snp->cpu_guest_nice, snc->cpu_nice - snc->cpu_guest_nice, deltot_jiffies), ll_sp_value(snp->cpu_sys, snc->cpu_sys, deltot_jiffies), ll_sp_value(snp->cpu_iowait, snc->cpu_iowait, deltot_jiffies), ll_sp_value(snp->cpu_hardirq, snc->cpu_hardirq, deltot_jiffies), ll_sp_value(snp->cpu_softirq, snc->cpu_softirq, deltot_jiffies), ll_sp_value(snp->cpu_steal, snc->cpu_steal, deltot_jiffies), ll_sp_value(snp->cpu_guest, snc->cpu_guest, deltot_jiffies), ll_sp_value(snp->cpu_guest_nice, snc->cpu_guest_nice, deltot_jiffies)); cprintf_xpc(NO_UNIT, XLOW, 1, 7, 2, (snc->cpu_idle < snp->cpu_idle) ? 0.0 : ll_sp_value(snp->cpu_idle, snc->cpu_idle, deltot_jiffies)); printf("\n"); } } /* *************************************************************************** * Display CPU statistics for NUMA nodes in JSON format. * * IN: * @tab Number of tabs to print. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. *************************************************************************** */ void write_json_node_stats(int tab, unsigned long long deltot_jiffies, int prev, int curr) { struct stats_cpu *snc, *snp, *scc, *scp; int cpu, node, next = FALSE; char node_name[16]; xprintf(tab++, "\"node-load\": ["); for (node = 0; node <= node_nr + 1; node++) { snc = st_node[curr] + node; snp = st_node[prev] + node; /* Check if we want stats about this node */ if (!(*(node_bitmap + (node >> 3)) & (1 << (node & 0x07)))) continue; if (!cpu_per_node[node]) /* No CPU in this node */ continue; if (next) { printf(",\n"); } next = TRUE; if (node == 0) { /* This is node "all", i.e. CPU "all" */ strcpy(node_name, K_LOWERALL); } else { snprintf(node_name, sizeof(node_name), "%d", node - 1); node_name[sizeof(node_name) -1] = '\0'; /* Recalculate interval for current node */ deltot_jiffies = 0; for (cpu = 1; cpu <= cpu_nr; cpu++) { scc = st_cpu[curr] + cpu; scp = st_cpu[prev] + cpu; if ((scp->cpu_user + scp->cpu_nice + scp->cpu_sys + scp->cpu_idle + scp->cpu_iowait + scp->cpu_hardirq + scp->cpu_steal + scp->cpu_softirq == 0) && (interval != 0)) continue; if (cpu2node[cpu - 1] == node - 1) { deltot_jiffies += get_per_cpu_interval(scc, scp); } } if (!deltot_jiffies) { /* All CPU in node are tickless and/or offline */ xprintf0(tab, "{\"node\": \"%d\", \"usr\": 0.00, \"nice\": 0.00, \"sys\": 0.00, " "\"iowait\": 0.00, \"irq\": 0.00, \"soft\": 0.00, \"steal\": 0.00, " "\"guest\": 0.00, \"gnice\": 0.00, \"idle\": 100.00}", node - 1); continue; } } xprintf0(tab, "{\"node\": \"%s\", \"usr\": %.2f, \"nice\": %.2f, \"sys\": %.2f, " "\"iowait\": %.2f, \"irq\": %.2f, \"soft\": %.2f, \"steal\": %.2f, " "\"guest\": %.2f, \"gnice\": %.2f, \"idle\": %.2f}", node_name, (snc->cpu_user - snc->cpu_guest) < (snp->cpu_user - snp->cpu_guest) ? 0.0 : ll_sp_value(snp->cpu_user - snp->cpu_guest, snc->cpu_user - snc->cpu_guest, deltot_jiffies), (snc->cpu_nice - snc->cpu_guest_nice) < (snp->cpu_nice - snp->cpu_guest_nice) ? 0.0 : ll_sp_value(snp->cpu_nice - snp->cpu_guest_nice, snc->cpu_nice - snc->cpu_guest_nice, deltot_jiffies), ll_sp_value(snp->cpu_sys, snc->cpu_sys, deltot_jiffies), ll_sp_value(snp->cpu_iowait, snc->cpu_iowait, deltot_jiffies), ll_sp_value(snp->cpu_hardirq, snc->cpu_hardirq, deltot_jiffies), ll_sp_value(snp->cpu_softirq, snc->cpu_softirq, deltot_jiffies), ll_sp_value(snp->cpu_steal, snc->cpu_steal, deltot_jiffies), ll_sp_value(snp->cpu_guest, snc->cpu_guest, deltot_jiffies), ll_sp_value(snp->cpu_guest_nice, snc->cpu_guest_nice, deltot_jiffies), (snc->cpu_idle < snp->cpu_idle) ? 0.0 : ll_sp_value(snp->cpu_idle, snc->cpu_idle, deltot_jiffies)); } printf("\n"); xprintf0(--tab, "]"); } /* *************************************************************************** * Display nodes statistics in plain or JSON format. * * IN: * @dis TRUE if a header line must be printed. * @deltot_jiffies * Number of jiffies spent on the interval by all processors. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @tab Number of tabs to print (JSON format only). * @next TRUE is a previous activity has been displayed (JSON format * only). *************************************************************************** */ void write_node_stats(int dis, unsigned long long deltot_jiffies, int prev, int curr, char *prev_string, char *curr_string, int tab, int *next) { if (!deltot_jiffies) { /* CPU "all" cannot be tickless */ deltot_jiffies = 1; } if (DISPLAY_JSON_OUTPUT(flags)) { if (*next) { printf(",\n"); } *next = TRUE; write_json_node_stats(tab, deltot_jiffies, prev, curr); } else { write_plain_node_stats(dis, deltot_jiffies, prev, curr, prev_string, curr_string); } } /* *************************************************************************** * Display total number of interrupts per CPU in plain format. * * IN: * @dis TRUE if a header line must be printed. * @itv Interval value. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_plain_isumcpu_stats(int dis, unsigned long long itv, int prev, int curr, char *prev_string, char *curr_string, const unsigned char offline_cpu_bitmap[]) { struct stats_cpu *scc, *scp; struct stats_global_irq *sic, *sip; unsigned long long pc_itv; int cpu; if (dis) { printf("\n%-11s CPU intr/s\n", prev_string); } if (*cpu_bitmap & 1) { printf("%-11s", curr_string); cprintf_in(IS_STR, " %s", " all", 0); /* Print total number of interrupts among all cpu */ cprintf_f(NO_UNIT, FALSE, 1, 9, 2, S_VALUE(st_irq[prev]->irq_nr, st_irq[curr]->irq_nr, itv)); printf("\n"); } for (cpu = 1; cpu <= cpu_nr; cpu++) { sic = st_irq[curr] + cpu; sip = st_irq[prev] + cpu; scc = st_cpu[curr] + cpu; scp = st_cpu[prev] + cpu; /* Check if we want stats about this CPU */ if (!(*(cpu_bitmap + (cpu >> 3)) & (1 << (cpu & 0x07))) || offline_cpu_bitmap[cpu >> 3] & (1 << (cpu & 0x07))) continue; printf("%-11s", curr_string); cprintf_in(IS_INT, " %4d", "", cpu - 1); /* Recalculate itv for current proc */ pc_itv = get_per_cpu_interval(scc, scp); if (!pc_itv) { /* This is a tickless CPU: Value displayed is 0.00 */ cprintf_f(NO_UNIT, FALSE, 1, 9, 2, 0.0); printf("\n"); } else { /* Display total number of interrupts for current CPU */ cprintf_f(NO_UNIT, FALSE, 1, 9, 2, S_VALUE(sip->irq_nr, sic->irq_nr, itv)); printf("\n"); } } } /* *************************************************************************** * Display total number of interrupts per CPU in JSON format. * * IN: * @tab Number of tabs to print. * @itv Interval value. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_json_isumcpu_stats(int tab, unsigned long long itv, int prev, int curr, const unsigned char offline_cpu_bitmap[]) { struct stats_cpu *scc, *scp; struct stats_global_irq *sic, *sip; unsigned long long pc_itv; int cpu, next = FALSE; xprintf(tab++, "\"sum-interrupts\": ["); if (*cpu_bitmap & 1) { next = TRUE; /* Print total number of interrupts among all cpu */ xprintf0(tab, "{\"cpu\": \"all\", \"intr\": %.2f}", S_VALUE(st_irq[prev]->irq_nr, st_irq[curr]->irq_nr, itv)); } for (cpu = 1; cpu <= cpu_nr; cpu++) { sic = st_irq[curr] + cpu; sip = st_irq[prev] + cpu; scc = st_cpu[curr] + cpu; scp = st_cpu[prev] + cpu; /* Check if we want stats about this CPU */ if (!(*(cpu_bitmap + (cpu >> 3)) & (1 << (cpu & 0x07))) || offline_cpu_bitmap[cpu >> 3] & (1 << (cpu & 0x07))) continue; if (next) { printf(",\n"); } next = TRUE; /* Recalculate itv for current proc */ pc_itv = get_per_cpu_interval(scc, scp); if (!pc_itv) { /* This is a tickless CPU: Value displayed is 0.00 */ xprintf0(tab, "{\"cpu\": \"%d\", \"intr\": 0.00}", cpu - 1); } else { /* Display total number of interrupts for current CPU */ xprintf0(tab, "{\"cpu\": \"%d\", \"intr\": %.2f}", cpu - 1, S_VALUE(sip->irq_nr, sic->irq_nr, itv)); } } printf("\n"); xprintf0(--tab, "]"); } /* *************************************************************************** * Display total number of interrupts per CPU in plain or JSON format. * * IN: * @dis TRUE if a header line must be printed. * @itv Interval value. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @tab Number of tabs to print (JSON format only). * @next TRUE is a previous activity has been displayed (JSON format * only). * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_isumcpu_stats(int dis, unsigned long long itv, int prev, int curr, char *prev_string, char *curr_string, int tab, int *next, unsigned char offline_cpu_bitmap[]) { if (DISPLAY_JSON_OUTPUT(flags)) { if (*next) { printf(",\n"); } *next = TRUE; write_json_isumcpu_stats(tab, itv, prev, curr, offline_cpu_bitmap); } else { write_plain_isumcpu_stats(dis, itv, prev, curr, prev_string, curr_string, offline_cpu_bitmap); } } /* *************************************************************************** * Display interrupts statistics for each CPU in plain format. * * IN: * @st_ic Array for per-CPU statistics. * @ic_nr Number of interrupts (hard or soft) per CPU. * @dis TRUE if a header line must be printed. * @itv Interval value. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_plain_irqcpu_stats(struct stats_irqcpu *st_ic[], int ic_nr, int dis, unsigned long long itv, int prev, int curr, char *prev_string, char *curr_string, const unsigned char offline_cpu_bitmap[]) { int j = ic_nr, offset, cpu, colwidth[NR_IRQS]; struct stats_irqcpu *p, *q, *p0, *q0; /* * Check if number of interrupts has changed. * If this is the case, the header line will be printed again. * NB: A zero interval value indicates that we are * displaying statistics since system startup. */ if (!dis && interval) { for (j = 0; j < ic_nr; j++) { p0 = st_ic[curr] + j; q0 = st_ic[prev] + j; if (strcmp(p0->irq_name, q0->irq_name)) /* * These are two different interrupts: The header must be displayed * (maybe an interrupt has disappeared, or a new one has just been registered). * Note that we compare even empty strings for the case where * a disappearing interrupt would be the last one in the list. */ break; } } if (dis || (j < ic_nr)) { /* Print header */ printf("\n%-11s CPU", prev_string); for (j = 0; j < ic_nr; j++) { p0 = st_ic[curr] + j; if (p0->irq_name[0] == '\0') /* End of the list of interrupts */ break; printf(" %8s/s", p0->irq_name); } printf("\n"); } /* Calculate column widths */ for (j = 0; j < ic_nr; j++) { p0 = st_ic[curr] + j; /* * Width is IRQ name + 2 for the trailing "/s". * Width is calculated even for "undefined" interrupts (with * an empty irq_name string) to quiet code analysis tools. */ colwidth[j] = strlen(p0->irq_name) + 2; /* * Normal space for printing a number is 11 chars * (space + 10 digits including the period). */ if (colwidth[j] < 10) { colwidth[j] = 10; } } for (cpu = 1; cpu <= cpu_nr; cpu++) { /* * Check if we want stats about this CPU. * CPU must have been explicitly selected using option -P, * else we display every CPU (unless it's offline). */ if ((!(*(cpu_bitmap + (cpu >> 3)) & (1 << (cpu & 0x07))) && USE_OPTION_P(flags)) || offline_cpu_bitmap[cpu >> 3] & (1 << (cpu & 0x07))) continue; printf("%-11s", curr_string); cprintf_in(IS_INT, " %3d", "", cpu - 1); for (j = 0; j < ic_nr; j++) { p0 = st_ic[curr] + j; /* irq_name set only for CPU#0 */ /* * An empty string for irq_name means it is a remaining interrupt * which is no longer used, for example because the * number of interrupts has decreased in /proc/interrupts. */ if (p0->irq_name[0] == '\0') /* End of the list of interrupts */ break; q0 = st_ic[prev] + j; offset = j; /* * If we want stats for the time since system startup, * we have p0->irq_name != q0->irq_name, since q0 structure * is completely set to zero. */ if (strcmp(p0->irq_name, q0->irq_name) && interval) { /* Check if interrupt exists elsewhere in list */ for (offset = 0; offset < ic_nr; offset++) { q0 = st_ic[prev] + offset; if (!strcmp(p0->irq_name, q0->irq_name)) /* Interrupt found at another position */ break; } } p = st_ic[curr] + (cpu - 1) * ic_nr + j; if (!strcmp(p0->irq_name, q0->irq_name) || !interval) { q = st_ic[prev] + (cpu - 1) * ic_nr + offset; cprintf_f(NO_UNIT, FALSE, 1, colwidth[j], 2, S_VALUE(q->interrupt, p->interrupt, itv)); } else { /* * Instead of printing "N/A", assume that previous value * for this new interrupt was zero. */ cprintf_f(NO_UNIT, FALSE, 1, colwidth[j], 2, S_VALUE(0, p->interrupt, itv)); } } printf("\n"); } } /* *************************************************************************** * Display interrupts statistics for each CPU in JSON format. * * IN: * @tab Number of tabs to print. * @st_ic Array for per-CPU statistics. * @ic_nr Number of interrupts (hard or soft) per CPU. * @itv Interval value. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @type Activity (M_D_IRQ_CPU or M_D_SOFTIRQS). * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_json_irqcpu_stats(int tab, struct stats_irqcpu *st_ic[], int ic_nr, unsigned long long itv, int prev, int curr, int type, const unsigned char offline_cpu_bitmap[]) { int j = ic_nr, offset, cpu; struct stats_irqcpu *p, *q, *p0, *q0; int nextcpu = FALSE, nextirq; if (type == M_D_IRQ_CPU) { xprintf(tab++, "\"individual-interrupts\": ["); } else { xprintf(tab++, "\"soft-interrupts\": ["); } for (cpu = 1; cpu <= cpu_nr; cpu++) { /* * Check if we want stats about this CPU. * CPU must have been explicitly selected using option -P, * else we display every CPU (unless it's offline). */ if ((!(*(cpu_bitmap + (cpu >> 3)) & (1 << (cpu & 0x07))) && USE_OPTION_P(flags)) || offline_cpu_bitmap[cpu >> 3] & (1 << (cpu & 0x07))) continue; if (nextcpu) { printf(",\n"); } nextcpu = TRUE; nextirq = FALSE; xprintf(tab++, "{\"cpu\": \"%d\", \"intr\": [", cpu - 1); for (j = 0; j < ic_nr; j++) { p0 = st_ic[curr] + j; /* irq_name set only for CPU#0 */ /* * An empty string for irq_name means it is a remaining interrupt * which is no longer used, for example because the * number of interrupts has decreased in /proc/interrupts. */ if (p0->irq_name[0] == '\0') /* End of the list of interrupts */ break; q0 = st_ic[prev] + j; offset = j; if (nextirq) { printf(",\n"); } nextirq = TRUE; /* * If we want stats for the time since system startup, * we have p0->irq_name != q0->irq_name, since q0 structure * is completely set to zero. */ if (strcmp(p0->irq_name, q0->irq_name) && interval) { /* Check if interrupt exists elsewhere in list */ for (offset = 0; offset < ic_nr; offset++) { q0 = st_ic[prev] + offset; if (!strcmp(p0->irq_name, q0->irq_name)) /* Interrupt found at another position */ break; } } p = st_ic[curr] + (cpu - 1) * ic_nr + j; if (!strcmp(p0->irq_name, q0->irq_name) || !interval) { q = st_ic[prev] + (cpu - 1) * ic_nr + offset; xprintf0(tab, "{\"name\": \"%s\", \"value\": %.2f}", p0->irq_name, S_VALUE(q->interrupt, p->interrupt, itv)); } else { /* * Instead of printing "N/A", assume that previous value * for this new interrupt was zero. */ xprintf0(tab, "{\"name\": \"%s\", \"value\": %.2f}", p0->irq_name, S_VALUE(0, p->interrupt, itv)); } } printf("\n"); xprintf0(--tab, "] }"); } printf("\n"); xprintf0(--tab, "]"); } /* *************************************************************************** * Display interrupts statistics for each CPU in plain or JSON format. * * IN: * @st_ic Array for per-CPU statistics. * @ic_nr Number of interrupts (hard or soft) per CPU. * @dis TRUE if a header line must be printed. * @itv Interval value. * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where current statistics will be saved. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. * @tab Number of tabs to print (JSON format only). * @next TRUE is a previous activity has been displayed (JSON format * only). * @type Activity (M_D_IRQ_CPU or M_D_SOFTIRQS). * @offline_cpu_bitmap * CPU bitmap for offline CPU. *************************************************************************** */ void write_irqcpu_stats(struct stats_irqcpu *st_ic[], int ic_nr, int dis, unsigned long long itv, int prev, int curr, char *prev_string, char *curr_string, int tab, int *next, int type, unsigned char offline_cpu_bitmap[]) { if (DISPLAY_JSON_OUTPUT(flags)) { if (*next) { printf(",\n"); } *next = TRUE; write_json_irqcpu_stats(tab, st_ic, ic_nr, itv, prev, curr, type, offline_cpu_bitmap); } else { write_plain_irqcpu_stats(st_ic, ic_nr, dis, itv, prev, curr, prev_string, curr_string, offline_cpu_bitmap); } } /* *************************************************************************** * Core function used to display statistics. * * IN: * @prev Position in array where statistics used as reference are. * Stats used as reference may be the previous ones read, or * the very first ones when calculating the average. * @curr Position in array where statistics for current sample are. * @dis TRUE if a header line must be printed. * @prev_string String displayed at the beginning of a header line. This is * the timestamp of the previous sample, or "Average" when * displaying average stats. * @curr_string String displayed at the beginning of current sample stats. * This is the timestamp of the current sample, or "Average" * when displaying average stats. *************************************************************************** */ void write_stats_core(int prev, int curr, int dis, char *prev_string, char *curr_string) { unsigned long long itv, deltot_jiffies = 1; int tab = 4, next = FALSE; unsigned char offline_cpu_bitmap[BITMAP_SIZE(NR_CPUS)] = {0}; /* Test stdout */ TEST_STDOUT(STDOUT_FILENO); /* * Compute CPU "all" as sum of all individual CPU (on SMP machines) * and look for offline CPU. */ deltot_jiffies = get_global_cpu_mpstats(prev, curr, offline_cpu_bitmap); if (DISPLAY_JSON_OUTPUT(flags)) { xprintf(tab++, "{"); xprintf(tab, "\"timestamp\": \"%s\",", curr_string); } /* Get time interval */ itv = get_interval(uptime_cs[prev], uptime_cs[curr]); /* Print CPU stats */ if (DISPLAY_CPU(actflags)) { write_cpu_stats(dis, deltot_jiffies, prev, curr, prev_string, curr_string, tab, &next, offline_cpu_bitmap); } /* Print node CPU stats */ if (DISPLAY_NODE(actflags)) { set_node_cpu_stats(prev, curr); write_node_stats(dis, deltot_jiffies, prev, curr, prev_string, curr_string, tab, &next); } /* Print total number of interrupts per processor */ if (DISPLAY_IRQ_SUM(actflags)) { write_isumcpu_stats(dis, itv, prev, curr, prev_string, curr_string, tab, &next, offline_cpu_bitmap); } /* Display each interrupt value for each CPU */ if (DISPLAY_IRQ_CPU(actflags)) { write_irqcpu_stats(st_irqcpu, irqcpu_nr, dis, itv, prev, curr, prev_string, curr_string, tab, &next, M_D_IRQ_CPU, offline_cpu_bitmap); } if (DISPLAY_SOFTIRQS(actflags)) { write_irqcpu_stats(st_softirqcpu, softirqcpu_nr, dis, itv, prev, curr, prev_string, curr_string, tab, &next, M_D_SOFTIRQS, offline_cpu_bitmap); } if (DISPLAY_JSON_OUTPUT(flags)) { printf("\n"); xprintf0(--tab, "}"); } } /* *************************************************************************** * Print statistics average. * * IN: * @curr Position in array where statistics for current sample are. * @dis TRUE if a header line must be printed. *************************************************************************** */ void write_stats_avg(int curr, int dis) { char string[16]; strncpy(string, _("Average:"), 16); string[15] = '\0'; write_stats_core(2, curr, dis, string, string); } /* *************************************************************************** * Print statistics. * * IN: * @curr Position in array where statistics for current sample are. * @dis TRUE if a header line must be printed. *************************************************************************** */ void write_stats(int curr, int dis) { char cur_time[2][TIMESTAMP_LEN]; /* Get previous timestamp */ if (is_iso_time_fmt()) { strftime(cur_time[!curr], sizeof(cur_time[!curr]), "%H:%M:%S", &mp_tstamp[!curr]); } else { strftime(cur_time[!curr], sizeof(cur_time[!curr]), "%X", &(mp_tstamp[!curr])); } /* Get current timestamp */ if (is_iso_time_fmt()) { strftime(cur_time[curr], sizeof(cur_time[curr]), "%H:%M:%S", &mp_tstamp[curr]); } else { strftime(cur_time[curr], sizeof(cur_time[curr]), "%X", &(mp_tstamp[curr])); } write_stats_core(!curr, curr, dis, cur_time[!curr], cur_time[curr]); } /* *************************************************************************** * Read total number of interrupts from /proc/stat. * * IN: * @st_irq Structure where total number of interrupts will be saved. * * OUT: * @st_irq Structure with total number of interrupts. *************************************************************************** */ void read_stat_total_irq(struct stats_global_irq *st_irq) { FILE *fp; char line[1024]; unsigned long long irq_nr; if ((fp = fopen(STAT, "r")) == NULL) return; while (fgets(line, sizeof(line), fp) != NULL) { if (!strncmp(line, "intr ", 5)) { /* Read total number of interrupts received since system boot */ sscanf(line + 5, "%llu", &irq_nr); st_irq->irq_nr = (unsigned int) irq_nr; break; } } fclose(fp); } /* *************************************************************************** * Read stats from /proc/interrupts or /proc/softirqs. * * IN: * @file /proc file to read (interrupts or softirqs). * @ic_nr Number of interrupts (hard or soft) per CPU. * @curr Position in array where current statistics will be saved. * * OUT: * @st_ic Array for per-CPU interrupts statistics. *************************************************************************** */ void read_interrupts_stat(char *file, struct stats_irqcpu *st_ic[], int ic_nr, int curr) { FILE *fp; struct stats_global_irq *st_irq_i; struct stats_irqcpu *p; char *line = NULL, *li; unsigned long irq = 0; unsigned int cpu, c = 0; int cpu_index[cpu_nr], index = 0, len; char *cp, *next; if ((fp = fopen(file, "r")) != NULL) { SREALLOC(line, char, INTERRUPTS_LINE + 11 * cpu_nr); /* * Parse header line to see which CPUs are online */ while (fgets(line, INTERRUPTS_LINE + 11 * cpu_nr, fp) != NULL) { next = line; while (((cp = strstr(next, "CPU")) != NULL) && (index < cpu_nr)) { cpu = strtol(cp + 3, &next, 10); if (cpu >= cpu_nr) break; cpu_index[index++] = cpu; /* * Reset total number of interrupts received by a CPU * only for online CPU. Only needed for st_irq structures. */ st_irq_i = st_irq[curr] + cpu + 1; st_irq_i->irq_nr = 0; /* * For offline CPU, pick up previous values so that when the * CPU goes back online, values won't jump from zero. */ fwd_irq_values(st_ic, c, cpu, ic_nr, curr); c = cpu + 1; } if (index) /* Header line found */ break; } /* Process possible offline CPU at the end of the list */ fwd_irq_values(st_ic, c, cpu_nr, ic_nr, curr); /* Parse each line of interrupts statistics data */ while ((fgets(line, INTERRUPTS_LINE + 11 * cpu_nr, fp) != NULL) && (irq < ic_nr)) { /* Skip over ":" */ if ((cp = strchr(line, ':')) == NULL) /* Chr ':' not found */ continue; cp++; p = st_ic[curr] + irq; /* Remove possible heading spaces in interrupt's name... */ li = line; while (*li == ' ') li++; len = strcspn(li, ":"); if (len >= MAX_IRQ_LEN) { len = MAX_IRQ_LEN - 1; } /* ...then save its name */ strncpy(p->irq_name, li, len); p->irq_name[len] = '\0'; /* For each interrupt: Get number received by each CPU */ for (cpu = 0; cpu < index; cpu++) { p = st_ic[curr] + cpu_index[cpu] * ic_nr + irq; st_irq_i = st_irq[curr] + cpu_index[cpu] + 1; /* * No need to set (st_irqcpu + cpu * irqcpu_nr)->irq_name: * This is the same as st_irqcpu->irq_name. * Now save current interrupt value for current CPU (in * stats_irqcpu structure) and total number of interrupts * received by current CPU (in stats_global_irq structure). */ p->interrupt = strtoul(cp, &next, 10); st_irq_i->irq_nr += p->interrupt; cp = next; } irq++; } fclose(fp); free(line); } while (irq < ic_nr) { /* Nb of interrupts per processor has changed */ p = st_ic[curr] + irq; p->irq_name[0] = '\0'; /* This value means this is a dummy interrupt */ irq++; } } /* *************************************************************************** * Main loop: Read stats from the relevant sources, and display them. * * IN: * @dis_hdr Set to TRUE if the header line must always be printed. * @rows Number of rows of screen. *************************************************************************** */ void rw_mpstat_loop(int dis_hdr, int rows) { struct stats_cpu *scc; int new_cpu_nr; int curr = 1, dis = 1; unsigned long lines = rows; /* Read system uptime and CPU stats */ read_uptime(&(uptime_cs[0])); read_stat_cpu(st_cpu[0], cpu_nr + 1); /* * Calculate global CPU stats as the sum of individual ones. * Done only on SMP machines. On UP machines, we keep the values * read from /proc/stat for global CPU stats. */ if (cpu_nr > 1) { int i; memset(st_cpu[0], 0, STATS_CPU_SIZE); for (i = 1; i <= cpu_nr; i++) { scc = st_cpu[0] + i; st_cpu[0]->cpu_user += scc->cpu_user; st_cpu[0]->cpu_nice += scc->cpu_nice; st_cpu[0]->cpu_sys += scc->cpu_sys; st_cpu[0]->cpu_idle += scc->cpu_idle; st_cpu[0]->cpu_iowait += scc->cpu_iowait; st_cpu[0]->cpu_hardirq += scc->cpu_hardirq; st_cpu[0]->cpu_steal += scc->cpu_steal; st_cpu[0]->cpu_softirq += scc->cpu_softirq; st_cpu[0]->cpu_guest += scc->cpu_guest; st_cpu[0]->cpu_guest_nice += scc->cpu_guest_nice; } } /* Read system topology */ if (DISPLAY_CPU(actflags) && DISPLAY_TOPOLOGY(flags)) { read_topology(cpu_nr, st_cpu_topology); } /* * Read total number of interrupts received among all CPU. * (this is the first value on the line "intr:" in the /proc/stat file). */ if (DISPLAY_IRQ_SUM(actflags)) { read_stat_total_irq(st_irq[0]); } /* * Read number of interrupts received by each CPU, for each interrupt, * and compute the total number of interrupts received by each CPU. */ if (DISPLAY_IRQ_SUM(actflags) || DISPLAY_IRQ_CPU(actflags)) { /* Read this file to display int per CPU or total nr of int per CPU */ read_interrupts_stat(INTERRUPTS, st_irqcpu, irqcpu_nr, 0); } if (DISPLAY_SOFTIRQS(actflags)) { read_interrupts_stat(SOFTIRQS, st_softirqcpu, softirqcpu_nr, 0); } if (!interval) { /* Display since boot time */ mp_tstamp[1] = mp_tstamp[0]; memset(st_cpu[1], 0, STATS_CPU_SIZE * (cpu_nr + 1)); memset(st_node[1], 0, STATS_CPU_SIZE * (cpu_nr + 1)); memset(st_irq[1], 0, STATS_GLOBAL_IRQ_SIZE * (cpu_nr + 1)); memset(st_irqcpu[1], 0, STATS_IRQCPU_SIZE * (cpu_nr + 1) * irqcpu_nr); if (DISPLAY_SOFTIRQS(actflags)) { memset(st_softirqcpu[1], 0, STATS_IRQCPU_SIZE * (cpu_nr + 1) * softirqcpu_nr); } write_stats(0, DISP_HDR); if (DISPLAY_JSON_OUTPUT(flags)) { printf("\n\t\t\t]\n\t\t}\n\t]\n}}\n"); } exit(0); } /* Set a handler for SIGALRM */ memset(&alrm_act, 0, sizeof(alrm_act)); alrm_act.sa_handler = alarm_handler; sigaction(SIGALRM, &alrm_act, NULL); alarm(interval); /* Save the first stats collected. Will be used to compute the average */ mp_tstamp[2] = mp_tstamp[0]; uptime_cs[2] = uptime_cs[0]; memcpy(st_cpu[2], st_cpu[0], STATS_CPU_SIZE * (cpu_nr + 1)); memcpy(st_node[2], st_node[0], STATS_CPU_SIZE * (cpu_nr + 1)); memcpy(st_irq[2], st_irq[0], STATS_GLOBAL_IRQ_SIZE * (cpu_nr + 1)); memcpy(st_irqcpu[2], st_irqcpu[0], STATS_IRQCPU_SIZE * (cpu_nr + 1) * irqcpu_nr); if (DISPLAY_SOFTIRQS(actflags)) { memcpy(st_softirqcpu[2], st_softirqcpu[0], STATS_IRQCPU_SIZE * (cpu_nr + 1) * softirqcpu_nr); } /* Set a handler for SIGINT */ memset(&int_act, 0, sizeof(int_act)); int_act.sa_handler = int_handler; sigaction(SIGINT, &int_act, NULL); __pause(); if (sigint_caught) /* SIGINT signal caught during first interval: Exit immediately */ return; do { /* * Resetting the structure not needed since every fields will be set. * Exceptions are per-CPU structures: Some of them may not be filled * if corresponding processor is disabled (offline). We set them to zero * to be able to distinguish between offline and tickless CPUs. */ memset(st_cpu[curr], 0, STATS_CPU_SIZE * (cpu_nr + 1)); /* Get time */ get_xtime(&(mp_tstamp[curr]), 0, LOCAL_TIME); /* Read uptime and CPU stats */ read_uptime(&(uptime_cs[curr])); read_stat_cpu(st_cpu[curr], cpu_nr + 1); /* Read system topology */ if (DISPLAY_CPU(actflags) && DISPLAY_TOPOLOGY(flags)) { read_topology(cpu_nr, st_cpu_topology); } /* Read total number of interrupts received among all CPU */ if (DISPLAY_IRQ_SUM(actflags)) { read_stat_total_irq(st_irq[curr]); } /* * Read number of interrupts received by each CPU, for each interrupt, * and compute the total number of interrupts received by each CPU. */ if (DISPLAY_IRQ_SUM(actflags) || DISPLAY_IRQ_CPU(actflags)) { read_interrupts_stat(INTERRUPTS, st_irqcpu, irqcpu_nr, curr); } if (DISPLAY_SOFTIRQS(actflags)) { read_interrupts_stat(SOFTIRQS, st_softirqcpu, softirqcpu_nr, curr); } /* Write stats */ if (!dis_hdr) { dis = lines / rows; if (dis) { lines %= rows; } lines++; } write_stats(curr, dis); if (count > 0) { count--; } if (count) { __pause(); if (sigint_caught) { /* SIGINT signal caught => Display average stats */ count = 0; } else { if (DISPLAY_JSON_OUTPUT(flags)) { printf(",\n"); } curr ^= 1; } } if (count && USE_OPTION_H(flags)) { /* Check if a vCPU has been physically hotplugged */ new_cpu_nr = get_cpu_nr(~0, TRUE); if (new_cpu_nr > cpu_nr) { /* Recalculate number of interrupts per processor */ irqcpu_nr = get_irqcpu_nr(INTERRUPTS, NR_IRQS, new_cpu_nr) + NR_IRQCPU_PREALLOC; /* Recalculate number of soft interrupts per processor */ softirqcpu_nr = get_irqcpu_nr(SOFTIRQS, NR_IRQS, new_cpu_nr) + NR_IRQCPU_PREALLOC; /* Reallocate cpu stats structures */ salloc_mp_struct(new_cpu_nr + 1, cpu_nr + 1); /* Get NUMA node placement */ node_nr = get_node_placement(new_cpu_nr, cpu_per_node, cpu2node); /* Update the highest processor number */ cpu_nr = new_cpu_nr; } } } while (count); /* Write stats average */ if (DISPLAY_JSON_OUTPUT(flags)) { printf("\n\t\t\t]\n\t\t}\n\t]\n}}\n"); } else { write_stats_avg(curr, dis_hdr); } } /* *************************************************************************** * Main entry to the program *************************************************************************** */ int main(int argc, char **argv) { int opt = 0, i, actset = FALSE; struct utsname header; int dis_hdr = -1; int rows = 23; char *t; #ifdef USE_NLS /* Init National Language Support */ init_nls(); #endif /* Init color strings */ init_colors(); /* Get HZ */ get_HZ(); /* What is the highest processor number on this machine? */ cpu_nr = get_cpu_nr(~0, TRUE); /* Calculate number of interrupts per processor */ irqcpu_nr = get_irqcpu_nr(INTERRUPTS, NR_IRQS, cpu_nr) + NR_IRQCPU_PREALLOC; /* Calculate number of soft interrupts per processor */ softirqcpu_nr = get_irqcpu_nr(SOFTIRQS, NR_IRQS, cpu_nr) + NR_IRQCPU_PREALLOC; /* * cpu_nr: a value of 2 means there are 2 processors (0 and 1). * In this case, we have to allocate 3 structures: global, proc0 and proc1. */ salloc_mp_struct(cpu_nr + 1, 0); /* Get NUMA node placement */ node_nr = get_node_placement(cpu_nr, cpu_per_node, cpu2node); while (++opt < argc) { if (!strncmp(argv[opt], "--dec=", 6) && (strlen(argv[opt]) == 7)) { /* Check that the argument is a digit */ if (!isdigit(argv[opt][6])) { usage(argv[0]); } /* Get number of decimal places */ dplaces_nr = atoi(argv[opt] + 6); if ((dplaces_nr < 0) || (dplaces_nr > 2)) { usage(argv[0]); } } else if (!strcmp(argv[opt], "-I")) { if (!argv[++opt]) { usage(argv[0]); } actset = TRUE; for (t = strtok(argv[opt], ","); t; t = strtok(NULL, ",")) { if (!strcmp(t, K_SUM)) { /* Display total number of interrupts per CPU */ actflags |= M_D_IRQ_SUM; } else if (!strcmp(t, K_CPU)) { /* Display interrupts per CPU */ actflags |= M_D_IRQ_CPU; } else if (!strcmp(t, K_SCPU)) { /* Display soft interrupts per CPU */ actflags |= M_D_SOFTIRQS; } else if (!strcmp(t, K_ALL)) { actflags |= M_D_IRQ_SUM + M_D_IRQ_CPU + M_D_SOFTIRQS; } else { usage(argv[0]); } } } else if (!strcmp(argv[opt], "-o")) { /* Select output format */ if (argv[++opt] && !strcmp(argv[opt], K_JSON)) { flags |= F_JSON_OUTPUT; } else { usage(argv[0]); } } else if (!strcmp(argv[opt], "-N")) { if (!argv[++opt]) { usage(argv[0]); } if (node_nr >= 0) { flags |= F_OPTION_N; actflags |= M_D_NODE; actset = TRUE; dis_hdr = 9; if (parse_values(argv[opt], node_bitmap, node_nr + 1, K_LOWERALL)) { usage(argv[0]); } } } else if (!strcmp(argv[opt], "-P")) { /* '-P ALL' can be used on UP machines */ if (!argv[++opt]) { usage(argv[0]); } flags |= F_OPTION_P; dis_hdr = 9; if (parse_values(argv[opt], cpu_bitmap, cpu_nr, K_LOWERALL)) { usage(argv[0]); } } #ifdef TEST else if (!strncmp(argv[opt], "--getenv", 8)) { __env = TRUE; } #endif else if (!strncmp(argv[opt], "-", 1)) { for (i = 1; *(argv[opt] + i); i++) { switch (*(argv[opt] + i)) { case 'A': flags |= F_OPTION_A; actflags |= M_D_CPU + M_D_IRQ_SUM + M_D_IRQ_CPU + M_D_SOFTIRQS; if (node_nr >= 0) { actflags |= M_D_NODE; } actset = TRUE; break; case 'H': /* Display physically hotplugged vCPU */ flags |= F_OPTION_H; break; case 'n': /* Display CPU stats based on NUMA node placement */ if (node_nr >= 0) { actflags |= M_D_NODE; actset = TRUE; } break; case 'T': /* Display logical topology */ flags |= F_TOPOLOGY; break; case 'u': /* Display CPU */ actflags |= M_D_CPU; break; case 'V': { char *mpstat_env[] = {ENV_COLORS, ENV_COLORS_SGR, ENV_TIME_FMT}; #define MPSTAT_ENV_NR 3 /* Print environment contents, version number and exit */ print_version(mpstat_env, MPSTAT_ENV_NR); break; } default: usage(argv[0]); } } } else if (interval < 0) { /* Get interval */ if (strspn(argv[opt], DIGITS) != strlen(argv[opt])) { usage(argv[0]); } interval = atol(argv[opt]); if (interval < 0) { usage(argv[0]); } count = -1; } else if (count <= 0) { /* Get count value */ if ((strspn(argv[opt], DIGITS) != strlen(argv[opt])) || !interval) { usage(argv[0]); } count = atol(argv[opt]); if (count < 1) { usage(argv[0]); } } else { usage(argv[0]); } } /* Default: Display CPU (e.g., "mpstat", "mpstat -P 1", "mpstat -P 1 -n", "mpstat -P 1 -N 1"... */ if (!actset || (USE_OPTION_P(flags) && !(actflags & ~M_D_NODE))) { actflags |= M_D_CPU; } if (count_bits(&actflags, sizeof(unsigned int)) > 1) { dis_hdr = 9; } if (USE_OPTION_A(flags)) { /* * Set -P ALL -N ALL only if individual CPU and/or nodes * have not been selected. */ if ((node_nr >= 0) && !USE_OPTION_N(flags)) { memset(node_bitmap, ~0, ((cpu_nr + 1) >> 3) + 1); flags += F_OPTION_N; } if (!USE_OPTION_P(flags)) { memset(cpu_bitmap, ~0, ((cpu_nr + 1) >> 3) + 1); flags += F_OPTION_P; } } if (!USE_OPTION_P(flags)) { /* Option -P not used: Set bit 0 (global stats among all proc) */ *cpu_bitmap = 1; } if (!USE_OPTION_N(flags)) { /* Option -N not used: Set bit 0 (global stats among all nodes) */ *node_bitmap = 1; } if (dis_hdr < 0) { dis_hdr = 0; } if (!dis_hdr) { /* Get window size */ rows = get_win_height(); } if (interval < 0) { /* Interval not set => display stats since boot time */ interval = 0; } if (DISPLAY_JSON_OUTPUT(flags)) { /* Use a decimal point to make JSON code compliant with RFC7159 */ setlocale(LC_NUMERIC, "C"); } /* Get time */ get_xtime(&(mp_tstamp[0]), 0, LOCAL_TIME); /* * Don't buffer data if redirected to a pipe. * Note: With musl-c, the behavior of this function is undefined except * when it is the first operation on the stream. */ setbuf(stdout, NULL); /* Get system name, release number and hostname */ __uname(&header); print_gal_header(&(mp_tstamp[0]), header.sysname, header.release, header.nodename, header.machine, get_cpu_nr(~0, FALSE), DISPLAY_JSON_OUTPUT(flags)); /* Main loop */ rw_mpstat_loop(dis_hdr, rows); /* Free structures */ sfree_mp_struct(); return 0; }