301 lines
10 KiB
Diff
301 lines
10 KiB
Diff
|
From cbec2872f7533a87f0b63a30d70386d203ebd138 Mon Sep 17 00:00:00 2001
|
||
|
From: Crystal Wood <swood@redhat.com>
|
||
|
Date: Tue, 13 Dec 2022 16:38:28 -0600
|
||
|
Subject: [PATCH] oslat: Add command line option for bucket width
|
||
|
|
||
|
New option -W/--bucket-width allows the user to specify how large of a
|
||
|
range of latencies is covered by a single bucket, including allowing the
|
||
|
creation of sub-microsecond buckets.
|
||
|
|
||
|
When the flag is not used, output should be unchanged. However, if a
|
||
|
bucket width is specified that is not a multiple of one microsecond,
|
||
|
latencies will be output as fractional microseconds, at nanosecond
|
||
|
precision. This includes JSON output.
|
||
|
|
||
|
When using this option, it is up to the user to determine what level
|
||
|
of precision is meaningful relative to measurement error, as is noted
|
||
|
in the documentation.
|
||
|
|
||
|
Signed-off-by: Crystal Wood <swood@redhat.com>
|
||
|
Signed-off-by: John Kacur <jkacur@redhat.com>
|
||
|
---
|
||
|
src/oslat/oslat.8 | 9 +++-
|
||
|
src/oslat/oslat.c | 110 ++++++++++++++++++++++++++++++++--------------
|
||
|
2 files changed, 85 insertions(+), 34 deletions(-)
|
||
|
|
||
|
diff --git a/src/oslat/oslat.8 b/src/oslat/oslat.8
|
||
|
index 39b36df0db3f..eb96448bfff1 100644
|
||
|
--- a/src/oslat/oslat.8
|
||
|
+++ b/src/oslat/oslat.8
|
||
|
@@ -7,7 +7,7 @@ oslat \- OS Latency Detector
|
||
|
.RI "[ \-shvz ] [ \-b " bucket-size " ] [ \-B " bias " ] [ \-c " cpu-list " ] \
|
||
|
[ \-C " cpu-main-thread " ] [ \-f " rt-prio " ] [ \-\-json " filename " ] \
|
||
|
[ \-m " workload-mem " ] [\-t " runtime " ] [ \-T " trace-threshold " ] \
|
||
|
-[ \-w " workload " ]"
|
||
|
+[ \-w " workload " ] [ \-W " bucket-width " ]"
|
||
|
.SH DESCRIPTION
|
||
|
.B oslat
|
||
|
is an open source userspace polling mode stress program to detect OS level
|
||
|
@@ -57,6 +57,13 @@ NOTE: please make sure the CPU frequency on all testing cores
|
||
|
are locked before using this parmater. If you don't know how
|
||
|
to lock the freq then please don't use this parameter.
|
||
|
.TP
|
||
|
+.B \-W, \-\-bucket-width
|
||
|
+Interval between buckets in nanoseconds
|
||
|
+
|
||
|
+NOTE: Widths not a multiple of 1000 cause ns-precision output
|
||
|
+You are responsible for considering the impact of measurement
|
||
|
+overhead at the nanosecond scale.
|
||
|
+.TP
|
||
|
.B \-h, \-\-help
|
||
|
Show the help message.
|
||
|
.TP
|
||
|
diff --git a/src/oslat/oslat.c b/src/oslat/oslat.c
|
||
|
index 55302f11986b..b680f1ebce96 100644
|
||
|
--- a/src/oslat/oslat.c
|
||
|
+++ b/src/oslat/oslat.c
|
||
|
@@ -192,6 +192,10 @@ struct global {
|
||
|
struct timeval tv_start;
|
||
|
int rtprio;
|
||
|
int bucket_size;
|
||
|
+ bool bucket_size_param;
|
||
|
+ int bucket_width;
|
||
|
+ int unit_per_us;
|
||
|
+ int precision;
|
||
|
int trace_threshold;
|
||
|
int runtime;
|
||
|
/* The core that we run the main thread. Default is cpu0 */
|
||
|
@@ -325,45 +329,46 @@ static float cycles_to_sec(const struct thread *t, uint64_t cycles)
|
||
|
|
||
|
static void insert_bucket(struct thread *t, stamp_t value)
|
||
|
{
|
||
|
- int index, us;
|
||
|
+ int index;
|
||
|
+ unsigned int lat;
|
||
|
uint64_t extra;
|
||
|
+ double us;
|
||
|
|
||
|
- index = value / t->counter_mhz;
|
||
|
- assert(index >= 0);
|
||
|
- us = index + 1;
|
||
|
- assert(us > 0);
|
||
|
-
|
||
|
+ lat = (value * g.unit_per_us + t->counter_mhz - 1) / t->counter_mhz;
|
||
|
+ us = (double)lat / g.unit_per_us;
|
||
|
if (!g.preheat && g.trace_threshold && us >= g.trace_threshold) {
|
||
|
- char *line = "%s: Trace threshold (%d us) triggered on cpu %d with %u us!\n"
|
||
|
+ char *line = "%s: Trace threshold (%d us) triggered on cpu %d with %.*f us!\n"
|
||
|
"Stopping the test.\n";
|
||
|
- tracemark(line, g.app_name, g.trace_threshold, t->core_i, us);
|
||
|
- err_quit(line, g.app_name, g.trace_threshold, t->core_i, us);
|
||
|
+ tracemark(line, g.app_name, g.trace_threshold, t->core_i,
|
||
|
+ g.precision, us);
|
||
|
+ err_quit(line, g.app_name, g.trace_threshold, t->core_i,
|
||
|
+ g.precision, us);
|
||
|
}
|
||
|
|
||
|
/* Update max latency */
|
||
|
- if (us > t->maxlat)
|
||
|
- t->maxlat = us;
|
||
|
+ if (lat > t->maxlat)
|
||
|
+ t->maxlat = lat;
|
||
|
|
||
|
- if (us < t->minlat)
|
||
|
- t->minlat = us;
|
||
|
+ if (lat < t->minlat)
|
||
|
+ t->minlat = lat;
|
||
|
|
||
|
if (g.bias) {
|
||
|
/* t->bias will be set after pre-heat if user enabled it */
|
||
|
- us -= g.bias;
|
||
|
+ lat -= g.bias;
|
||
|
/*
|
||
|
* Negative should hardly happen, but if it happens, we assume we're in
|
||
|
- * the smallest bucket, which is 1us. Same to index.
|
||
|
+ * the smallest bucket.
|
||
|
*/
|
||
|
- if (us <= 0)
|
||
|
- us = 1;
|
||
|
- index -= g.bias;
|
||
|
- if (index < 0)
|
||
|
- index = 0;
|
||
|
+ if (lat <= 0)
|
||
|
+ lat = 1;
|
||
|
}
|
||
|
|
||
|
+ index = lat / g.bucket_width;
|
||
|
+ assert(index >= 0);
|
||
|
+
|
||
|
/* Too big the jitter; put into the last bucket */
|
||
|
if (index >= g.bucket_size) {
|
||
|
- /* Keep the extra bit (in us) */
|
||
|
+ /* Keep the extra bit (in bucket width multiples) */
|
||
|
extra = index - g.bucket_size;
|
||
|
if (t->overflow_sum + extra < t->overflow_sum) {
|
||
|
/* The uint64_t even overflowed itself; bail out */
|
||
|
@@ -455,6 +460,19 @@ static void *thread_main(void *arg)
|
||
|
printf("%s\n", end); \
|
||
|
} while (0)
|
||
|
|
||
|
+#define putfieldp(label, val, end) do { \
|
||
|
+ printf("%12s:\t", label); \
|
||
|
+ for (i = 0; i < g.n_threads; ++i) \
|
||
|
+ printf(" %.*f", g.precision, \
|
||
|
+ (double)(val) / g.unit_per_us); \
|
||
|
+ printf("%s\n", end); \
|
||
|
+ } while (0)
|
||
|
+
|
||
|
+static double bucket_to_lat(int bucket)
|
||
|
+{
|
||
|
+ return (g.bias + (bucket + 1) * (double)g.bucket_width) / g.unit_per_us;
|
||
|
+}
|
||
|
+
|
||
|
void calculate(struct thread *t)
|
||
|
{
|
||
|
int i, j;
|
||
|
@@ -465,11 +483,11 @@ void calculate(struct thread *t)
|
||
|
/* Calculate average */
|
||
|
sum = count = 0;
|
||
|
for (j = 0; j < g.bucket_size; j++) {
|
||
|
- sum += 1.0 * t[i].buckets[j] * (g.bias+j+1);
|
||
|
+ sum += t[i].buckets[j] * bucket_to_lat(j);
|
||
|
count += t[i].buckets[j];
|
||
|
}
|
||
|
/* Add the extra amount of huge spikes in */
|
||
|
- sum += t->overflow_sum;
|
||
|
+ sum += t->overflow_sum * g.bucket_width;
|
||
|
t[i].average = sum / count;
|
||
|
}
|
||
|
}
|
||
|
@@ -501,16 +519,16 @@ static void write_summary(struct thread *t)
|
||
|
print_dotdotdot = 0;
|
||
|
}
|
||
|
|
||
|
- snprintf(bucket_name, sizeof(bucket_name), "%03"PRIu64
|
||
|
- " (us)", g.bias+j+1);
|
||
|
+ snprintf(bucket_name, sizeof(bucket_name), "%03.*f (us)",
|
||
|
+ g.precision, bucket_to_lat(j));
|
||
|
putfield(bucket_name, t[i].buckets[j], PRIu64,
|
||
|
(j == g.bucket_size - 1) ? " (including overflows)" : "");
|
||
|
}
|
||
|
|
||
|
- putfield("Minimum", t[i].minlat, PRIu64, " (us)");
|
||
|
+ putfieldp("Minimum", t[i].minlat, " (us)");
|
||
|
putfield("Average", t[i].average, ".3lf", " (us)");
|
||
|
- putfield("Maximum", t[i].maxlat, PRIu64, " (us)");
|
||
|
- putfield("Max-Min", t[i].maxlat - t[i].minlat, PRIu64, " (us)");
|
||
|
+ putfieldp("Maximum", t[i].maxlat, " (us)");
|
||
|
+ putfieldp("Max-Min", t[i].maxlat - t[i].minlat, " (us)");
|
||
|
putfield("Duration", cycles_to_sec(&(t[i]), t[i].runtime),
|
||
|
".3f", " (sec)");
|
||
|
printf("\n");
|
||
|
@@ -537,8 +555,8 @@ static void write_summary_json(FILE *f, void *data)
|
||
|
if (t[i].buckets[j] == 0)
|
||
|
continue;
|
||
|
fprintf(f, "%s", comma ? ",\n" : "\n");
|
||
|
- fprintf(f, " \"%" PRIu64 "\": %" PRIu64,
|
||
|
- g.bias+j+1, t[i].buckets[j]);
|
||
|
+ fprintf(f, " \"%.*f\": %" PRIu64,
|
||
|
+ g.precision, bucket_to_lat(j), t[i].buckets[j]);
|
||
|
comma = 1;
|
||
|
}
|
||
|
if (comma)
|
||
|
@@ -610,6 +628,10 @@ static void usage(int error)
|
||
|
"-v, --version Display the version of the software.\n"
|
||
|
"-w, --workload Specify a kind of workload, default is no workload\n"
|
||
|
" (options: no, memmove)\n"
|
||
|
+ "-W, --bucket-width Interval between buckets in nanoseconds\n"
|
||
|
+ " NOTE: Widths not a multiple of 1000 cause ns-precision output\n"
|
||
|
+ " You are responsible for considering the impact of measurement\n"
|
||
|
+ " overhead at the nanosecond scale.\n"
|
||
|
"-z, --zero-omit Don't display buckets in the output histogram if all zeros.\n"
|
||
|
);
|
||
|
exit(error);
|
||
|
@@ -630,7 +652,7 @@ static int workload_select(char *name)
|
||
|
}
|
||
|
|
||
|
enum option_value {
|
||
|
- OPT_BUCKETSIZE=1, OPT_CPU_LIST, OPT_CPU_MAIN_THREAD,
|
||
|
+ OPT_BUCKETSIZE = 1, OPT_BUCKETWIDTH, OPT_CPU_LIST, OPT_CPU_MAIN_THREAD,
|
||
|
OPT_DURATION, OPT_JSON, OPT_RT_PRIO, OPT_HELP, OPT_TRACE_TH,
|
||
|
OPT_WORKLOAD, OPT_WORKLOAD_MEM, OPT_BIAS,
|
||
|
OPT_QUIET, OPT_SINGLE_PREHEAT, OPT_ZERO_OMIT,
|
||
|
@@ -644,6 +666,7 @@ static void parse_options(int argc, char *argv[])
|
||
|
int option_index = 0;
|
||
|
static struct option options[] = {
|
||
|
{ "bucket-size", required_argument, NULL, OPT_BUCKETSIZE },
|
||
|
+ { "bucket-width", required_argument, NULL, OPT_BUCKETWIDTH },
|
||
|
{ "cpu-list", required_argument, NULL, OPT_CPU_LIST },
|
||
|
{ "cpu-main-thread", required_argument, NULL, OPT_CPU_MAIN_THREAD},
|
||
|
{ "duration", required_argument, NULL, OPT_DURATION },
|
||
|
@@ -660,7 +683,7 @@ static void parse_options(int argc, char *argv[])
|
||
|
{ "version", no_argument, NULL, OPT_VERSION },
|
||
|
{ NULL, 0, NULL, 0 },
|
||
|
};
|
||
|
- int i, c = getopt_long(argc, argv, "b:Bc:C:D:f:hm:qsw:T:vz",
|
||
|
+ int i, c = getopt_long(argc, argv, "b:Bc:C:D:f:hm:qsw:W:T:vz",
|
||
|
options, &option_index);
|
||
|
long ncores;
|
||
|
|
||
|
@@ -670,6 +693,7 @@ static void parse_options(int argc, char *argv[])
|
||
|
switch (c) {
|
||
|
case OPT_BUCKETSIZE:
|
||
|
case 'b':
|
||
|
+ g.bucket_size_param = true;
|
||
|
g.bucket_size = strtol(optarg, NULL, 10);
|
||
|
if (g.bucket_size > 1024 || g.bucket_size <= 4) {
|
||
|
printf("Illegal bucket size: %s (should be: 4-1024)\n",
|
||
|
@@ -677,6 +701,20 @@ static void parse_options(int argc, char *argv[])
|
||
|
exit(1);
|
||
|
}
|
||
|
break;
|
||
|
+ case OPT_BUCKETWIDTH:
|
||
|
+ case 'W':
|
||
|
+ g.bucket_width = strtol(optarg, NULL, 10);
|
||
|
+ if (g.bucket_width <= 0) {
|
||
|
+ printf("Illegal bucket width: %s\n", optarg);
|
||
|
+ exit(1);
|
||
|
+ }
|
||
|
+ if (g.bucket_width % 1000) {
|
||
|
+ g.unit_per_us = 1000;
|
||
|
+ g.precision = 3;
|
||
|
+ } else {
|
||
|
+ g.bucket_width /= 1000;
|
||
|
+ }
|
||
|
+ break;
|
||
|
case OPT_BIAS:
|
||
|
case 'B':
|
||
|
g.enable_bias = 1;
|
||
|
@@ -776,6 +814,9 @@ static void parse_options(int argc, char *argv[])
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
+
|
||
|
+ if (!g.bucket_size_param && g.precision == 3 && g.bucket_width < 1000)
|
||
|
+ g.bucket_size = BUCKET_SIZE * 1000 / g.bucket_width;
|
||
|
}
|
||
|
|
||
|
void dump_globals(void)
|
||
|
@@ -811,7 +852,8 @@ static void record_bias(struct thread *t)
|
||
|
bias = t[i].minlat;
|
||
|
}
|
||
|
g.bias = bias;
|
||
|
- printf("Global bias set to %" PRId64 " (us)\n", bias);
|
||
|
+ printf("Global bias set to %.*f (us)\n", g.precision,
|
||
|
+ (double)bias / g.unit_per_us);
|
||
|
}
|
||
|
|
||
|
int main(int argc, char *argv[])
|
||
|
@@ -835,6 +877,8 @@ int main(int argc, char *argv[])
|
||
|
g.app_name = argv[0];
|
||
|
g.rtprio = 0;
|
||
|
g.bucket_size = BUCKET_SIZE;
|
||
|
+ g.bucket_width = 1;
|
||
|
+ g.unit_per_us = 1;
|
||
|
g.runtime = 1;
|
||
|
g.workload = &workload_list[WORKLOAD_DEFAULT];
|
||
|
g.workload_mem_size = WORKLOAD_MEM_SIZE;
|
||
|
--
|
||
|
2.38.1
|
||
|
|