1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
5 * Copyright (C) 2017 Red Hat Inc.
7 * Author: J. Eric Ivancich <ivancich@redhat.com>
9 * This is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License version
11 * 2.1, as published by the Free Software Foundation. See file
23 #include <condition_variable>
25 #include "../support/src/run_every.h"
26 #include "dmclock_util.h"
27 #include "dmclock_recs.h"
33 // OrigTracker is a best-effort implementation of the the original
34 // dmClock calculations of delta and rho. It adheres to an
35 // interface, implemented via a template type, that allows it to
36 // be replaced with an alternative. The interface consists of the
37 // static create, prepare_req, resp_update, and get_last_delta
40 Counter delta_prev_req
;
47 OrigTracker(Counter global_delta
,
49 delta_prev_req(global_delta
),
50 rho_prev_req(global_rho
),
55 static inline OrigTracker
create(Counter the_delta
, Counter the_rho
) {
56 return OrigTracker(the_delta
, the_rho
);
59 inline ReqParams
prepare_req(Counter
& the_delta
, Counter
& the_rho
) {
60 Counter delta_out
= the_delta
- delta_prev_req
- my_delta
;
61 Counter rho_out
= the_rho
- rho_prev_req
- my_rho
;
62 delta_prev_req
= the_delta
;
63 rho_prev_req
= the_rho
;
66 return ReqParams(uint32_t(delta_out
), uint32_t(rho_out
));
69 inline void resp_update(PhaseType phase
,
75 if (phase
== PhaseType::reservation
) {
81 inline Counter
get_last_delta() const {
82 return delta_prev_req
;
84 }; // struct OrigTracker
87 // BorrowingTracker always returns a positive delta and rho. If
88 // not enough responses have come in to allow that, we will borrow
89 // a future response and repay it later.
90 class BorrowingTracker
{
91 Counter delta_prev_req
;
98 BorrowingTracker(Counter global_delta
, Counter global_rho
) :
99 delta_prev_req(global_delta
),
100 rho_prev_req(global_rho
),
105 static inline BorrowingTracker
create(Counter the_delta
,
107 return BorrowingTracker(the_delta
, the_rho
);
110 inline Counter
calc_with_borrow(const Counter
& global
,
111 const Counter
& previous
,
113 Counter result
= global
- previous
;
115 // if no replies have come in, borrow one from the future
118 } else if (result
> borrow
) {
119 // if we can give back all of what we borrowed, do so
124 // can only return part of what was borrowed in order to
126 borrow
= borrow
- result
+ 1;
131 inline ReqParams
prepare_req(Counter
& the_delta
, Counter
& the_rho
) {
133 calc_with_borrow(the_delta
, delta_prev_req
, delta_borrow
);
135 calc_with_borrow(the_rho
, rho_prev_req
, rho_borrow
);
136 delta_prev_req
= the_delta
;
137 rho_prev_req
= the_rho
;
138 return ReqParams(uint32_t(delta_out
), uint32_t(rho_out
));
141 inline void resp_update(PhaseType phase
,
146 if (phase
== PhaseType::reservation
) {
151 inline Counter
get_last_delta() const {
152 return delta_prev_req
;
154 }; // struct BorrowingTracker
158 * S is server identifier type
160 * T is the server info class that adheres to ServerTrackerIfc
163 template<typename S
, typename T
= OrigTracker
>
164 class ServiceTracker
{
165 // we don't want to include gtest.h just for FRIEND_TEST
166 friend class dmclock_client_server_erase_Test
;
168 using TimePoint
= decltype(std::chrono::steady_clock::now());
169 using Duration
= std::chrono::milliseconds
;
170 using MarkPoint
= std::pair
<TimePoint
,Counter
>;
172 Counter delta_counter
; // # reqs completed
173 Counter rho_counter
; // # reqs completed via reservation
174 std::map
<S
,T
> server_map
;
175 mutable std::mutex data_mtx
; // protects Counters and map
177 using DataGuard
= std::lock_guard
<decltype(data_mtx
)>;
181 std::deque
<MarkPoint
> clean_mark_points
;
182 Duration clean_age
; // age at which server tracker cleaned
184 // NB: All threads declared at end, so they're destructed firs!
186 std::unique_ptr
<RunEvery
> cleaning_job
;
191 // we have to start the counters at 1, as 0 is used in the
193 template<typename Rep
, typename Per
>
194 ServiceTracker(std::chrono::duration
<Rep
,Per
> _clean_every
,
195 std::chrono::duration
<Rep
,Per
> _clean_age
) :
198 clean_age(std::chrono::duration_cast
<Duration
>(_clean_age
))
201 std::unique_ptr
<RunEvery
>(
202 new RunEvery(_clean_every
,
203 std::bind(&ServiceTracker::do_clean
, this)));
207 // the reason we're overloading the constructor rather than
208 // using default values for the arguments is so that callers
209 // have to either use all defaults or specify all timings; with
210 // default arguments they could specify some without others
212 ServiceTracker(std::chrono::minutes(5), std::chrono::minutes(10))
219 * Incorporates the response data received into the counters.
221 void track_resp(const S
& server_id
,
222 const PhaseType
& phase
,
223 Counter request_cost
= 1u) {
224 DataGuard
g(data_mtx
);
226 auto it
= server_map
.find(server_id
);
227 if (server_map
.end() == it
) {
228 // this code can only run if a request did not precede the
229 // response or if the record was cleaned up b/w when
230 // the request was made and now
231 auto i
= server_map
.emplace(server_id
,
232 T::create(delta_counter
, rho_counter
));
235 it
->second
.resp_update(phase
, delta_counter
, rho_counter
, request_cost
);
239 * Returns the ReqParams for the given server.
241 ReqParams
get_req_params(const S
& server
) {
242 DataGuard
g(data_mtx
);
243 auto it
= server_map
.find(server
);
244 if (server_map
.end() == it
) {
245 server_map
.emplace(server
,
246 T::create(delta_counter
, rho_counter
));
247 return ReqParams(1, 1);
249 return it
->second
.prepare_req(delta_counter
, rho_counter
);
256 * This is being called regularly by RunEvery. Every time it's
257 * called it notes the time and delta counter (mark point) in a
258 * deque. It also looks at the deque to find the most recent
259 * mark point that is older than clean_age. It then walks the
260 * map and delete all server entries that were last used before
264 TimePoint now
= std::chrono::steady_clock::now();
265 DataGuard
g(data_mtx
);
266 clean_mark_points
.emplace_back(MarkPoint(now
, delta_counter
));
268 Counter earliest
= 0;
269 auto point
= clean_mark_points
.front();
270 while (point
.first
<= now
- clean_age
) {
271 earliest
= point
.second
;
272 clean_mark_points
.pop_front();
273 point
= clean_mark_points
.front();
277 for (auto i
= server_map
.begin();
278 i
!= server_map
.end();
281 if (i2
->second
.get_last_delta() <= earliest
) {
282 server_map
.erase(i2
);
287 }; // class ServiceTracker