1 /* vi: set sw=4 ts=4: */
3 * Report CPU and I/O stats, based on sysstat version 9.1.2 by Sebastien Godard
5 * Copyright (C) 2010 Marek Polacek <mmpolacek@gmail.com>
7 * Licensed under GPLv2, see file LICENSE in this source tree.
10 //config:config IOSTAT
11 //config: bool "iostat"
14 //config: Report CPU and I/O statistics
16 //applet:IF_IOSTAT(APPLET(iostat, BB_DIR_BIN, BB_SUID_DROP))
18 //kbuild:lib-$(CONFIG_IOSTAT) += iostat.o
21 #include <sys/utsname.h> /* struct utsname */
23 //#define debug(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__)
24 #define debug(fmt, ...) ((void)0)
26 #define MAX_DEVICE_NAME 12
27 #define MAX_DEVICE_NAME_STR "12"
30 typedef unsigned long long cputime_t
;
31 typedef long long icputime_t
;
32 # define FMT_DATA "ll"
33 # define CPUTIME_MAX (~0ULL)
35 typedef unsigned long cputime_t
;
36 typedef long icputime_t
;
38 # define CPUTIME_MAX (~0UL)
59 cputime_t vector
[N_STATS_CPU
];
69 unsigned long long rd_sectors
;
70 unsigned long long wr_sectors
;
75 typedef struct stats_dev
{
76 struct stats_dev
*next
;
77 char dname
[MAX_DEVICE_NAME
+ 1];
78 stats_dev_data_t prev_data
;
79 stats_dev_data_t curr_data
;
82 /* Globals. Sort by size and access frequency. */
85 unsigned total_cpus
; /* Number of CPUs */
86 unsigned clk_tck
; /* Number of clock ticks per second */
87 llist_t
*dev_name_list
; /* List of devices entered on the command line */
88 stats_dev_t
*stats_dev_list
;
95 #define G (*ptr_to_globals)
96 #define INIT_G() do { \
97 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
102 /* Must match option string! */
112 static ALWAYS_INLINE
int this_is_smp(void)
114 return (G
.total_cpus
> 1);
117 static void print_header(void)
122 uname(&uts
); /* never fails */
124 /* Date representation for the current locale */
125 strftime(buf
, sizeof(buf
), "%x", &G
.tmtime
);
127 printf("%s %s (%s) \t%s \t_%s_\t(%u CPU)\n\n",
128 uts
.sysname
, uts
.release
, uts
.nodename
,
129 buf
, uts
.machine
, G
.total_cpus
);
132 static void get_localtime(struct tm
*ptm
)
136 localtime_r(&timer
, ptm
);
139 static void print_timestamp(void)
142 /* %x: date representation for the current locale */
143 /* %X: time representation for the current locale */
144 strftime(buf
, sizeof(buf
), "%x %X", &G
.tmtime
);
148 static cputime_t
get_smp_uptime(void)
151 unsigned long sec
, dec
;
153 fp
= xfopen_for_read("/proc/uptime");
155 if (fscanf(fp
, "%lu.%lu", &sec
, &dec
) != 2)
156 bb_error_msg_and_die("can't read '%s'", "/proc/uptime");
160 return (cputime_t
)sec
* G
.clk_tck
+ dec
* G
.clk_tck
/ 100;
163 /* Fetch CPU statistics from /proc/stat */
164 static void get_cpu_statistics(stats_cpu_t
*sc
)
169 fp
= xfopen_for_read("/proc/stat");
171 memset(sc
, 0, sizeof(*sc
));
173 while (fgets(buf
, sizeof(buf
), fp
)) {
177 /* Does the line start with "cpu "? */
178 if (!starts_with_cpu(buf
) || buf
[3] != ' ') {
182 for (i
= STATS_CPU_USER
; i
<= STATS_CPU_GUEST
; i
++) {
183 ibuf
= skip_whitespace(ibuf
);
184 sscanf(ibuf
, "%"FMT_DATA
"u", &sc
->vector
[i
]);
185 if (i
!= STATS_CPU_GUEST
) {
186 sc
->vector
[GLOBAL_UPTIME
] += sc
->vector
[i
];
188 ibuf
= skip_non_whitespace(ibuf
);
194 sc
->vector
[SMP_UPTIME
] = get_smp_uptime();
200 static ALWAYS_INLINE cputime_t
get_interval(cputime_t old
, cputime_t
new)
202 cputime_t itv
= new - old
;
204 return (itv
== 0) ? 1 : itv
;
207 #if CPUTIME_MAX > 0xffffffff
209 * Handle overflow conditions properly for counters which can have
210 * less bits than cputime_t, depending on the kernel version.
212 /* Surprisingly, on 32bit inlining is a size win */
213 static ALWAYS_INLINE cputime_t
overflow_safe_sub(cputime_t prev
, cputime_t curr
)
215 cputime_t v
= curr
- prev
;
217 if ((icputime_t
)v
< 0 /* curr < prev - counter overflow? */
218 && prev
<= 0xffffffff /* kernel uses 32bit value for the counter? */
220 /* Add 33th bit set to 1 to curr, compensating for the overflow */
221 /* double shift defeats "warning: left shift count >= width of type" */
222 v
+= ((cputime_t
)1 << 16) << 16;
227 static ALWAYS_INLINE cputime_t
overflow_safe_sub(cputime_t prev
, cputime_t curr
)
233 static double percent_value(cputime_t prev
, cputime_t curr
, cputime_t itv
)
235 return ((double)overflow_safe_sub(prev
, curr
)) / itv
* 100;
238 static void print_stats_cpu_struct(stats_cpu_pair_t
*stats
)
240 cputime_t
*p
= stats
->prev
->vector
;
241 cputime_t
*c
= stats
->curr
->vector
;
242 printf(" %7.2f %7.2f %7.2f %7.2f %7.2f %7.2f\n",
243 percent_value(p
[STATS_CPU_USER
] , c
[STATS_CPU_USER
] , stats
->itv
),
244 percent_value(p
[STATS_CPU_NICE
] , c
[STATS_CPU_NICE
] , stats
->itv
),
245 percent_value(p
[STATS_CPU_SYSTEM
] + p
[STATS_CPU_SOFTIRQ
] + p
[STATS_CPU_IRQ
],
246 c
[STATS_CPU_SYSTEM
] + c
[STATS_CPU_SOFTIRQ
] + c
[STATS_CPU_IRQ
], stats
->itv
),
247 percent_value(p
[STATS_CPU_IOWAIT
], c
[STATS_CPU_IOWAIT
], stats
->itv
),
248 percent_value(p
[STATS_CPU_STEAL
] , c
[STATS_CPU_STEAL
] , stats
->itv
),
249 percent_value(p
[STATS_CPU_IDLE
] , c
[STATS_CPU_IDLE
] , stats
->itv
)
253 static void cpu_report(stats_cpu_pair_t
*stats
)
255 /* Always print a header */
256 puts("avg-cpu: %user %nice %system %iowait %steal %idle");
258 /* Print current statistics */
259 print_stats_cpu_struct(stats
);
262 static void print_stats_dev_struct(stats_dev_t
*stats_dev
, cputime_t itv
)
264 stats_dev_data_t
*p
= &stats_dev
->prev_data
;
265 stats_dev_data_t
*c
= &stats_dev
->curr_data
;
266 if (option_mask32
& OPT_z
)
267 if (p
->rd_ops
== c
->rd_ops
&& p
->wr_ops
== c
->wr_ops
)
270 printf("%-13s %8.2f %12.2f %12.2f %10llu %10llu\n",
272 percent_value(p
->rd_ops
+ p
->wr_ops
, c
->rd_ops
+ c
->wr_ops
, itv
),
273 percent_value(p
->rd_sectors
, c
->rd_sectors
, itv
) / G
.unit
.div
,
274 percent_value(p
->wr_sectors
, c
->wr_sectors
, itv
) / G
.unit
.div
,
275 (c
->rd_sectors
- p
->rd_sectors
) / G
.unit
.div
,
276 (c
->wr_sectors
- p
->wr_sectors
) / G
.unit
.div
280 static void print_devstat_header(void)
282 printf("Device:%15s%6s%s/s%6s%s/s%6s%s%6s%s\n",
284 G
.unit
.str
, "_read", G
.unit
.str
, "_wrtn",
285 G
.unit
.str
, "_read", G
.unit
.str
, "_wrtn"
290 * Is input partition of format [sdaN]?
292 static int is_partition(const char *dev
)
294 /* Ok, this is naive... */
295 return ((dev
[0] - 's') | (dev
[1] - 'd') | (dev
[2] - 'a')) == 0 && isdigit(dev
[3]);
298 static stats_dev_t
*stats_dev_find_or_new(const char *dev_name
)
300 stats_dev_t
**curr
= &G
.stats_dev_list
;
302 while (*curr
!= NULL
) {
303 if (strcmp((*curr
)->dname
, dev_name
) == 0)
305 curr
= &(*curr
)->next
;
308 *curr
= xzalloc(sizeof(stats_dev_t
));
309 strncpy((*curr
)->dname
, dev_name
, MAX_DEVICE_NAME
);
313 static void stats_dev_free(stats_dev_t
*stats_dev
)
316 stats_dev_free(stats_dev
->next
);
321 static void do_disk_statistics(cputime_t itv
)
324 char dev_name
[MAX_DEVICE_NAME
+ 1];
325 unsigned long long rd_sec_or_dummy
;
326 unsigned long long wr_sec_or_dummy
;
327 stats_dev_data_t
*curr_data
;
328 stats_dev_t
*stats_dev
;
332 fp
= xfopen_for_read("/proc/diskstats");
333 /* Read and possibly print stats from /proc/diskstats */
334 while (fgets(buf
, sizeof(buf
), fp
)) {
335 sscanf(buf
, "%*s %*s %"MAX_DEVICE_NAME_STR
"s", dev_name
);
336 if (G
.dev_name_list
) {
337 /* Is device name in list? */
338 if (!llist_find_str(G
.dev_name_list
, dev_name
))
340 } else if (is_partition(dev_name
)) {
344 stats_dev
= stats_dev_find_or_new(dev_name
);
345 curr_data
= &stats_dev
->curr_data
;
347 rc
= sscanf(buf
, "%*s %*s %*s %lu %llu %llu %llu %lu %*s %llu",
350 &curr_data
->rd_sectors
,
353 &curr_data
->wr_sectors
);
355 curr_data
->rd_sectors
= rd_sec_or_dummy
;
356 curr_data
->wr_sectors
= wr_sec_or_dummy
;
357 //curr_data->rd_ops = ;
358 curr_data
->wr_ops
= (unsigned long)curr_data
->rd_sectors
;
361 if (!G
.dev_name_list
/* User didn't specify device */
363 && curr_data
->rd_ops
== 0
364 && curr_data
->wr_ops
== 0
366 /* Don't print unused device */
370 /* Print current statistics */
371 print_stats_dev_struct(stats_dev
, itv
);
372 stats_dev
->prev_data
= *curr_data
;
378 static void dev_report(cputime_t itv
)
380 /* Always print a header */
381 print_devstat_header();
383 /* Fetch current disk statistics */
384 do_disk_statistics(itv
);
387 //usage:#define iostat_trivial_usage
388 //usage: "[-c] [-d] [-t] [-z] [-k|-m] [ALL|BLOCKDEV...] [INTERVAL [COUNT]]"
389 //usage:#define iostat_full_usage "\n\n"
390 //usage: "Report CPU and I/O statistics\n"
391 //usage: "\n -c Show CPU utilization"
392 //usage: "\n -d Show device utilization"
393 //usage: "\n -t Print current time"
394 //usage: "\n -z Omit devices with no activity"
395 //usage: "\n -k Use kb/s"
396 //usage: "\n -m Use Mb/s"
398 int iostat_main(int argc
, char **argv
) MAIN_EXTERNALLY_VISIBLE
;
399 int iostat_main(int argc UNUSED_PARAM
, char **argv
)
404 stats_cpu_t stats_data
[2];
405 smallint current_stats
;
409 memset(&stats_data
, 0, sizeof(stats_data
));
411 /* Get number of clock ticks per sec */
412 G
.clk_tck
= bb_clk_tck();
414 /* Determine number of CPUs */
415 G
.total_cpus
= get_cpu_count();
416 if (G
.total_cpus
== 0)
419 /* Parse and process arguments */
420 /* -k and -m are mutually exclusive */
421 opt_complementary
= "k--m:m--k";
422 opt
= getopt32(argv
, "cdtzkm");
423 if (!(opt
& (OPT_c
+ OPT_d
)))
425 opt
|= OPT_c
+ OPT_d
;
429 /* Store device names into device list */
430 while (*argv
&& !isdigit(*argv
[0])) {
431 if (strcmp(*argv
, "ALL") != 0) {
432 /* If not ALL, save device name */
433 char *dev_name
= skip_dev_pfx(*argv
);
434 if (!llist_find_str(G
.dev_name_list
, dev_name
)) {
435 llist_add_to(&G
.dev_name_list
, dev_name
);
447 interval
= xatoi_positive(*argv
);
448 count
= (interval
!= 0 ? -1 : 1);
451 /* Get count value */
452 count
= xatoi_positive(*argv
);
465 get_localtime(&G
.tmtime
);
473 stats_cpu_pair_t stats
;
475 stats
.prev
= &stats_data
[current_stats
^ 1];
476 stats
.curr
= &stats_data
[current_stats
];
478 /* Fill the time structure */
479 get_localtime(&G
.tmtime
);
481 /* Fetch current CPU statistics */
482 get_cpu_statistics(stats
.curr
);
485 stats
.itv
= get_interval(
486 stats
.prev
->vector
[GLOBAL_UPTIME
],
487 stats
.curr
->vector
[GLOBAL_UPTIME
]
496 /* Separate outputs by a newline */
502 stats
.itv
= get_interval(
503 stats
.prev
->vector
[SMP_UPTIME
],
504 stats
.curr
->vector
[SMP_UPTIME
]
507 dev_report(stats
.itv
);
523 if (ENABLE_FEATURE_CLEAN_UP
) {
524 llist_free(G
.dev_name_list
, NULL
);
525 stats_dev_free(G
.stats_dev_list
);