All of lore.kernel.org
 help / color / mirror / Atom feed
From: Stefan Hajnoczi <stefanha@redhat.com>
To: qemu-devel@nongnu.org
Cc: Peter Maydell <peter.maydell@linaro.org>,
	Alberto Garcia <berto@igalia.com>,
	Stefan Hajnoczi <stefanha@redhat.com>
Subject: [Qemu-devel] [PULL 27/44] util: Infrastructure for computing recent averages
Date: Tue, 10 Nov 2015 14:14:22 +0000	[thread overview]
Message-ID: <1447164879-6756-28-git-send-email-stefanha@redhat.com> (raw)
In-Reply-To: <1447164879-6756-1-git-send-email-stefanha@redhat.com>

From: Alberto Garcia <berto@igalia.com>

This module computes the average of a set of values within a time
window, keeping also track of the minimum and maximum values.

In order to produce more accurate results it works internally by
creating two time windows of the same period, offsetted by half of
that period. Values are accounted on both windows and the data is
always returned from the oldest one.

[Add missing util/replay.o to test-timed-average dependencies to fix the
build.
--Stefan]

Signed-off-by: Alberto Garcia <berto@igalia.com>
Message-id: 201b09c21bbc9c329779d2b2365ee2b9c80dceeb.1446044837.git.berto@igalia.com
Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 include/qemu/timed-average.h |  63 +++++++++++++
 tests/Makefile               |   4 +
 tests/test-timed-average.c   |  90 +++++++++++++++++++
 util/Makefile.objs           |   1 +
 util/timed-average.c         | 210 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 368 insertions(+)
 create mode 100644 include/qemu/timed-average.h
 create mode 100644 tests/test-timed-average.c
 create mode 100644 util/timed-average.c

diff --git a/include/qemu/timed-average.h b/include/qemu/timed-average.h
new file mode 100644
index 0000000..f1cdddc
--- /dev/null
+++ b/include/qemu/timed-average.h
@@ -0,0 +1,63 @@
+/*
+ * QEMU timed average computation
+ *
+ * Copyright (C) Nodalink, EURL. 2014
+ * Copyright (C) Igalia, S.L. 2015
+ *
+ * Authors:
+ *   Benoît Canet <benoit.canet@nodalink.com>
+ *   Alberto Garcia <berto@igalia.com>
+ *
+ * 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) version 3 or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TIMED_AVERAGE_H
+#define TIMED_AVERAGE_H
+
+#include <stdint.h>
+
+#include "qemu/timer.h"
+
+typedef struct TimedAverageWindow TimedAverageWindow;
+typedef struct TimedAverage TimedAverage;
+
+/* All fields of both structures are private */
+
+struct TimedAverageWindow {
+    uint64_t      min;             /* minimum value accounted in the window */
+    uint64_t      max;             /* maximum value accounted in the window */
+    uint64_t      sum;             /* sum of all values */
+    uint64_t      count;           /* number of values */
+    int64_t       expiration;      /* the end of the current window in ns */
+};
+
+struct TimedAverage {
+    uint64_t           period;     /* period in nanoseconds */
+    TimedAverageWindow windows[2]; /* two overlapping windows of with
+                                    * an offset of period / 2 between them */
+    unsigned           current;    /* the current window index: it's also the
+                                    * oldest window index */
+    QEMUClockType      clock_type; /* the clock used */
+};
+
+void timed_average_init(TimedAverage *ta, QEMUClockType clock_type,
+                        uint64_t period);
+
+void timed_average_account(TimedAverage *ta, uint64_t value);
+
+uint64_t timed_average_min(TimedAverage *ta);
+uint64_t timed_average_avg(TimedAverage *ta);
+uint64_t timed_average_max(TimedAverage *ta);
+
+#endif
diff --git a/tests/Makefile b/tests/Makefile
index 6a6b1fc..90c4141 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -83,6 +83,7 @@ check-unit-y += tests/test-crypto-cipher$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlscredsx509$(EXESUF)
 check-unit-$(CONFIG_GNUTLS) += tests/test-crypto-tlssession$(EXESUF)
 check-unit-$(CONFIG_LINUX) += tests/test-qga$(EXESUF)
+check-unit-y += tests/test-timed-average$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -412,6 +413,9 @@ tests/test-vmstate$(EXESUF): tests/test-vmstate.o \
 	migration/vmstate.o migration/qemu-file.o migration/qemu-file-buf.o \
         migration/qemu-file-unix.o qjson.o \
 	$(test-qom-obj-y)
+tests/test-timed-average$(EXESUF): tests/test-timed-average.o qemu-timer.o \
+	libqemuutil.a stubs/clock-warp.o stubs/cpu-get-icount.o \
+	stubs/notify-event.o stubs/replay.o
 
 tests/test-qapi-types.c tests/test-qapi-types.h :\
 $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
diff --git a/tests/test-timed-average.c b/tests/test-timed-average.c
new file mode 100644
index 0000000..a049799
--- /dev/null
+++ b/tests/test-timed-average.c
@@ -0,0 +1,90 @@
+/*
+ * Timed average computation tests
+ *
+ * Copyright Nodalink, EURL. 2014
+ *
+ * Authors:
+ *  Benoît Canet     <benoit.canet@nodalink.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ */
+
+#include <glib.h>
+#include <unistd.h>
+
+#include "qemu/timed-average.h"
+
+/* This is the clock for QEMU_CLOCK_VIRTUAL */
+static int64_t my_clock_value;
+
+int64_t cpu_get_clock(void)
+{
+    return my_clock_value;
+}
+
+static void account(TimedAverage *ta)
+{
+    timed_average_account(ta, 1);
+    timed_average_account(ta, 5);
+    timed_average_account(ta, 2);
+    timed_average_account(ta, 4);
+    timed_average_account(ta, 3);
+}
+
+static void test_average(void)
+{
+    TimedAverage ta;
+    uint64_t result;
+    int i;
+
+    /* we will compute some average on a period of 1 second */
+    timed_average_init(&ta, QEMU_CLOCK_VIRTUAL, NANOSECONDS_PER_SECOND);
+
+    result = timed_average_min(&ta);
+    g_assert(result == 0);
+    result = timed_average_avg(&ta);
+    g_assert(result == 0);
+    result = timed_average_max(&ta);
+    g_assert(result == 0);
+
+    for (i = 0; i < 100; i++) {
+        account(&ta);
+        result = timed_average_min(&ta);
+        g_assert(result == 1);
+        result = timed_average_avg(&ta);
+        g_assert(result == 3);
+        result = timed_average_max(&ta);
+        g_assert(result == 5);
+        my_clock_value += NANOSECONDS_PER_SECOND / 10;
+    }
+
+    my_clock_value += NANOSECONDS_PER_SECOND * 100;
+
+    result = timed_average_min(&ta);
+    g_assert(result == 0);
+    result = timed_average_avg(&ta);
+    g_assert(result == 0);
+    result = timed_average_max(&ta);
+    g_assert(result == 0);
+
+    for (i = 0; i < 100; i++) {
+        account(&ta);
+        result = timed_average_min(&ta);
+        g_assert(result == 1);
+        result = timed_average_avg(&ta);
+        g_assert(result == 3);
+        result = timed_average_max(&ta);
+        g_assert(result == 5);
+        my_clock_value += NANOSECONDS_PER_SECOND / 10;
+    }
+}
+
+int main(int argc, char **argv)
+{
+    /* tests in the same order as the header function declarations */
+    g_test_init(&argc, &argv, NULL);
+    g_test_add_func("/timed-average/average", test_average);
+    return g_test_run();
+}
+
diff --git a/util/Makefile.objs b/util/Makefile.objs
index d7cc399..89dd80e 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -29,3 +29,4 @@ util-obj-y += qemu-coroutine.o qemu-coroutine-lock.o qemu-coroutine-io.o
 util-obj-y += qemu-coroutine-sleep.o
 util-obj-y += coroutine-$(CONFIG_COROUTINE_BACKEND).o
 util-obj-y += buffer.o
+util-obj-y += timed-average.o
diff --git a/util/timed-average.c b/util/timed-average.c
new file mode 100644
index 0000000..98a1170
--- /dev/null
+++ b/util/timed-average.c
@@ -0,0 +1,210 @@
+/*
+ * QEMU timed average computation
+ *
+ * Copyright (C) Nodalink, EURL. 2014
+ * Copyright (C) Igalia, S.L. 2015
+ *
+ * Authors:
+ *   Benoît Canet <benoit.canet@nodalink.com>
+ *   Alberto Garcia <berto@igalia.com>
+ *
+ * This program is free sofware: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Sofware Foundation, either version 2 of the License, or
+ * (at your option) version 3 or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include "qemu/timed-average.h"
+
+/* This module computes an average of a set of values within a time
+ * window.
+ *
+ * Algorithm:
+ *
+ * - Create two windows with a certain expiration period, and
+ *   offsetted by period / 2.
+ * - Each time you want to account a new value, do it in both windows.
+ * - The minimum / maximum / average values are always returned from
+ *   the oldest window.
+ *
+ * Example:
+ *
+ *        t=0          |t=0.5           |t=1          |t=1.5            |t=2
+ *        wnd0: [0,0.5)|wnd0: [0.5,1.5) |             |wnd0: [1.5,2.5)  |
+ *        wnd1: [0,1)  |                |wnd1: [1,2)  |                 |
+ *
+ * Values are returned from:
+ *
+ *        wnd0---------|wnd1------------|wnd0---------|wnd1-------------|
+ */
+
+/* Update the expiration of a time window
+ *
+ * @w:      the window used
+ * @now:    the current time in nanoseconds
+ * @period: the expiration period in nanoseconds
+ */
+static void update_expiration(TimedAverageWindow *w, int64_t now,
+                              int64_t period)
+{
+    /* time elapsed since the last theoretical expiration */
+    int64_t elapsed = (now - w->expiration) % period;
+    /* time remaininging until the next expiration */
+    int64_t remaining = period - elapsed;
+    /* compute expiration */
+    w->expiration = now + remaining;
+}
+
+/* Reset a window
+ *
+ * @w: the window to reset
+ */
+static void window_reset(TimedAverageWindow *w)
+{
+    w->min = UINT64_MAX;
+    w->max = 0;
+    w->sum = 0;
+    w->count = 0;
+}
+
+/* Get the current window (that is, the one with the earliest
+ * expiration time).
+ *
+ * @ta:  the TimedAverage structure
+ * @ret: a pointer to the current window
+ */
+static TimedAverageWindow *current_window(TimedAverage *ta)
+{
+     return &ta->windows[ta->current];
+}
+
+/* Initialize a TimedAverage structure
+ *
+ * @ta:         the TimedAverage structure
+ * @clock_type: the type of clock to use
+ * @period:     the time window period in nanoseconds
+ */
+void timed_average_init(TimedAverage *ta, QEMUClockType clock_type,
+                        uint64_t period)
+{
+    int64_t now = qemu_clock_get_ns(clock_type);
+
+    /* Returned values are from the oldest window, so they belong to
+     * the interval [ta->period/2,ta->period). By adjusting the
+     * requested period by 4/3, we guarantee that they're in the
+     * interval [2/3 period,4/3 period), closer to the requested
+     * period on average */
+    ta->period = (uint64_t) period * 4 / 3;
+    ta->clock_type = clock_type;
+    ta->current = 0;
+
+    window_reset(&ta->windows[0]);
+    window_reset(&ta->windows[1]);
+
+    /* Both windows are offsetted by half a period */
+    ta->windows[0].expiration = now + ta->period / 2;
+    ta->windows[1].expiration = now + ta->period;
+}
+
+/* Check if the time windows have expired, updating their counters and
+ * expiration time if that's the case.
+ *
+ * @ta: the TimedAverage structure
+ */
+static void check_expirations(TimedAverage *ta)
+{
+    int64_t now = qemu_clock_get_ns(ta->clock_type);
+    int i;
+
+    assert(ta->period != 0);
+
+    /* Check if the windows have expired */
+    for (i = 0; i < 2; i++) {
+        TimedAverageWindow *w = &ta->windows[i];
+        if (w->expiration <= now) {
+            window_reset(w);
+            update_expiration(w, now, ta->period);
+        }
+    }
+
+    /* Make ta->current point to the oldest window */
+    if (ta->windows[0].expiration < ta->windows[1].expiration) {
+        ta->current = 0;
+    } else {
+        ta->current = 1;
+    }
+}
+
+/* Account a value
+ *
+ * @ta:    the TimedAverage structure
+ * @value: the value to account
+ */
+void timed_average_account(TimedAverage *ta, uint64_t value)
+{
+    int i;
+    check_expirations(ta);
+
+    /* Do the accounting in both windows at the same time */
+    for (i = 0; i < 2; i++) {
+        TimedAverageWindow *w = &ta->windows[i];
+
+        w->sum += value;
+        w->count++;
+
+        if (value < w->min) {
+            w->min = value;
+        }
+
+        if (value > w->max) {
+            w->max = value;
+        }
+    }
+}
+
+/* Get the minimum value
+ *
+ * @ta:  the TimedAverage structure
+ * @ret: the minimum value
+ */
+uint64_t timed_average_min(TimedAverage *ta)
+{
+    TimedAverageWindow *w;
+    check_expirations(ta);
+    w = current_window(ta);
+    return w->min < UINT64_MAX ? w->min : 0;
+}
+
+/* Get the average value
+ *
+ * @ta:  the TimedAverage structure
+ * @ret: the average value
+ */
+uint64_t timed_average_avg(TimedAverage *ta)
+{
+    TimedAverageWindow *w;
+    check_expirations(ta);
+    w = current_window(ta);
+    return w->count > 0 ? w->sum / w->count : 0;
+}
+
+/* Get the maximum value
+ *
+ * @ta:  the TimedAverage structure
+ * @ret: the maximum value
+ */
+uint64_t timed_average_max(TimedAverage *ta)
+{
+    check_expirations(ta);
+    return current_window(ta)->max;
+}
-- 
2.5.0

  parent reply	other threads:[~2015-11-10 14:15 UTC|newest]

Thread overview: 50+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-11-10 14:13 [Qemu-devel] [PULL 00/44] Block patches Stefan Hajnoczi
2015-11-10 14:13 ` [Qemu-devel] [PULL 01/44] block: Add more types for tracked request Stefan Hajnoczi
2015-11-10 14:13 ` [Qemu-devel] [PULL 02/44] block: Track flush requests Stefan Hajnoczi
2015-11-10 14:13 ` [Qemu-devel] [PULL 03/44] block: Track discard requests Stefan Hajnoczi
2015-11-10 14:13 ` [Qemu-devel] [PULL 04/44] iscsi: Emulate commands in iscsi_aio_ioctl as iscsi_ioctl Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 05/44] block: Add ioctl parameter fields to BlockRequest Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 06/44] block: Emulate bdrv_ioctl with bdrv_aio_ioctl and track both Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 07/44] block: Drop BlockDriver.bdrv_ioctl Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 08/44] block: Introduce BlockDriver.bdrv_drain callback Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 09/44] qed: Implement .bdrv_drain Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 10/44] qapi: Add transaction support to block-dirty-bitmap operations Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 11/44] iotests: add transactional incremental backup test Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 12/44] block: rename BlkTransactionState and BdrvActionOps Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 13/44] backup: Extract dirty bitmap handling as a separate function Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 14/44] blockjob: Introduce reference count and fix reference to job->bs Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 15/44] blockjob: Add .commit and .abort block job actions Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 16/44] blockjob: Add "completed" and "ret" in BlockJob Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 17/44] blockjob: Simplify block_job_finish_sync Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 18/44] block: Add block job transactions Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 19/44] block/backup: Rely on commit/abort for cleanup Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 20/44] block: Add BlockJobTxn support to backup_run Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 21/44] block: add transactional properties Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 22/44] iotests: 124 - transactional failure test Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 23/44] tests: add BlockJobTxn unit test Stefan Hajnoczi
2015-11-12 18:26   ` Eric Blake
2015-11-10 14:14 ` [Qemu-devel] [PULL 24/44] xen_disk: Account for flush operations Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 25/44] ide: Account for write operations correctly Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 26/44] block: define 'clock_type' for the accounting code Stefan Hajnoczi
2015-11-10 14:14 ` Stefan Hajnoczi [this message]
2015-11-10 14:14 ` [Qemu-devel] [PULL 28/44] block: Add idle_time_ns to BlockDeviceStats Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 29/44] block: Add statistics for failed and invalid I/O operations Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 30/44] block: Allow configuring whether to account failed and invalid ops Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 31/44] block: Compute minimum, maximum and average I/O latencies Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 32/44] block: Add average I/O queue depth to BlockDeviceTimedStats Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 33/44] block: New option to define the intervals for collecting I/O statistics Stefan Hajnoczi
2015-11-10 17:23   ` Eric Blake
2015-11-10 18:49     ` Markus Armbruster
2015-11-11 11:10     ` Alberto Garcia
2015-11-10 14:14 ` [Qemu-devel] [PULL 34/44] qemu-io: Account for failed, invalid and flush operations Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 35/44] block: Use QEMU_CLOCK_VIRTUAL for the accounting code in qtest mode Stefan Hajnoczi
2015-11-10 15:08   ` Paolo Bonzini
2015-11-10 14:14 ` [Qemu-devel] [PULL 36/44] iotests: Add test for the block device statistics Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 37/44] nvme: Account for failed and invalid operations Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 38/44] virtio-blk: " Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 39/44] xen_disk: " Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 40/44] atapi: " Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 41/44] ide: " Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 42/44] macio: Account for failed operations Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 43/44] scsi-disk: " Stefan Hajnoczi
2015-11-10 14:14 ` [Qemu-devel] [PULL 44/44] block: Update copyright of the accounting code Stefan Hajnoczi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1447164879-6756-28-git-send-email-stefanha@redhat.com \
    --to=stefanha@redhat.com \
    --cc=berto@igalia.com \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.