Project
Loading...
Searching...
No Matches
CapacityEstimator.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
13
14#include <algorithm>
15#include <cassert>
16#include <cmath>
17#include <limits>
18#include <mutex>
19#include <stdexcept>
20#include <tuple>
21#include <unordered_map>
22#include <vector>
23
24#include "Framework/Logger.h"
25
27{
28
30 struct Entry {
31 float ratio{0.f};
32 float margin{0.f};
34 };
35
36 struct UndoRecord {
37 bool existed{false};
39 };
40
41 explicit Impl(Config config) : cfg{config} {}
42
44 mutable std::mutex mutex;
45 std::unordered_map<KeyType, Entry> entries;
46 std::unordered_map<KeyType, UndoRecord> undo;
47 bool transactionActive{false};
48
50 {
51 if (!transactionActive || undo.find(key) != undo.end()) {
52 return;
53 }
54 const auto current = entries.find(key);
55 if (current == entries.end()) {
56 undo.emplace(key, UndoRecord{});
57 } else {
58 undo.emplace(key, UndoRecord{.existed = true, .previous = current->second});
59 }
60 }
61
62 void observe(KeyType key, double scale, size_t requested, size_t granted, size_t emitted,
63 size_t spilled, bool overflowed, bool memoryLimited)
64 {
65 // Record the first-touch undo state before entries[key] can insert or the
66 // existing live entry can be modified. If undo insertion throws, the live
67 // estimator remains unchanged and Tracker's failure path can roll back the
68 // transaction without observing a partial update.
70 auto& e = entries[key];
71 auto& statistics = e.statistics;
72 statistics.requested += requested;
73 statistics.granted += granted;
74 statistics.emitted += emitted;
75 statistics.spilled += spilled;
76
77 const bool firstSample = statistics.samples == 0;
78 if (firstSample) {
79 e.margin = cfg.marginInit;
80 }
81 const auto sample = static_cast<float>(double(emitted) / scale);
82 e.ratio = firstSample ? sample : (cfg.alpha * sample) + ((1.f - cfg.alpha) * e.ratio);
83 statistics.maxEmitted = std::max(statistics.maxEmitted, emitted);
85
86 if (memoryLimited) {
88 e.margin = std::max(cfg.marginMin, e.margin * cfg.marginDown);
89 return;
90 }
91 if (overflowed) {
94 if (!firstSample) {
95 const float shortfall = granted ? static_cast<float>(double(emitted) / double(granted)) : cfg.marginUp;
96 e.margin = std::min(cfg.marginMax, e.margin * std::clamp(shortfall * cfg.marginOverflowSlack, 1.02f, cfg.marginUp));
97 }
98 return;
99 }
100 const float util = granted ? float(double(emitted) / double(granted)) : 1.f;
101 if (util < cfg.lowWatermark) {
103 e.margin = std::max(cfg.marginMin, e.margin * cfg.marginDown);
105 }
106 } else if (statistics.nLowStreak > 0) {
108 }
109 }
110};
111
113
114CapacityEstimator::CapacityEstimator(Config cfg) : mImpl{std::make_unique<Impl>(cfg)} {}
115
117
119{
120 std::lock_guard lock{mImpl->mutex};
121 mImpl->entries.clear();
122 mImpl->undo.clear();
123 mImpl->transactionActive = false;
124}
125
127{
128 std::lock_guard lock{mImpl->mutex};
129 if (mImpl->transactionActive) {
130 throw std::logic_error{"CapacityEstimator transaction already active"};
131 }
132 assert(mImpl->undo.empty());
133 mImpl->transactionActive = true;
134}
135
137{
138 std::lock_guard lock{mImpl->mutex};
139 mImpl->undo.clear();
140 mImpl->transactionActive = false;
141}
142
144{
145 std::lock_guard lock{mImpl->mutex};
146 if (!mImpl->transactionActive) {
147 return;
148 }
149 for (const auto& [key, record] : mImpl->undo) {
150 if (record.existed) {
151 const auto current = mImpl->entries.find(key);
152 assert(current != mImpl->entries.end());
153 current->second = record.previous;
154 } else {
155 mImpl->entries.erase(key);
156 }
157 }
158 mImpl->undo.clear();
159 mImpl->transactionActive = false;
160}
161
162size_t CapacityEstimator::capacity(uint64_t key, double scale) const
163{
164 if (!(scale > 0.)) {
165 return 0;
166 }
167 std::lock_guard lock{mImpl->mutex};
168 const auto it = mImpl->entries.find(key);
169 if (it == mImpl->entries.end() || it->second.statistics.samples == 0) {
170 return mImpl->cfg.floorSlots;
171 }
172 const auto& e = it->second;
173 const double raw = double(e.ratio) * scale * double(e.margin);
174 if (!std::isfinite(raw) || raw < 0.) {
175 return mImpl->cfg.floorSlots;
176 }
177 // A ratio is only meaningful at the scale it was measured at. Learned on a handful of inputs it
178 // can be arbitrarily large, and applying it to a scale orders of magnitude bigger asks for a slab
179 // nobody can allocate. Bound the request by what this site has ever actually emitted: overshooting
180 // burns memory that a bump allocator cannot give back, undershooting only costs one retry.
181 const size_t ceiling = std::max(mImpl->cfg.floorSlots, static_cast<size_t>(double(e.statistics.maxEmitted) * double(mImpl->cfg.marginMax)));
182 if (raw >= static_cast<double>(ceiling)) {
183 return ceiling;
184 }
185 return std::max(mImpl->cfg.floorSlots, static_cast<size_t>(std::ceil(raw)));
186}
187
189{
190 std::lock_guard lock{mImpl->mutex};
191 const auto it = mImpl->entries.find(key);
192 if (it == mImpl->entries.end() || it->second.statistics.maxEmitted == 0) {
193 return mImpl->cfg.floorSlots;
194 }
195 const auto& e = it->second;
196 const double raw = double(e.statistics.maxEmitted) * double(e.margin);
197 if (!std::isfinite(raw) || raw >= static_cast<double>(std::numeric_limits<size_t>::max())) {
198 return std::numeric_limits<size_t>::max();
199 }
200 return std::max(mImpl->cfg.floorSlots, static_cast<size_t>(std::ceil(raw)));
201}
202
203double CapacityEstimator::expected(uint64_t key, double scale) const
204{
205 if (!(scale > 0.)) {
206 return 0.;
207 }
208 std::lock_guard lock{mImpl->mutex};
209 const auto it = mImpl->entries.find(key);
210 if (it == mImpl->entries.end() || it->second.statistics.samples == 0) {
211 return 0.;
212 }
213 const double raw = double(it->second.ratio) * scale;
214 return std::isfinite(raw) && raw > 0. ? raw : 0.;
215}
216
218{
219 std::lock_guard lock{mImpl->mutex};
220 const auto it = mImpl->entries.find(key);
221 if (it == mImpl->entries.end()) {
222 return {};
223 }
224 return it->second.statistics;
225}
226
227void CapacityEstimator::update(uint64_t key, double scale, size_t emitted, size_t capacityUsed, bool overflowed, bool memoryLimited)
228{
229 if (!(scale > 0.)) {
230 return;
231 }
232 std::lock_guard lock{mImpl->mutex};
233 mImpl->observe(key, scale, capacityUsed, capacityUsed, emitted,
234 overflowed && emitted > capacityUsed ? emitted - capacityUsed : 0,
235 overflowed, memoryLimited);
236}
237
238void CapacityEstimator::update(uint64_t key, double scale, size_t requested, size_t granted,
239 size_t emitted, size_t spilled, bool overflowed, bool memoryLimited)
240{
241 if (!(scale > 0.)) {
242 return;
243 }
244 std::lock_guard lock{mImpl->mutex};
245 mImpl->observe(key, scale, requested, granted, emitted, spilled, overflowed, memoryLimited);
246}
247
249{
250 std::lock_guard lock{mImpl->mutex};
251 std::vector<KeyType> keys;
252 keys.reserve(mImpl->entries.size());
253 for (const auto& [key, _] : mImpl->entries) {
254 keys.push_back(key);
255 }
256 std::sort(keys.begin(), keys.end(), [](KeyType a, KeyType b) {
257 const auto da = decodeKey(a);
258 const auto db = decodeKey(b);
259 return std::tie(da.site, da.iteration, da.variant, da.slot) <
260 std::tie(db.site, db.iteration, db.variant, db.slot);
261 });
262 if (keys.empty()) {
263 return;
264 }
265 LOGP(info, "Printing CapacityEstimators:");
266 for (const auto key : keys) {
267 const auto& value = mImpl->entries.at(key);
268 const auto& statistics = value.statistics;
269 const auto decoded = decodeKey(key);
270 LOGP(info, "\tSite:{} | iter:{} | var:({},{}) | slot:{} | ratio:{} | margin:{} | maxEmitted:{} | samples:{} | low:{} | requested:{} | granted:{} | emitted:{} | spilled:{} | overflows:{}", SlabSiteNames[decoded.site], decoded.iteration, getVariantHigh(decoded.variant), getVariantLow(decoded.variant), decoded.slot, value.ratio, value.margin, statistics.maxEmitted, statistics.samples, statistics.nLowStreak, statistics.requested, statistics.granted, statistics.emitted, statistics.spilled, statistics.overflowEvents);
271 }
272}
273
274} // namespace o2::itsmft::tracking
Cross-timeframe output-size prediction.
std::vector< std::string > keys
o2::raw::RawFileWriter * raw
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char Directory with events to Events directory is and not inside events Show standalone event Run event generator maximum time bin of continuous for triggered for automatic continuous for automatic continuous triggered uint64_t
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
configStandalone Reconstruction rec_tpc float
StringRef key
static constexpr int getVariantHigh(int variant) noexcept
Statistics statistics(uint64_t key) const
size_t capacity(uint64_t key, double scale) const
static constexpr int getVariantLow(int variant) noexcept
double expected(uint64_t key, double scale) const
void update(uint64_t key, double scale, size_t emitted, size_t capacityUsed, bool overflowed, bool memoryLimited)
static constexpr Decoded decodeKey(KeyType key) noexcept
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
uint32_t trackClusterIndicesSize noexcept
constexpr const char *const SlabSiteNames[SlabSite::NSlabSite]
std::unordered_map< KeyType, Entry > entries
void observe(KeyType key, double scale, size_t requested, size_t granted, size_t emitted, size_t spilled, bool overflowed, bool memoryLimited)
std::unordered_map< KeyType, UndoRecord > undo