strace/count.c
Eugene Syromyatnikov d055196eb4 count: add information about minimum and maximum call duration
* count.c (struct call_counts): Add time_min, time_max fields.
(max_ts): New constant.
(enum count_summary_columns): Add CSC_TIME_MIN, CSC_TIME_MAX.
(count_syscall): Fill time_min field with max_ts in counts
initialisation, update time_min and time_max.
(min_time_cmp, max_time_cmp): New functions.
(set_sortby): Add keys for them.
(set_count_summary_columns): Add support for time_min and time_max
fields printing.
* strace.1.in (.SH OPTIONS) <-S, -U>: Document it.

Closes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=240945
Closes: https://github.com/strace/strace/issues/32
2018-09-04 20:36:08 +02:00

497 lines
13 KiB
C

/*
* Copyright (c) 1991, 1992 Paul Kranenburg <pk@cs.few.eur.nl>
* Copyright (c) 1993 Branko Lankester <branko@hacktic.nl>
* Copyright (c) 1993, 1994, 1995, 1996 Rick Sladkey <jrs@world.std.com>
* Copyright (c) 1996-1999 Wichert Akkerman <wichert@cistron.nl>
* Copyright (c) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Linux for s390 port by D.J. Barrow
* <barrow_dj@mail.yahoo.com,djbarrow@de.ibm.com>
* Copyright (c) 2004 Roland McGrath <roland@redhat.com>
* Copyright (c) 2006 Dmitry V. Levin <ldv@altlinux.org>
* Copyright (c) 2006-2018 The strace developers.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "defs.h"
#include <stdarg.h>
/* Per-syscall stats structure */
struct call_counts {
/* time may be total latency or system time */
struct timespec time;
struct timespec time_min;
struct timespec time_max;
double time_avg;
unsigned int calls, errors;
};
static struct call_counts *countv[SUPPORTED_PERSONALITIES];
#define counts (countv[current_personality])
static const struct timespec zero_ts;
static const struct timespec max_ts = {
(time_t) (long long) (zero_extend_signed_to_ull(~(((time_t) 0))) >> 1),
LONG_MAX };
static struct timespec overhead;
enum count_summary_columns {
CSC_NONE,
CSC_TIME_100S,
CSC_TIME_TOTAL,
CSC_TIME_MIN,
CSC_TIME_MAX,
CSC_TIME_AVG,
CSC_CALLS,
CSC_ERRORS,
CSC_SC_NAME,
CSC_MAX,
};
#define DEF_COLUMNS \
_(CSC_TIME_100S), \
_(CSC_TIME_TOTAL), \
_(CSC_TIME_AVG), \
_(CSC_CALLS), \
_(CSC_ERRORS), \
_(CSC_SC_NAME), \
/* End of DEF_COLUMNS definition */
#define _(x) x
static uint8_t columns[CSC_MAX] = { DEF_COLUMNS };
#undef _
#define _(x) [x] = 1
static uint8_t visible[CSC_MAX] = { DEF_COLUMNS };
#undef _
void
count_syscall(struct tcb *tcp, const struct timespec *syscall_exiting_ts)
{
if (!scno_in_range(tcp->scno))
return;
if (!counts) {
counts = xcalloc(nsyscalls, sizeof(*counts));
for (size_t i = 0; i < nsyscalls; i++)
counts[i].time_min = max_ts;
}
struct call_counts *cc = &counts[tcp->scno];
cc->calls++;
if (syserror(tcp))
cc->errors++;
struct timespec wts;
if (count_wallclock) {
/* wall clock time spent while in syscall */
ts_sub(&wts, syscall_exiting_ts, &tcp->etime);
} else {
/* system CPU time spent while in syscall */
ts_sub(&wts, &tcp->stime, &tcp->ltime);
}
ts_sub(&wts, &wts, &overhead);
ts_max(&wts, &wts, &zero_ts);
if (ts_cmp(&wts, &cc->time_min) < 0)
cc->time_min = wts;
if (ts_cmp(&wts, &cc->time_max) > 0)
cc->time_max = wts;
ts_add(&cc->time, &cc->time, &wts);
}
static int
time_cmp(const void *a, const void *b)
{
return -ts_cmp(&counts[*((unsigned int *) a)].time,
&counts[*((unsigned int *) b)].time);
}
static int
min_time_cmp(const void *a, const void *b)
{
return -ts_cmp(&counts[*((unsigned int *) a)].time_min,
&counts[*((unsigned int *) b)].time_min);
}
static int
max_time_cmp(const void *a, const void *b)
{
return -ts_cmp(&counts[*((unsigned int *) a)].time_max,
&counts[*((unsigned int *) b)].time_max);
}
static int
avg_time_cmp(const void *a, const void *b)
{
double m = counts[*((unsigned int *) a)].time_avg;
double n = counts[*((unsigned int *) b)].time_avg;
return (m < n) ? 1 : (m > n) ? -1 : 0;
}
static int
syscall_cmp(const void *a, const void *b)
{
const char *a_name = sysent[*((unsigned int *) a)].sys_name;
const char *b_name = sysent[*((unsigned int *) b)].sys_name;
return strcmp(a_name ? a_name : "", b_name ? b_name : "");
}
static int
count_cmp(const void *a, const void *b)
{
unsigned int m = counts[*((unsigned int *) a)].calls;
unsigned int n = counts[*((unsigned int *) b)].calls;
return (m < n) ? 1 : (m > n) ? -1 : 0;
}
static int
error_cmp(const void *a, const void *b)
{
unsigned int m = counts[*((unsigned int *) a)].errors;
unsigned int n = counts[*((unsigned int *) b)].errors;
return (m < n) ? 1 : (m > n) ? -1 : 0;
}
static int (*sortfun)(const void *a, const void *b);
void
set_sortby(const char *sortby)
{
static const struct {
int (*fn)(const void *a, const void *b);
const char *name;
} sort_fns[] = {
{ time_cmp, "time" },
{ time_cmp, "time_total" },
{ time_cmp, "total_time" },
{ min_time_cmp, "min_time" },
{ min_time_cmp, "time_min" },
{ max_time_cmp, "max_time" },
{ max_time_cmp, "time_max" },
{ avg_time_cmp, "avg_time" },
{ avg_time_cmp, "time_avg" },
{ count_cmp, "count" },
{ count_cmp, "calls" },
{ error_cmp, "errors" },
{ error_cmp, "error" },
{ syscall_cmp, "name" },
{ syscall_cmp, "syscall_name" },
{ syscall_cmp, "syscall" },
{ NULL, "nothing" },
{ NULL, "none" },
};
for (size_t i = 0; i < ARRAY_SIZE(sort_fns); i++) {
if (strcmp(sort_fns[i].name, sortby))
continue;
sortfun = sort_fns[i].fn;
return;
}
error_msg_and_help("invalid sortby: '%s'", sortby);
}
void
set_count_summary_columns(const char *s)
{
static const char *cnames[] = {
[CSC_TIME_100S] = "time_percent",
[CSC_TIME_TOTAL] = "total_time",
[CSC_TIME_MIN] = "min_time",
[CSC_TIME_MAX] = "max_time",
[CSC_TIME_AVG] = "avg_time",
[CSC_CALLS] = "calls",
[CSC_ERRORS] = "errors",
[CSC_SC_NAME] = "syscall",
};
const char *pos = s;
const char *prev = s;
size_t cur = 0;
memset(columns, 0, sizeof(columns));
memset(visible, 0, sizeof(visible));
do {
bool found = false;
pos = strchr(prev, ',');
for (size_t i = 0; i < ARRAY_SIZE(cnames); i++) {
if (!cnames[i] || (pos
? ((size_t) (pos - prev) != strlen(cnames[i]))
|| strncmp(prev, cnames[i], pos - prev)
: strcmp(prev, cnames[i]))) {
continue;
}
if (visible[i])
error_msg_and_help("call summary column "
"has been provided more "
"than once: '%s' (-U option "
"residual: '%s')",
cnames[i], prev);
columns[cur++] = i;
visible[i] = 1;
found = true;
break;
}
if (!found)
error_msg_and_help("unknown column name: '%.*s'",
(int) (pos ? pos - prev : INT_MAX),
prev);
prev = pos + 1;
} while (pos);
}
void
set_overhead(const char *str)
{
if (parse_ts(str, &overhead) < 0)
error_msg_and_help("invalid -O argument: '%s'", str);
}
static size_t ATTRIBUTE_FORMAT((printf, 1, 2))
num_chars(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
int ret = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
return (unsigned int) MAX(ret, 0);
}
static void
call_summary_pers(FILE *outf)
{
enum column_flags {
CF_L = 1 << 0, /* Left-aligned column */
};
static const struct {
const char *s;
size_t sz;
const char *fmt;
uint32_t flags;
} cdesc[] = {
[CSC_TIME_100S] = { ARRSZ_PAIR("% time"), "%*.2f" },
[CSC_TIME_TOTAL] = { ARRSZ_PAIR("seconds"), "%*.6f" },
[CSC_TIME_MIN] = { ARRSZ_PAIR("shortest"), "%*.6f" },
[CSC_TIME_MAX] = { ARRSZ_PAIR("longest"), "%*.6f" },
[CSC_TIME_AVG] = { ARRSZ_PAIR("usecs/call"), "%*lu" },
[CSC_CALLS] = { ARRSZ_PAIR("calls"), "%*" PRIu64 },
[CSC_ERRORS] = { ARRSZ_PAIR("errors"), "%*" PRIu64 },
[CSC_SC_NAME] = { ARRSZ_PAIR("syscall"), "%-*s", CF_L },
};
unsigned int *indices;
struct timespec tv_cum = zero_ts;
struct timespec tv_min = max_ts;
struct timespec tv_max = zero_ts;
uint64_t call_cum = 0;
uint64_t error_cum = 0;
double float_tv_cum;
double percent;
double ts_avg_max = 0;
size_t sc_name_max = 0;
/* sort, calculate statistics */
indices = xcalloc(sizeof(indices[0]), nsyscalls);
for (size_t i = 0; i < nsyscalls; i++) {
struct timespec dtv;
indices[i] = i;
if (counts[i].calls == 0)
continue;
ts_add(&tv_cum, &tv_cum, &counts[i].time);
ts_min(&tv_min, &tv_min, &counts[i].time_min);
ts_max(&tv_max, &tv_max, &counts[i].time_max);
call_cum += counts[i].calls;
error_cum += counts[i].errors;
ts_div(&dtv, &counts[i].time, counts[i].calls);
counts[i].time_avg = ts_float(&dtv);
ts_avg_max = MAX(ts_avg_max, counts[i].time_avg);
sc_name_max = MAX(sc_name_max, strlen(sysent[i].sys_name));
}
float_tv_cum = ts_float(&tv_cum);
if (sortfun)
qsort((void *) indices, nsyscalls, sizeof(indices[0]), sortfun);
/* calculate column widths */
#define W_(c_, v_) [c_] = MAX((cdesc[c_].sz - 1), (v_))
unsigned int cwidths[CSC_MAX] = {
W_(CSC_TIME_100S, sizeof("100.00") - 1),
W_(CSC_TIME_TOTAL, num_chars("%.6f", float_tv_cum)),
W_(CSC_TIME_MIN, num_chars("%ld.000000", tv_min.tv_sec)),
W_(CSC_TIME_MAX, num_chars("%ld.000000", tv_max.tv_sec)),
W_(CSC_TIME_AVG, num_chars("%lu", (unsigned long)
(ts_avg_max * 1e6))),
W_(CSC_CALLS, num_chars("%" PRIu64, call_cum)),
W_(CSC_ERRORS, num_chars("%" PRIu64, error_cum)),
W_(CSC_SC_NAME, sc_name_max + 1),
};
#undef W_
/* header */
for (size_t i = 0; columns[i] && i < ARRAY_SIZE(columns); i++) {
const char *fmt = cdesc[columns[i]].flags & CF_L
? "%s%-*s" : "%s%*s";
fprintf(outf, fmt, i ? " " : "", cwidths[columns[i]],
cdesc[columns[i]].s);
}
fputc('\n', outf);
for (size_t i = 0; columns[i] && i < ARRAY_SIZE(columns); i++) {
if (i)
fputc(' ', outf);
for (size_t j = 0; j < cwidths[columns[i]]; j++)
fputc('-', outf);
}
fputc('\n', outf);
/* data output */
for (size_t i = 0; i < nsyscalls; i++) {
unsigned int idx = indices[i];
struct call_counts *cc = &counts[idx];
double float_syscall_time;
if (cc->calls == 0)
continue;
float_syscall_time = ts_float(&cc->time);
percent = (100.0 * float_syscall_time);
/* else: float_tv_cum can be 0.0 too and we get 0/0 = NAN */
if (percent != 0.0)
percent /= float_tv_cum;
for (size_t i = 0; columns[i] && i < ARRAY_SIZE(columns); i++) {
const size_t c = columns[i];
if (i)
fputc(' ', outf);
#define PC_(c_, val_) \
case (c_): fprintf(outf, cdesc[c].fmt, cwidths[c], (val_)); break;
switch (c) {
PC_(CSC_TIME_100S, percent)
PC_(CSC_TIME_TOTAL, float_syscall_time)
PC_(CSC_TIME_MIN, ts_float(&cc->time_min))
PC_(CSC_TIME_MAX, ts_float(&cc->time_max))
PC_(CSC_TIME_AVG, (long) (cc->time_avg * 1e6))
PC_(CSC_CALLS, cc->calls)
case CSC_ERRORS:
if (cc->errors)
fprintf(outf, cdesc[c].fmt,
cwidths[c], cc->errors);
else
fprintf(outf, "%*s", cwidths[c], "");
break;
PC_(CSC_SC_NAME, sysent[idx].sys_name)
}
}
fputc('\n', outf);
}
free(indices);
/* footer */
for (size_t i = 0; columns[i] && i < ARRAY_SIZE(columns); i++) {
if (i)
fputc(' ', outf);
for (size_t j = 0; j < cwidths[columns[i]]; j++)
fputc('-', outf);
}
fputc('\n', outf);
/* totals */
for (size_t i = 0; columns[i] && i < ARRAY_SIZE(columns); i++) {
const size_t c = columns[i];
if (i)
fputc(' ', outf);
switch (c) {
PC_(CSC_TIME_100S, 100.0)
PC_(CSC_TIME_TOTAL, float_tv_cum)
PC_(CSC_TIME_MIN, ts_float(&tv_min))
PC_(CSC_TIME_MAX, ts_float(&tv_max))
PC_(CSC_TIME_AVG,
(unsigned long) (float_tv_cum / call_cum * 1e6))
PC_(CSC_CALLS, call_cum)
PC_(CSC_ERRORS, error_cum)
PC_(CSC_SC_NAME, "total")
}
}
fputc('\n', outf);
#undef PC_
}
void
call_summary(FILE *outf)
{
unsigned int i, old_pers = current_personality;
for (i = 0; i < SUPPORTED_PERSONALITIES; ++i) {
if (!countv[i])
continue;
if (current_personality != i)
set_personality(i);
if (i)
fprintf(outf,
"System call usage summary for %s mode:\n",
personality_names[i]);
call_summary_pers(outf);
}
if (old_pers != current_personality)
set_personality(old_pers);
}