diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2021-10-21 12:48:00 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-21 12:48:00 +0200 |
commit | 9abe019606f2367b05e4e13d796de65dddf7c449 (patch) | |
tree | 7d4046f32237badc487b9f341ef79d67e363f782 | |
parent | ea1b8716866e78a9485c10486e55ea8dc7e75590 (diff) | |
parent | 28e6bf435392ac410b82e059cc2bdcb61418a184 (diff) |
Merge pull request #19674 from vespa-engine/havardpe/idle-tracking
add idle tracking helper classes
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; + } +}; + +} |