blob: 2359eec2200d41ffd44c213bd91b08579bc1dbfa [file] [log] [blame]
Andy Hung06f3aba2019-12-03 16:36:42 -08001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
19#include <any>
20#include <map>
21#include <sstream>
22#include <string>
23
Ray Essickf27e9872019-12-07 06:28:46 -080024#include <media/MediaMetricsItem.h>
Andy Hung06f3aba2019-12-03 16:36:42 -080025
26namespace android::mediametrics {
27
28/**
Ray Essickf27e9872019-12-07 06:28:46 -080029 * The TransactionLog is used to record mediametrics::Items to present
Andy Hung06f3aba2019-12-03 16:36:42 -080030 * different views on the time information (selected by audio, and sorted by key).
31 *
32 * The TransactionLog will always present data in timestamp order. (Perhaps we
33 * just make this submit order).
34 *
35 * These Views have a cost in shared pointer storage, so they aren't quite free.
36 *
37 * The TransactionLog is NOT thread safe.
38 */
Andy Hungcd24cca2020-01-02 18:27:43 -080039class TransactionLog final { // made final as we have copy constructor instead of dup() override.
Andy Hung06f3aba2019-12-03 16:36:42 -080040public:
41 // In long term run, the garbage collector aims to keep the
42 // Transaction Log between the Low Water Mark and the High Water Mark.
43
44 // low water mark
45 static inline constexpr size_t kLogItemsLowWater = 5000;
46 // high water mark
47 static inline constexpr size_t kLogItemsHighWater = 10000;
48
49 // Estimated max data usage is 1KB * kLogItemsHighWater.
50
51 TransactionLog() = default;
52
53 TransactionLog(size_t lowWaterMark, size_t highWaterMark)
54 : mLowWaterMark(lowWaterMark)
55 , mHighWaterMark(highWaterMark) {
56 LOG_ALWAYS_FATAL_IF(highWaterMark <= lowWaterMark,
57 "%s: required that highWaterMark:%zu > lowWaterMark:%zu",
58 __func__, highWaterMark, lowWaterMark);
59 }
60
Andy Hungcd24cca2020-01-02 18:27:43 -080061 // The TransactionLog copy constructor/assignment is effectively an
62 // instantaneous, isochronous snapshot of the other TransactionLog.
63 //
64 // The contents of the Transaction Log are shared pointers to immutable instances -
65 // std::shared_ptr<const mediametrics::Item>, so we use a shallow copy,
66 // which is more efficient in space and execution time than a deep copy,
67 // and gives the same results.
68
69 TransactionLog(const TransactionLog &other) {
70 *this = other;
71 }
72
73 TransactionLog& operator=(const TransactionLog &other) {
74 std::lock_guard lock(mLock);
75 mLog.clear();
76 mItemMap.clear();
77
78 std::lock_guard lock2(other.mLock);
79 mLog = other.mLog;
80 mItemMap = other.mItemMap;
81
82 return *this;
83 }
84
Andy Hung06f3aba2019-12-03 16:36:42 -080085 /**
86 * Put an item in the TransactionLog.
87 */
Ray Essickf27e9872019-12-07 06:28:46 -080088 status_t put(const std::shared_ptr<const mediametrics::Item>& item) {
Andy Hung06f3aba2019-12-03 16:36:42 -080089 const std::string& key = item->getKey();
90 const int64_t time = item->getTimestamp();
91
92 std::vector<std::any> garbage; // objects destroyed after lock.
93 std::lock_guard lock(mLock);
94
95 (void)gc_l(garbage);
96 mLog.emplace(time, item);
97 mItemMap[key].emplace(time, item);
98 return NO_ERROR; // no errors for now.
99 }
100
101 /**
102 * Returns all records within [startTime, endTime]
103 */
Ray Essickf27e9872019-12-07 06:28:46 -0800104 std::vector<std::shared_ptr<const mediametrics::Item>> get(
Andy Hung06f3aba2019-12-03 16:36:42 -0800105 int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
106 std::lock_guard lock(mLock);
107 return getItemsInRange_l(mLog, startTime, endTime);
108 }
109
110 /**
111 * Returns all records for a key within [startTime, endTime]
112 */
Ray Essickf27e9872019-12-07 06:28:46 -0800113 std::vector<std::shared_ptr<const mediametrics::Item>> get(
Andy Hung06f3aba2019-12-03 16:36:42 -0800114 const std::string& key,
115 int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
116 std::lock_guard lock(mLock);
117 auto mapIt = mItemMap.find(key);
118 if (mapIt == mItemMap.end()) return {};
119 return getItemsInRange_l(mapIt->second, startTime, endTime);
120 }
121
122 /**
123 * Returns a pair consisting of the Transaction Log as a string
124 * and the number of lines in the string.
125 *
126 * The number of lines in the returned pair is used as an optimization
127 * for subsequent line limiting.
128 *
129 * \param lines the maximum number of lines in the string returned.
Andy Hung709b91e2020-04-04 14:23:36 -0700130 * \param sinceNs the nanoseconds since Unix epoch to start dump (0 shows all)
131 * \param prefix the desired key prefix to match (nullptr shows all)
Andy Hung06f3aba2019-12-03 16:36:42 -0800132 */
Andy Hung709b91e2020-04-04 14:23:36 -0700133 std::pair<std::string, int32_t> dump(
134 int32_t lines, int64_t sinceNs, const char *prefix = nullptr) const {
Andy Hung06f3aba2019-12-03 16:36:42 -0800135 std::stringstream ss;
136 int32_t ll = lines;
137 std::lock_guard lock(mLock);
138
139 // All audio items in time order.
140 if (ll > 0) {
141 ss << "Consolidated:\n";
142 --ll;
143 }
Andy Hung709b91e2020-04-04 14:23:36 -0700144 auto [s, l] = dumpMapTimeItem(mLog, ll, sinceNs, prefix);
145 ss << std::move(s);
146 ll -= l;
Andy Hung06f3aba2019-12-03 16:36:42 -0800147
148 // Grouped by item key (category)
149 if (ll > 0) {
150 ss << "Categorized:\n";
151 --ll;
152 }
Andy Hung709b91e2020-04-04 14:23:36 -0700153
154 for (auto it = prefix != nullptr ? mItemMap.lower_bound(prefix) : mItemMap.begin();
155 it != mItemMap.end();
156 ++it) {
Andy Hung06f3aba2019-12-03 16:36:42 -0800157 if (ll <= 0) break;
Andy Hung709b91e2020-04-04 14:23:36 -0700158 if (prefix != nullptr && !startsWith(it->first, prefix)) break;
159 auto [s, l] = dumpMapTimeItem(it->second, ll - 1, sinceNs, prefix);
160 if (l == 0) continue; // don't show empty groups (due to sinceNs).
161 ss << " " << it->first << "\n" << std::move(s);
162 ll -= l + 1;
Andy Hung06f3aba2019-12-03 16:36:42 -0800163 }
164 return { ss.str(), lines - ll };
165 }
166
167 /**
168 * Returns number of Items in the TransactionLog.
169 */
170 size_t size() const {
171 std::lock_guard lock(mLock);
172 return mLog.size();
173 }
174
175 /**
176 * Clears all Items from the TransactionLog.
177 */
178 // TODO: Garbage Collector, sweep and expire old values
179 void clear() {
180 std::lock_guard lock(mLock);
181 mLog.clear();
182 mItemMap.clear();
183 }
184
185private:
186 using MapTimeItem =
Ray Essickf27e9872019-12-07 06:28:46 -0800187 std::multimap<int64_t /* time */, std::shared_ptr<const mediametrics::Item>>;
Andy Hung06f3aba2019-12-03 16:36:42 -0800188
Andy Hung709b91e2020-04-04 14:23:36 -0700189 static std::pair<std::string, int32_t> dumpMapTimeItem(
190 const MapTimeItem& mapTimeItem,
191 int32_t lines, int64_t sinceNs = 0, const char *prefix = nullptr) {
192 std::stringstream ss;
193 int32_t ll = lines;
194 // Note: for our data, mapTimeItem.lower_bound(0) == mapTimeItem.begin().
195 for (auto it = mapTimeItem.lower_bound(sinceNs);
196 it != mapTimeItem.end(); ++it) {
197 if (ll <= 0) break;
198 if (prefix != nullptr && !startsWith(it->second->getKey(), prefix)) {
199 continue;
200 }
201 ss << " " << it->second->toString() << "\n";
202 --ll;
203 }
204 return { ss.str(), lines - ll };
205 }
206
Andy Hung06f3aba2019-12-03 16:36:42 -0800207 // GUARDED_BY mLock
208 /**
209 * Garbage collects if the TimeMachine size exceeds the high water mark.
210 *
211 * \param garbage a type-erased vector of elements to be destroyed
212 * outside of lock. Move large items to be destroyed here.
213 *
214 * \return true if garbage collection was done.
215 */
216 bool gc_l(std::vector<std::any>& garbage) {
217 if (mLog.size() < mHighWaterMark) return false;
218
219 ALOGD("%s: garbage collection", __func__);
220
221 auto eraseEnd = mLog.begin();
222 size_t toRemove = mLog.size() - mLowWaterMark;
223 // remove at least those elements.
224
225 // use a stale vector with precise type to avoid type erasure overhead in garbage
Ray Essickf27e9872019-12-07 06:28:46 -0800226 std::vector<std::shared_ptr<const mediametrics::Item>> stale;
Andy Hung06f3aba2019-12-03 16:36:42 -0800227
228 for (size_t i = 0; i < toRemove; ++i) {
229 stale.emplace_back(std::move(eraseEnd->second));
230 ++eraseEnd; // amortized O(1)
231 }
232 // ensure that eraseEnd is an lower bound on timeToErase.
233 const int64_t timeToErase = eraseEnd->first;
234 while (eraseEnd != mLog.end()) {
235 auto it = eraseEnd;
236 --it; // amortized O(1)
237 if (it->first != timeToErase) {
238 break; // eraseEnd represents a unique time jump.
239 }
240 stale.emplace_back(std::move(eraseEnd->second));
241 ++eraseEnd;
242 }
243
244 mLog.erase(mLog.begin(), eraseEnd); // O(ptr_diff)
245
246 size_t itemMapCount = 0;
247 for (auto it = mItemMap.begin(); it != mItemMap.end();) {
248 auto &keyHist = it->second;
249 auto it2 = keyHist.lower_bound(timeToErase);
250 if (it2 == keyHist.end()) {
251 garbage.emplace_back(std::move(keyHist)); // directly move keyhist to garbage
252 it = mItemMap.erase(it);
253 } else {
254 for (auto it3 = keyHist.begin(); it3 != it2; ++it3) {
255 stale.emplace_back(std::move(it3->second));
256 }
257 keyHist.erase(keyHist.begin(), it2);
258 itemMapCount += keyHist.size();
259 ++it;
260 }
261 }
262
263 garbage.emplace_back(std::move(stale));
264
265 ALOGD("%s(%zu, %zu): log size:%zu item map size:%zu, item map items:%zu",
266 __func__, mLowWaterMark, mHighWaterMark,
267 mLog.size(), mItemMap.size(), itemMapCount);
268 return true;
269 }
270
Ray Essickf27e9872019-12-07 06:28:46 -0800271 static std::vector<std::shared_ptr<const mediametrics::Item>> getItemsInRange_l(
Andy Hung06f3aba2019-12-03 16:36:42 -0800272 const MapTimeItem& map,
273 int64_t startTime = 0, int64_t endTime = INT64_MAX) {
274 auto it = map.lower_bound(startTime);
275 if (it == map.end()) return {};
276
277 auto it2 = map.upper_bound(endTime);
278
Ray Essickf27e9872019-12-07 06:28:46 -0800279 std::vector<std::shared_ptr<const mediametrics::Item>> ret;
Andy Hung06f3aba2019-12-03 16:36:42 -0800280 while (it != it2) {
281 ret.push_back(it->second);
282 ++it;
283 }
284 return ret;
285 }
286
Andy Hung5d3f2d12020-03-04 19:55:03 -0800287 const size_t mLowWaterMark = kLogItemsLowWater;
Andy Hung06f3aba2019-12-03 16:36:42 -0800288 const size_t mHighWaterMark = kLogItemsHighWater;
289
290 mutable std::mutex mLock;
291
292 // GUARDED_BY mLock
293 MapTimeItem mLog;
294
295 // GUARDED_BY mLock
296 std::map<std::string /* item_key */, MapTimeItem> mItemMap;
297};
298
299} // namespace android::mediametrics