summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2021-10-21 12:48:00 +0200
committerGitHub <noreply@github.com>2021-10-21 12:48:00 +0200
commit9abe019606f2367b05e4e13d796de65dddf7c449 (patch)
tree7d4046f32237badc487b9f341ef79d67e363f782
parentea1b8716866e78a9485c10486e55ea8dc7e75590 (diff)
parent28e6bf435392ac410b82e059cc2bdcb61418a184 (diff)
Merge pull request #19674 from vespa-engine/havardpe/idle-tracking
add idle tracking helper classes
-rw-r--r--vespalib/CMakeLists.txt1
-rw-r--r--vespalib/src/tests/executor_idle_tracking/CMakeLists.txt9
-rw-r--r--vespalib/src/tests/executor_idle_tracking/executor_idle_tracking_test.cpp79
-rw-r--r--vespalib/src/vespa/vespalib/util/CMakeLists.txt1
-rw-r--r--vespalib/src/vespa/vespalib/util/executor_idle_tracking.cpp6
-rw-r--r--vespalib/src/vespa/vespalib/util/executor_idle_tracking.h79
6 files changed, 175 insertions, 0 deletions
diff --git a/vespalib/CMakeLists.txt b/vespalib/CMakeLists.txt
index 472696131d2..6acac5d3bf5 100644
--- a/vespalib/CMakeLists.txt
+++ b/vespalib/CMakeLists.txt
@@ -55,6 +55,7 @@ vespa_define_module(
src/tests/eventbarrier
src/tests/exception_classes
src/tests/executor
+ src/tests/executor_idle_tracking
src/tests/explore_modern_cpp
src/tests/false
src/tests/fiddle
diff --git a/vespalib/src/tests/executor_idle_tracking/CMakeLists.txt b/vespalib/src/tests/executor_idle_tracking/CMakeLists.txt
new file mode 100644
index 00000000000..5ad8f33db91
--- /dev/null
+++ b/vespalib/src/tests/executor_idle_tracking/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vespalib_executor_idle_tracking_test_app TEST
+ SOURCES
+ executor_idle_tracking_test.cpp
+ DEPENDS
+ vespalib
+ GTest::GTest
+)
+vespa_add_test(NAME vespalib_executor_idle_tracking_test_app COMMAND vespalib_executor_idle_tracking_test_app)
diff --git a/vespalib/src/tests/executor_idle_tracking/executor_idle_tracking_test.cpp b/vespalib/src/tests/executor_idle_tracking/executor_idle_tracking_test.cpp
new file mode 100644
index 00000000000..42faebe45f7
--- /dev/null
+++ b/vespalib/src/tests/executor_idle_tracking/executor_idle_tracking_test.cpp
@@ -0,0 +1,79 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/vespalib/util/executor_idle_tracking.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using namespace vespalib;
+
+steady_time at(duration d) { return steady_time(d); }
+
+TEST(ThreadIdleTrackingTest, can_track_idle_time) {
+ ThreadIdleTracker state;
+ EXPECT_FALSE(state.is_idle()); // starts in active state
+ state.set_idle(at(50ms));
+ EXPECT_TRUE(state.is_idle());
+ EXPECT_EQ(count_ms(state.set_active(at(65ms))), 15);
+ EXPECT_FALSE(state.is_idle());
+ state.set_idle(at(100ms));
+ EXPECT_TRUE(state.is_idle());
+ EXPECT_EQ(count_ms(state.set_active(at(150ms))), 50);
+ EXPECT_FALSE(state.is_idle());
+}
+
+TEST(ThreadIdleTrackingTest, redundant_set_idle_is_handled) {
+ ThreadIdleTracker state;
+ state.set_idle(at(50ms));
+ state.set_idle(at(100ms));
+ EXPECT_TRUE(state.is_idle());
+ EXPECT_EQ(count_ms(state.set_active(at(150ms))), 100);
+}
+
+TEST(ThreadIdleTrackingTest, redundant_set_active_is_handled) {
+ ThreadIdleTracker state;
+ state.set_idle(at(50ms));
+ EXPECT_EQ(count_ms(state.set_active(at(150ms))), 100);
+ EXPECT_EQ(count_ms(state.set_active(at(200ms))), 0);
+ EXPECT_FALSE(state.is_idle());
+}
+
+TEST(ThreadIdleTrackingTest, reset_consumes_idle_time_when_idle) {
+ ThreadIdleTracker state;
+ state.set_idle(at(50ms));
+ EXPECT_EQ(count_ms(state.reset(at(100ms))), 50);
+ EXPECT_TRUE(state.is_idle());
+ EXPECT_EQ(count_ms(state.set_active(at(150ms))), 50);
+}
+
+TEST(ThreadIdleTrackingTest, reset_does_nothing_when_active) {
+ ThreadIdleTracker state;
+ EXPECT_EQ(count_ms(state.reset(at(100ms))), 0);
+ EXPECT_FALSE(state.is_idle());
+}
+
+TEST(ExecutorIdleTrackingTest, can_calculate_idle_metric) {
+ ExecutorIdleTracker state(at(100ms));
+ state.was_idle(20ms);
+ state.was_idle(5ms);
+ state.was_idle(15ms);
+ state.was_idle(3ms);
+ state.was_idle(7ms); // 50 ms total idle
+ EXPECT_EQ(state.reset(at(120ms), 5), 0.5); // 100 ms total time
+ EXPECT_EQ(state.reset(at(140ms), 5), 0.0);
+ state.was_idle(25ms);
+ EXPECT_EQ(state.reset(at(160ms), 5), 0.25);
+}
+
+TEST(ExecutorIdleTrackingTest, avoids_idle_above_1) {
+ ExecutorIdleTracker state(at(100ms));
+ state.was_idle(100ms);
+ EXPECT_EQ(state.reset(at(110ms), 1), 1.0);
+}
+
+TEST(ExecutorIdleTrackingTest, avoids_division_by_zero) {
+ ExecutorIdleTracker state(at(100ms));
+ EXPECT_EQ(state.reset(at(100ms), 1), 0.0);
+ state.was_idle(10ms);
+ EXPECT_EQ(state.reset(at(100ms), 1), 1.0);
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/vespa/vespalib/util/CMakeLists.txt b/vespalib/src/vespa/vespalib/util/CMakeLists.txt
index 6115c21623d..75ea02d448e 100644
--- a/vespalib/src/vespa/vespalib/util/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/util/CMakeLists.txt
@@ -23,6 +23,7 @@ vespa_add_library(vespalib_vespalib_util OBJECT
error.cpp
exception.cpp
exceptions.cpp
+ executor_idle_tracking.cpp
file_area_freelist.cpp
gencnt.cpp
generationhandler.cpp
diff --git a/vespalib/src/vespa/vespalib/util/executor_idle_tracking.cpp b/vespalib/src/vespa/vespalib/util/executor_idle_tracking.cpp
new file mode 100644
index 00000000000..2e3c98c872f
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/util/executor_idle_tracking.cpp
@@ -0,0 +1,6 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "executor_idle_tracking.h"
+
+namespace vespa {
+}
diff --git a/vespalib/src/vespa/vespalib/util/executor_idle_tracking.h b/vespalib/src/vespa/vespalib/util/executor_idle_tracking.h
new file mode 100644
index 00000000000..f85680b9726
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/util/executor_idle_tracking.h
@@ -0,0 +1,79 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "time.h"
+
+// Used by various executors to adjust the utilization number reported
+// in ExecutorStats. How to use (also see unit test):
+//
+// (0) Remember that these classes are not thread-safe themselves
+//
+// (1) Keep one ThreadIdleTracker per worker thread and one
+// ExecutorIdleTracker in the executor itself. Note that the
+// ExecutorIdleTracker needs the current time as a constructor
+// parameter.
+//
+// (2) Each time a worker thread is blocked; call set_idle with the
+// current time right before blocking and call set_active with the
+// current time right after waking up again. Pass the result from
+// the set_active function to the was_idle function.
+//
+// (3) Each time stats are sampled, start by sampling the current
+// time. Then call ThreadIdleTracker::reset for (at least) all
+// blocked worker threads and pass the results to the was_idle
+// function. Then call ExecutorIdleTracker::reset with the current
+// time and the number of threads as parameters. Subtract this
+// result from the utilization of the stats to be reported.
+
+namespace vespalib {
+
+class ThreadIdleTracker {
+private:
+ steady_time _idle_tag = steady_time::min();
+public:
+ bool is_idle() const { return (_idle_tag != steady_time::min()); }
+ void set_idle(steady_time t) {
+ if (!is_idle()) {
+ _idle_tag = t;
+ }
+ }
+ duration set_active(steady_time t) {
+ if (is_idle()) {
+ duration how_long_idle = (t - _idle_tag);
+ _idle_tag = steady_time::min();
+ return how_long_idle;
+ } else {
+ return duration::zero();
+ }
+ }
+ duration reset(steady_time t) {
+ if (is_idle()) {
+ duration how_long_idle = (t - _idle_tag);
+ _idle_tag = t;
+ return how_long_idle;
+ } else {
+ return duration::zero();
+ }
+ }
+};
+
+class ExecutorIdleTracker {
+private:
+ steady_time _start;
+ duration _total_idle = duration::zero();
+public:
+ ExecutorIdleTracker(steady_time t) : _start(t) {}
+ void was_idle(duration how_long_idle) {
+ _total_idle += how_long_idle;
+ }
+ double reset(steady_time t, size_t num_threads) {
+ double idle = count_ns(_total_idle);
+ double elapsed = std::max(idle, double(count_ns((t - _start) * num_threads)));
+ _start = t;
+ _total_idle = duration::zero();
+ return (elapsed > 0) ? (idle / elapsed) : 0.0;
+ }
+};
+
+}