-
Notifications
You must be signed in to change notification settings - Fork 83
/
Copy pathsequencer_impl.h
133 lines (111 loc) · 5.17 KB
/
sequencer_impl.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#pragma once
#include "envoy/common/pure.h"
#include "envoy/common/time.h"
#include "envoy/event/dispatcher.h"
#include "nighthawk/common/platform_util.h"
#include "nighthawk/common/rate_limiter.h"
#include "nighthawk/common/sequencer.h"
#include "nighthawk/common/statistic.h"
#include "nighthawk/common/termination_predicate.h"
#include "external/envoy/source/common/common/logger.h"
namespace Nighthawk {
namespace {
using namespace std::chrono_literals;
// We shoot for a 40kHz resolution.
constexpr std::chrono::microseconds NighthawkTimerResolution = 25us;
} // namespace
#define ALL_SEQUENCER_STATS(COUNTER) COUNTER(failed_terminations)
struct SequencerStats {
ALL_SEQUENCER_STATS(GENERATE_COUNTER_STRUCT)
};
/**
* The Sequencer will drive calls to the SequencerTarget at a pace indicated by the associated
* RateLimiter. The contract with the target is that it will call the provided callback when it is
* ready. The target will return true if it was able to proceed, or false if a retry is warranted at
* a later time (because of being out of required resources, for example).
* Note that owner of SequencerTarget must outlive the SequencerImpl to avoid use-after-free.
* Also, the Sequencer implementation is a single-shot design. The general usage pattern is:
* SequencerImpl sequencer(...)
* sequencer.start();
* sequencer.waitForCompletion();
*/
class SequencerImpl : public Sequencer, public Envoy::Logger::Loggable<Envoy::Logger::Id::main> {
public:
SequencerImpl(
const PlatformUtil& platform_util, Envoy::Event::Dispatcher& dispatcher,
Envoy::TimeSource& time_source, RateLimiterPtr&& rate_limiter, SequencerTarget target,
StatisticPtr&& latency_statistic, StatisticPtr&& blocked_statistic,
nighthawk::client::SequencerIdleStrategy::SequencerIdleStrategyOptions idle_strategy,
TerminationPredicatePtr&& termination_predicate, Envoy::Stats::Scope& scope);
/**
* Starts the Sequencer. Should be followed up with a call to waitForCompletion().
*/
void start() override;
/**
* Blocking call that waits for the Sequencer flow to terminate. Start() must have been called
* before this.
*/
void waitForCompletion() override;
std::chrono::nanoseconds executionDuration() const override { return rate_limiter_->elapsed(); }
const RateLimiter& rate_limiter() const override { return *rate_limiter_; }
double completionsPerSecond() const override {
const double usec =
std::chrono::duration_cast<std::chrono::microseconds>(executionDuration()).count();
return usec == 0 ? 0 : ((targets_completed_ / usec) * 1000000);
}
StatisticPtrMap statistics() const override;
const Statistic& blockedStatistic() const { return *blocked_statistic_; }
const Statistic& latencyStatistic() const { return *latency_statistic_; }
protected:
/**
* Run is called initially by start() and thereafter by two timers:
* - a periodic one running at a 1 ms resolution (the current minimum)
* - one to spin on calls to run().
*
* Spinning is performed when the Sequencer implementation considers itself idle, where "idle" is
* defined as:
* - All benchmark target calls have reported back
* - Either the rate limiter or the benchmark target is prohibiting initiation of the next
* benchmark target call.
*
* The spinning is performed to improve timelyness when initiating
* calls to the benchmark targets, and observational data also shows significant improvement of
* actually latency measurement (most pronounced on non-tuned systems). As a side-effect, spinning
* keeps the CPU busy, preventing C-state frequency changes. Systems with appropriately cooled
* processors should not be impacted by thermal throttling. When thermal throttling does occur, it
* makes sense to first warm up the system to get it into a steady state regarding processor
* frequency.
*
* For more context on the current implementation of how we spin, see the the review discussion:
* https://github.com/envoyproxy/envoy-perf/pull/49#discussion_r259133387
*
* @param from_periodic_timer Indicates if we this is called from the periodic timer.
* Used to determine if re-enablement of the periodic timer should be performed before returning.
*/
void run(bool from_periodic_timer);
void scheduleRun();
void stop(bool timed_out);
void unblockAndUpdateStatisticIfNeeded(const Envoy::MonotonicTime& now);
void updateStartBlockingTimeIfNeeded();
private:
SequencerTarget target_;
const PlatformUtil& platform_util_;
Envoy::Event::Dispatcher& dispatcher_;
Envoy::TimeSource& time_source_;
std::unique_ptr<RateLimiter> rate_limiter_;
StatisticPtr latency_statistic_;
StatisticPtr blocked_statistic_;
Envoy::Event::TimerPtr periodic_timer_;
Envoy::Event::TimerPtr spin_timer_;
uint64_t targets_initiated_{0};
uint64_t targets_completed_{0};
bool running_{};
bool blocked_{};
Envoy::MonotonicTime blocked_start_;
nighthawk::client::SequencerIdleStrategy::SequencerIdleStrategyOptions idle_strategy_;
TerminationPredicatePtr termination_predicate_;
TerminationPredicate::Status last_termination_status_;
Envoy::Stats::ScopeSharedPtr scope_;
SequencerStats sequencer_stats_;
};
} // namespace Nighthawk