[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[XTF PATCH v1 08/15] common: add primitives for measurements


  • To: xen-devel@xxxxxxxxxxxxxxxxxxxx
  • From: dmukhin@xxxxxxxx
  • Date: Wed, 4 Nov 2026 17:38:10 -0800
  • Arc-authentication-results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 148.163.138.245) smtp.rcpttodomain=lists.xenproject.org smtp.mailfrom=ford.com; dmarc=pass (p=reject sp=reject pct=100) action=none header.from=ford.com; dkim=pass (signature was verified) header.d=saarlouis.ford.com; dkim=pass (signature was verified) header.d=ford.com; arc=none (0)
  • Arc-message-signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=wPk5MiQpW3L4/cUn6ZNGOeDWfT7+2sCu03MdvRd5xzk=; b=CeoZErfFdWT9vE39wiFnQ0DIet2uumiotrkG2rf1jAMZJSnOQaDgUqEvLmeDbCB11W5FAsHn/Z/zGn2nJMjK013mtweebeZf/WXuUf5w0oLFlQwB0pkAF1SLXuYyfp4SkT7Vt50gmAMfG+jNMqq5wUQNH4Ac7f0pcAc5L2u6vZTeOZZmbvkO2X0yKBar2BjAMae2sdsZhWBY7gSZv/BjVQAIUcTEQ0cpsdlm+Ion8mdeN0evlKCHjhFFD18gkGovAktxcmulorq4car2pcnu21dtAz6QeV6mRiECZUD7gECKLHGHFiG2KSjVW7+c2FOB0bP3NCdvo/7s/8RwctPZ1w==
  • Arc-seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=W/3XnYa9VLUYeGGwkcOH+SMh22RJRNrM65iZYx2uuJTVCwX+S7+cp+fPy2FF4rLp/IK0n2jvDazGBnWt3J5qfTndxFdFrQYk03Nlf1XEZotXUMYQ4wxB3tyyni+GYd6GHyoI7Qq+hWM1dgkUGrtgELrZk2C3Wq5MFI5SRvbF2TQzivSJyHHidOm/elZytCDKDT9RO5UZM1TGL7QkgIo23oqL3rARTfCx7LK6zItt+GQPv01pmiCeVBBEIryit8TKkLJIZH4DmjjGj1iyIgbOfdLHxFCdX3qIAkdTzAguh8xEQArtrDN6RlgxO23wmxsZSxzgfyrGraYe92IRH46KaA==
  • Authentication-results: eu.smtp.expurgate.cloud; dkim=pass header.s=ppford header.d=ford.com header.i="@ford.com" header.h="Cc:Content-Transfer-Encoding:Content-Type:Date:From:In-Reply-To:Message-ID:MIME-Version:References:Subject:To"; dkim=pass header.s=selector2-azureford-onmicrosoft-com header.d=azureford.onmicrosoft.com header.i="@azureford.onmicrosoft.com" header.h="From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck"; dkim=pass header.s=ppserprodsaar header.d=saarlouis.ford.com header.i="@saarlouis.ford.com" header.h="Cc:Content-Transfer-Encoding:Date:From:In-Reply-To:Message-ID:MIME-Version:References:Subject:To"; dkim=pass header.s=ppfserpocford header.d=ford.com header.i="@ford.com" header.h="Cc:Content-Transfer-Encoding:Date:From:In-Reply-To:Message-ID:MIME-Version:References:Subject:To"
  • Cc: andrew.cooper3@xxxxxxxxxx, anthony.perard@xxxxxxxxxx, jbeulich@xxxxxxxx, julien@xxxxxxx, michal.orzel@xxxxxxx, roger@xxxxxxxxxxxxxx, sstabellini@xxxxxxxxxx, dmukhin@xxxxxxx, Denis Mukhin <dmukhin@xxxxxxxx>
  • Delivery-date: Thu, 05 Nov 2026 01:38:56 +0000
  • List-id: Xen developer discussion <xen-devel.lists.xenproject.org>
  • Pser-m365-app: SER-APP

From: Denis Mukhin <dmukhin@xxxxxxxx>

Add new module (hvm64 only) to enable measurements aggregation and
printing from the XTF.

Signed-off-by: Denis Mukhin <dmukhin@xxxxxxxx>
---
 build/files.mk           |   1 +
 common/statistics.c      | 364 +++++++++++++++++++++++++++++++++++++++
 include/xtf/statistics.h |  23 +++
 3 files changed, 388 insertions(+)
 create mode 100644 common/statistics.c
 create mode 100644 include/xtf/statistics.h

diff --git a/build/files.mk b/build/files.mk
index 11624a89a21c..8a7d6f8d588f 100644
--- a/build/files.mk
+++ b/build/files.mk
@@ -55,4 +55,5 @@ $(foreach env,$(32BIT_ENVIRONMENTS),$(eval obj-$(env) += 
$(obj-32)))
 
 # 64bit specific objects
 obj-64  += $(ROOT)/arch/x86/entry_64.o
+obj-64  += $(ROOT)/common/statistics.o
 $(foreach env,$(64BIT_ENVIRONMENTS),$(eval obj-$(env) += $(obj-64)))
diff --git a/common/statistics.c b/common/statistics.c
new file mode 100644
index 000000000000..00292d97c8cb
--- /dev/null
+++ b/common/statistics.c
@@ -0,0 +1,364 @@
+#include <xtf/lib.h>
+#include <xtf/statistics.h>
+#include <xtf/time.h>
+#include <xtf.h>
+
+#define HIST_WIDTH 50
+
+static const uint64_t bucket_limit[] = {
+    /* 0 .. 10, step 1. */
+    1ull, 2ull, 3ull, 4ull, 5ull,
+    6ull, 7ull, 8ull, 9ull, 10ull,
+
+    /* 10 .. 100, step 10. */
+    20ull, 30ull, 40ull, 50ull, 60ull,
+    70ull, 80ull, 90ull, 100ull,
+
+    /* 100 .. 500, step 10. */
+    110ull, 120ull, 130ull, 140ull, 150ull,
+    160ull, 170ull, 180ull, 190ull, 200ull,
+    210ull, 220ull, 230ull, 240ull, 250ull,
+    260ull, 270ull, 280ull, 290ull, 300ull,
+    310ull, 320ull, 330ull, 340ull, 350ull,
+    360ull, 370ull, 380ull, 390ull, 400ull,
+    410ull, 420ull, 430ull, 440ull, 450ull,
+    460ull, 470ull, 480ull, 490ull, 500ull,
+
+    /* 500 .. 1000, step 50. */
+    550ull, 600ull, 650ull, 700ull, 750ull,
+    800ull, 850ull, 900ull, 950ull, 1000ull,
+
+    /* 1000 .. 5000, step 100. */
+    1100ull, 1200ull, 1300ull, 1400ull, 1500ull,
+    1600ull, 1700ull, 1800ull, 1900ull, 2000ull,
+    2100ull, 2200ull, 2300ull, 2400ull, 2500ull,
+    2600ull, 2700ull, 2800ull, 2900ull, 3000ull,
+    3100ull, 3200ull, 3300ull, 3400ull, 3500ull,
+    3600ull, 3700ull, 3800ull, 3900ull, 4000ull,
+    4100ull, 4200ull, 4300ull, 4400ull, 4500ull,
+    4600ull, 4700ull, 4800ull, 4900ull, 5000ull,
+
+    /* 5000 .. 10000, step 100. */
+    5100ull, 5200ull, 5300ull, 5400ull, 5500ull,
+    5600ull, 5700ull, 5800ull, 5900ull, 6000ull,
+    6100ull, 6200ull, 6300ull, 6400ull, 6500ull,
+    6600ull, 6700ull, 6800ull, 6900ull, 7000ull,
+    7100ull, 7200ull, 7300ull, 7400ull, 7500ull,
+    7600ull, 7700ull, 7800ull, 7900ull, 8000ull,
+    8100ull, 8200ull, 8300ull, 8400ull, 8500ull,
+    8600ull, 8700ull, 8800ull, 8900ull, 9000ull,
+    9100ull, 9200ull, 9300ull, 9400ull, 9500ull,
+    9600ull, 9700ull, 9800ull, 9900ull, 10000ull,
+
+    /* 10000 .. 100000, step 1000. */
+    11000ull, 12000ull, 13000ull, 14000ull, 15000ull,
+    16000ull, 17000ull, 18000ull, 19000ull, 20000ull,
+    21000ull, 22000ull, 23000ull, 24000ull, 25000ull,
+    26000ull, 27000ull, 28000ull, 29000ull, 30000ull,
+    31000ull, 32000ull, 33000ull, 34000ull, 35000ull,
+    36000ull, 37000ull, 38000ull, 39000ull, 40000ull,
+    41000ull, 42000ull, 43000ull, 44000ull, 45000ull,
+    46000ull, 47000ull, 48000ull, 49000ull, 50000ull,
+    51000ull, 52000ull, 53000ull, 54000ull, 55000ull,
+    56000ull, 57000ull, 58000ull, 59000ull, 60000ull,
+    61000ull, 62000ull, 63000ull, 64000ull, 65000ull,
+    66000ull, 67000ull, 68000ull, 69000ull, 70000ull,
+    71000ull, 72000ull, 73000ull, 74000ull, 75000ull,
+    76000ull, 77000ull, 78000ull, 79000ull, 80000ull,
+    81000ull, 82000ull, 83000ull, 84000ull, 85000ull,
+    86000ull, 87000ull, 88000ull, 89000ull, 90000ull,
+    91000ull, 92000ull, 93000ull, 94000ull, 95000ull,
+    96000ull, 97000ull, 98000ull, 99000ull, 100000ull,
+
+    /* 100000 .. 1000000, step 50000. */
+    150000ull, 200000ull, 250000ull, 300000ull, 350000ull,
+    400000ull, 450000ull, 500000ull, 550000ull, 600000ull,
+    650000ull, 700000ull, 750000ull, 800000ull, 850000ull,
+    900000ull, 950000ull, 1000000ull,
+
+    /* Coarse tail buckets. */
+    10000000ull, 100000000ull, 1000000000ull, ~0ull,
+};
+
+_Static_assert(ARRAY_SIZE(bucket_limit) == NR_BUCKETS,
+               "NR_BUCKETS must match bucket_limit[]");
+
+/* First bucket whose upper limit is strictly greater than sample. */
+static unsigned int find_bucket_gt(uint64_t sample)
+{
+    unsigned int lo = 0, hi = NR_BUCKETS - 1;
+
+    while ( lo < hi )
+    {
+        unsigned int mid = lo + (hi - lo) / 2;
+
+        if ( sample < bucket_limit[mid] )
+            hi = mid;
+        else
+            lo = mid + 1;
+    }
+
+    return lo;
+}
+
+void xtf_add_sample(struct statistics *s, uint64_t sample)
+{
+    if ( sample < s->min )
+        s->min = sample;
+
+    if ( sample > s->max )
+        s->max = sample;
+
+    s->sum += sample;
+    s->count++;
+    s->bucket[find_bucket_gt(sample)]++;
+}
+
+/* Nearest-rank percentile, expressed as a rational number. */
+static unsigned int get_bucket_by_quantile(const struct statistics *s,
+                                           unsigned int numerator,
+                                           unsigned int denominator)
+{
+    uint64_t target, seen = 0;
+    unsigned int i;
+
+    if ( !s->count )
+        return 0;
+
+    target = (s->count / denominator) * numerator;
+    target += ((s->count % denominator) * numerator + denominator - 1) /
+              denominator;
+
+    for ( i = 0; i < NR_BUCKETS; ++i )
+    {
+        seen += s->bucket[i];
+
+        if ( seen >= target )
+            return i;
+    }
+
+    return NR_BUCKETS - 1;
+}
+
+static uint64_t get_bucket_value(unsigned int bucket)
+{
+    /* Last bucket is open-ended; report its lower bound. */
+    if ( bucket >= NR_BUCKETS - 1 )
+        bucket = NR_BUCKETS - 2;
+
+    return bucket_limit[bucket];
+}
+
+/*
+ * Choose units by magnitude while preserving nanosecond precision:
+ *   < 1 us: integer ns
+ *   < 1 ms: us with three fractional digits
+ *  >= 1 ms: ms with six fractional digits
+ *
+ * Select units independently for each histogram boundary.
+ */
+enum time_unit {
+    TIME_NS,
+    TIME_US,
+    TIME_MS,
+};
+
+static enum time_unit get_time_unit(uint64_t ns)
+{
+    if ( ns >= 1000000 )
+        return TIME_MS;
+
+    return ns >= 1000 ? TIME_US : TIME_NS;
+}
+
+/* Fixed-width time field for histogram and summary columns. */
+static void print_time_field(uint64_t ns)
+{
+    switch ( get_time_unit(ns) )
+    {
+    case TIME_NS:
+        printk("%15"PRIu64" ns", ns);
+        break;
+
+    case TIME_US:
+        printk("%11"PRIu64".%03"PRIu64" us",
+               ns / 1000, ns % 1000);
+        break;
+
+    case TIME_MS:
+        printk("%8"PRIu64".%06"PRIu64" ms",
+               ns / 1000000, ns % 1000000);
+        break;
+    }
+}
+
+static void print_metric_value(const char *op, uint64_t cycles)
+{
+    printk("%2s %12"PRIu64"  ", op, cycles);
+    print_time_field(cycles_to_ns(cycles));
+}
+
+void xtf_print_histogram(const struct statistics *s)
+{
+    uint64_t low = 0, hits = 0, peak = 0;
+    unsigned int i, entries = 0;
+
+    for ( i = 0; i < NR_BUCKETS; ++i )
+        if ( s->bucket[i] > peak )
+            peak = s->bucket[i];
+
+    for ( i = 0; i < NR_BUCKETS; ++i )
+    {
+        uint64_t count = s->bucket[i];
+
+        if ( count )
+        {
+            uint64_t pct;
+            unsigned int width, j;
+
+            hits += count;
+            entries++;
+
+            /* Per-bucket percentage rounded to 0.00001%. */
+            pct = (count * 10000000ull + s->count / 2) / s->count;
+
+            /* Round up and show at least one marker for a nonempty bucket. */
+            width = (count * HIST_WIDTH + peak - 1) / peak;
+
+            if ( i == NR_BUCKETS - 1 )
+                printk("  >=%12"PRIu64"%12s", low, "");
+            else
+                printk("%12"PRIu64" .. %12"PRIu64,
+                       low, bucket_limit[i] - 1);
+
+            printk(" | ");
+
+            if ( i == NR_BUCKETS - 1 )
+            {
+                printk("  >=");
+                print_time_field(cycles_to_ns(low));
+                printk("%18s", "");
+            }
+            else
+            {
+                print_time_field(cycles_to_ns(low));
+                printk(" .. ");
+                print_time_field(cycles_to_ns(bucket_limit[i] - 1));
+            }
+
+            printk(" | ");
+
+            for ( j = 0; j < HIST_WIDTH; ++j )
+                printk("%c", j < width ? '#' : ' ');
+
+            printk(" | %3"PRIu64".%05"PRIu64"%% | %10"PRIu64"\n",
+                   pct / 100000, pct % 100000, count);
+        }
+
+        if ( i != NR_BUCKETS - 1 )
+            low = bucket_limit[i];
+    }
+
+    ASSERT( hits == s->count );
+
+    printk("\nTotals:\n");
+    printk("    Hits: %"PRIu64"\n", s->count);
+    printk("    Entries: %u\n", entries);
+    printk("\n");
+}
+
+void xtf_finalize_statistics(struct statistics *s)
+{
+    s->p99 = get_bucket_value(get_bucket_by_quantile(s, 99, 100));
+}
+
+enum metric_id {
+    METRIC_MIN,
+    METRIC_AVG,
+    METRIC_P50,
+    METRIC_P99,
+    METRIC_P999,
+    METRIC_P9999,
+    METRIC_P99999,
+    METRIC_MAX,
+};
+
+static const struct {
+    const char *name;
+    unsigned int numerator, denominator;
+} metric[] = {
+    [METRIC_MIN]    = { "min",     0,     0 },
+    [METRIC_AVG]    = { "avg",     0,     0 },
+    [METRIC_P50]    = { "p50",    50,   100 },
+    [METRIC_P99]    = { "p99",    99,   100 },
+    [METRIC_P999]   = { "p99.9", 999,  1000 },
+    [METRIC_P9999]  = { "p99.99", 9999, 10000 },
+    [METRIC_P99999] = { "p99.999", 99999, 100000 },
+    [METRIC_MAX]    = { "max",     0,     0 },
+};
+
+static void print_metric_column(const struct statistics *s, unsigned int id)
+{
+    uint64_t cycles;
+    const char *op = "";
+
+    if ( !s->count )
+    {
+        printk("%2s %12s  %18s", "", "-", "-");
+        return;
+    }
+
+    if ( id == METRIC_MIN )
+        cycles = s->min;
+    else if ( id == METRIC_AVG )
+        cycles = s->sum / s->count;
+    else if ( id == METRIC_MAX )
+        cycles = s->max;
+    else
+    {
+        unsigned int bucket = get_bucket_by_quantile(
+            s, metric[id].numerator, metric[id].denominator);
+
+        op = bucket == NR_BUCKETS - 1 ? ">=" : "<";
+        cycles = get_bucket_value(bucket);
+    }
+
+    print_metric_value(op, cycles);
+}
+
+void xtf_print_metrics(const struct statistics *s, const struct statistics *t)
+{
+    unsigned int i;
+
+    if ( t )
+    {
+        printk("A: %s (%"PRIu64" samples)\n", s->name, s->count);
+        printk("B: %s (%"PRIu64" samples)\n", t->name, t->count);
+    }
+    else
+        printk("%s (%"PRIu64" samples)\n", s->name, s->count);
+
+    if ( t )
+    {
+        printk("%-8s %2s %12s  %18s", "metric", "", "cycles A", "time A");
+        printk(  " | %2s %12s  %18s",           "", "cycles B", "time B");
+    }
+    else
+        printk("%-8s %2s %12s  %18s", "metric", "", "cycles",   "time");
+
+    printk("\n");
+
+    for ( i = 0; i < ARRAY_SIZE(metric); ++i )
+    {
+        printk("%-8s ", metric[i].name);
+        print_metric_column(s, i);
+
+        if ( t )
+        {
+            printk(" | ");
+            print_metric_column(t, i);
+        }
+
+        printk("\n");
+    }
+}
diff --git a/include/xtf/statistics.h b/include/xtf/statistics.h
new file mode 100644
index 000000000000..e074435ed5ad
--- /dev/null
+++ b/include/xtf/statistics.h
@@ -0,0 +1,23 @@
+#ifndef __XTF_STATISTICS_H_
+#define __XTF_STATISTICS_H_
+
+#include <stdint.h>
+
+#define NR_BUCKETS  271u
+
+struct statistics {
+    uint64_t bucket[NR_BUCKETS];
+    uint64_t min;
+    uint64_t max;
+    uint64_t p99;
+    uint64_t sum;
+    uint64_t count;
+    const char *name;
+};
+
+void xtf_add_sample(struct statistics *s, uint64_t sample);
+void xtf_finalize_statistics(struct statistics *s);
+void xtf_print_histogram(const struct statistics *s);
+void xtf_print_metrics(const struct statistics *s, const struct statistics *t);
+
+#endif /* __XTF_STATISTICS_H_ */
-- 
2.55.0




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.