blob: 0ca463986db9e3f9037ce9dfec3a90f69fa8175a [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
Andy Hungf7c14102020-04-18 14:54:08 -070024#include <android-base/thread_annotations.h>
Ray Essickf27e9872019-12-07 06:28:46 -080025#include <media/MediaMetricsItem.h>
Andy Hung06f3aba2019-12-03 16:36:42 -080026
27namespace android::mediametrics {
28
29/**
Ray Essickf27e9872019-12-07 06:28:46 -080030 * The TransactionLog is used to record mediametrics::Items to present
Andy Hung06f3aba2019-12-03 16:36:42 -080031 * different views on the time information (selected by audio, and sorted by key).
32 *
33 * The TransactionLog will always present data in timestamp order. (Perhaps we
34 * just make this submit order).
35 *
36 * These Views have a cost in shared pointer storage, so they aren't quite free.
37 *
38 * The TransactionLog is NOT thread safe.
39 */
Andy Hungcd24cca2020-01-02 18:27:43 -080040class TransactionLog final { // made final as we have copy constructor instead of dup() override.
Andy Hung06f3aba2019-12-03 16:36:42 -080041public:
42 // In long term run, the garbage collector aims to keep the
43 // Transaction Log between the Low Water Mark and the High Water Mark.
44
45 // low water mark
Andy Hung47178c22020-06-18 18:57:35 -070046 static inline constexpr size_t kLogItemsLowWater = 1700;
Andy Hung06f3aba2019-12-03 16:36:42 -080047 // high water mark
Andy Hung47178c22020-06-18 18:57:35 -070048 static inline constexpr size_t kLogItemsHighWater = 2000;
Andy Hung06f3aba2019-12-03 16:36:42 -080049
50 // Estimated max data usage is 1KB * kLogItemsHighWater.
51
52 TransactionLog() = default;
53
54 TransactionLog(size_t lowWaterMark, size_t highWaterMark)
55 : mLowWaterMark(lowWaterMark)
56 , mHighWaterMark(highWaterMark) {
57 LOG_ALWAYS_FATAL_IF(highWaterMark <= lowWaterMark,
58 "%s: required that highWaterMark:%zu > lowWaterMark:%zu",
59 __func__, highWaterMark, lowWaterMark);
60 }
61
Andy Hungcd24cca2020-01-02 18:27:43 -080062 // The TransactionLog copy constructor/assignment is effectively an
63 // instantaneous, isochronous snapshot of the other TransactionLog.
64 //
65 // The contents of the Transaction Log are shared pointers to immutable instances -
66 // std::shared_ptr<const mediametrics::Item>, so we use a shallow copy,
67 // which is more efficient in space and execution time than a deep copy,
68 // and gives the same results.
69
70 TransactionLog(const TransactionLog &other) {
71 *this = other;
72 }
73
74 TransactionLog& operator=(const TransactionLog &other) {
75 std::lock_guard lock(mLock);
76 mLog.clear();
77 mItemMap.clear();
78
79 std::lock_guard lock2(other.mLock);
80 mLog = other.mLog;
81 mItemMap = other.mItemMap;
Andy Hung47178c22020-06-18 18:57:35 -070082 mGarbageCollectionCount = other.mGarbageCollectionCount.load();
Andy Hungcd24cca2020-01-02 18:27:43 -080083
84 return *this;
85 }
86
Andy Hung06f3aba2019-12-03 16:36:42 -080087 /**
88 * Put an item in the TransactionLog.
89 */
Ray Essickf27e9872019-12-07 06:28:46 -080090 status_t put(const std::shared_ptr<const mediametrics::Item>& item) {
Andy Hung06f3aba2019-12-03 16:36:42 -080091 const std::string& key = item->getKey();
92 const int64_t time = item->getTimestamp();
93
94 std::vector<std::any> garbage; // objects destroyed after lock.
95 std::lock_guard lock(mLock);
96
Andy Hungf7c14102020-04-18 14:54:08 -070097 (void)gc(garbage);
Andy Hung7d391082020-04-18 15:03:51 -070098 mLog.emplace_hint(mLog.end(), time, item);
99 mItemMap[key].emplace_hint(mItemMap[key].end(), time, item);
Andy Hung06f3aba2019-12-03 16:36:42 -0800100 return NO_ERROR; // no errors for now.
101 }
102
103 /**
104 * Returns all records within [startTime, endTime]
105 */
Ray Essickf27e9872019-12-07 06:28:46 -0800106 std::vector<std::shared_ptr<const mediametrics::Item>> get(
Andy Hung06f3aba2019-12-03 16:36:42 -0800107 int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
108 std::lock_guard lock(mLock);
Andy Hungf7c14102020-04-18 14:54:08 -0700109 return getItemsInRange(mLog, startTime, endTime);
Andy Hung06f3aba2019-12-03 16:36:42 -0800110 }
111
112 /**
113 * Returns all records for a key within [startTime, endTime]
114 */
Ray Essickf27e9872019-12-07 06:28:46 -0800115 std::vector<std::shared_ptr<const mediametrics::Item>> get(
Andy Hung06f3aba2019-12-03 16:36:42 -0800116 const std::string& key,
117 int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
118 std::lock_guard lock(mLock);
119 auto mapIt = mItemMap.find(key);
120 if (mapIt == mItemMap.end()) return {};
Andy Hungf7c14102020-04-18 14:54:08 -0700121 return getItemsInRange(mapIt->second, startTime, endTime);
Andy Hung06f3aba2019-12-03 16:36:42 -0800122 }
123
124 /**
125 * Returns a pair consisting of the Transaction Log as a string
126 * and the number of lines in the string.
127 *
128 * The number of lines in the returned pair is used as an optimization
129 * for subsequent line limiting.
130 *
131 * \param lines the maximum number of lines in the string returned.
Andy Hung709b91e2020-04-04 14:23:36 -0700132 * \param sinceNs the nanoseconds since Unix epoch to start dump (0 shows all)
133 * \param prefix the desired key prefix to match (nullptr shows all)
Andy Hung06f3aba2019-12-03 16:36:42 -0800134 */
Andy Hung709b91e2020-04-04 14:23:36 -0700135 std::pair<std::string, int32_t> dump(
136 int32_t lines, int64_t sinceNs, const char *prefix = nullptr) const {
Andy Hung06f3aba2019-12-03 16:36:42 -0800137 std::stringstream ss;
138 int32_t ll = lines;
139 std::lock_guard lock(mLock);
140
141 // All audio items in time order.
142 if (ll > 0) {
143 ss << "Consolidated:\n";
144 --ll;
145 }
Andy Hung709b91e2020-04-04 14:23:36 -0700146 auto [s, l] = dumpMapTimeItem(mLog, ll, sinceNs, prefix);
Andy Hungb744faf2020-04-09 13:09:26 -0700147 ss << s;
Andy Hung709b91e2020-04-04 14:23:36 -0700148 ll -= l;
Andy Hung06f3aba2019-12-03 16:36:42 -0800149
150 // Grouped by item key (category)
151 if (ll > 0) {
152 ss << "Categorized:\n";
153 --ll;
154 }
Andy Hung709b91e2020-04-04 14:23:36 -0700155
156 for (auto it = prefix != nullptr ? mItemMap.lower_bound(prefix) : mItemMap.begin();
157 it != mItemMap.end();
158 ++it) {
Andy Hung06f3aba2019-12-03 16:36:42 -0800159 if (ll <= 0) break;
Andy Hung709b91e2020-04-04 14:23:36 -0700160 if (prefix != nullptr && !startsWith(it->first, prefix)) break;
161 auto [s, l] = dumpMapTimeItem(it->second, ll - 1, sinceNs, prefix);
162 if (l == 0) continue; // don't show empty groups (due to sinceNs).
Andy Hungb744faf2020-04-09 13:09:26 -0700163 ss << " " << it->first << "\n" << s;
Andy Hung709b91e2020-04-04 14:23:36 -0700164 ll -= l + 1;
Andy Hung06f3aba2019-12-03 16:36:42 -0800165 }
166 return { ss.str(), lines - ll };
167 }
168
169 /**
170 * Returns number of Items in the TransactionLog.
171 */
172 size_t size() const {
173 std::lock_guard lock(mLock);
174 return mLog.size();
175 }
176
177 /**
178 * Clears all Items from the TransactionLog.
179 */
180 // TODO: Garbage Collector, sweep and expire old values
181 void clear() {
182 std::lock_guard lock(mLock);
183 mLog.clear();
184 mItemMap.clear();
Andy Hung47178c22020-06-18 18:57:35 -0700185 mGarbageCollectionCount = 0;
186 }
187
188 size_t getGarbageCollectionCount() const {
189 return mGarbageCollectionCount;
Andy Hung06f3aba2019-12-03 16:36:42 -0800190 }
191
192private:
193 using MapTimeItem =
Ray Essickf27e9872019-12-07 06:28:46 -0800194 std::multimap<int64_t /* time */, std::shared_ptr<const mediametrics::Item>>;
Andy Hung06f3aba2019-12-03 16:36:42 -0800195
Andy Hung709b91e2020-04-04 14:23:36 -0700196 static std::pair<std::string, int32_t> dumpMapTimeItem(
197 const MapTimeItem& mapTimeItem,
198 int32_t lines, int64_t sinceNs = 0, const char *prefix = nullptr) {
199 std::stringstream ss;
200 int32_t ll = lines;
201 // Note: for our data, mapTimeItem.lower_bound(0) == mapTimeItem.begin().
202 for (auto it = mapTimeItem.lower_bound(sinceNs);
203 it != mapTimeItem.end(); ++it) {
204 if (ll <= 0) break;
205 if (prefix != nullptr && !startsWith(it->second->getKey(), prefix)) {
206 continue;
207 }
208 ss << " " << it->second->toString() << "\n";
209 --ll;
210 }
211 return { ss.str(), lines - ll };
212 }
213
Andy Hung06f3aba2019-12-03 16:36:42 -0800214 /**
215 * Garbage collects if the TimeMachine size exceeds the high water mark.
216 *
217 * \param garbage a type-erased vector of elements to be destroyed
218 * outside of lock. Move large items to be destroyed here.
219 *
220 * \return true if garbage collection was done.
221 */
Andy Hungf7c14102020-04-18 14:54:08 -0700222 bool gc(std::vector<std::any>& garbage) REQUIRES(mLock) {
Andy Hung06f3aba2019-12-03 16:36:42 -0800223 if (mLog.size() < mHighWaterMark) return false;
224
Andy Hung06f3aba2019-12-03 16:36:42 -0800225 auto eraseEnd = mLog.begin();
226 size_t toRemove = mLog.size() - mLowWaterMark;
227 // remove at least those elements.
228
229 // use a stale vector with precise type to avoid type erasure overhead in garbage
Ray Essickf27e9872019-12-07 06:28:46 -0800230 std::vector<std::shared_ptr<const mediametrics::Item>> stale;
Andy Hung06f3aba2019-12-03 16:36:42 -0800231
232 for (size_t i = 0; i < toRemove; ++i) {
233 stale.emplace_back(std::move(eraseEnd->second));
234 ++eraseEnd; // amortized O(1)
235 }
236 // ensure that eraseEnd is an lower bound on timeToErase.
237 const int64_t timeToErase = eraseEnd->first;
238 while (eraseEnd != mLog.end()) {
239 auto it = eraseEnd;
240 --it; // amortized O(1)
241 if (it->first != timeToErase) {
242 break; // eraseEnd represents a unique time jump.
243 }
244 stale.emplace_back(std::move(eraseEnd->second));
245 ++eraseEnd;
246 }
247
248 mLog.erase(mLog.begin(), eraseEnd); // O(ptr_diff)
249
250 size_t itemMapCount = 0;
251 for (auto it = mItemMap.begin(); it != mItemMap.end();) {
252 auto &keyHist = it->second;
253 auto it2 = keyHist.lower_bound(timeToErase);
254 if (it2 == keyHist.end()) {
255 garbage.emplace_back(std::move(keyHist)); // directly move keyhist to garbage
256 it = mItemMap.erase(it);
257 } else {
258 for (auto it3 = keyHist.begin(); it3 != it2; ++it3) {
259 stale.emplace_back(std::move(it3->second));
260 }
261 keyHist.erase(keyHist.begin(), it2);
262 itemMapCount += keyHist.size();
263 ++it;
264 }
265 }
266
267 garbage.emplace_back(std::move(stale));
268
269 ALOGD("%s(%zu, %zu): log size:%zu item map size:%zu, item map items:%zu",
270 __func__, mLowWaterMark, mHighWaterMark,
271 mLog.size(), mItemMap.size(), itemMapCount);
Andy Hung47178c22020-06-18 18:57:35 -0700272 ++mGarbageCollectionCount;
Andy Hung06f3aba2019-12-03 16:36:42 -0800273 return true;
274 }
275
Andy Hungf7c14102020-04-18 14:54:08 -0700276 static std::vector<std::shared_ptr<const mediametrics::Item>> getItemsInRange(
Andy Hung06f3aba2019-12-03 16:36:42 -0800277 const MapTimeItem& map,
278 int64_t startTime = 0, int64_t endTime = INT64_MAX) {
279 auto it = map.lower_bound(startTime);
280 if (it == map.end()) return {};
281
282 auto it2 = map.upper_bound(endTime);
283
Ray Essickf27e9872019-12-07 06:28:46 -0800284 std::vector<std::shared_ptr<const mediametrics::Item>> ret;
Andy Hung06f3aba2019-12-03 16:36:42 -0800285 while (it != it2) {
286 ret.push_back(it->second);
287 ++it;
288 }
289 return ret;
290 }
291
Andy Hung5d3f2d12020-03-04 19:55:03 -0800292 const size_t mLowWaterMark = kLogItemsLowWater;
Andy Hung06f3aba2019-12-03 16:36:42 -0800293 const size_t mHighWaterMark = kLogItemsHighWater;
294
Andy Hung47178c22020-06-18 18:57:35 -0700295 std::atomic<size_t> mGarbageCollectionCount{};
296
Andy Hung06f3aba2019-12-03 16:36:42 -0800297 mutable std::mutex mLock;
298
Andy Hungf7c14102020-04-18 14:54:08 -0700299 MapTimeItem mLog GUARDED_BY(mLock);
300 std::map<std::string /* item_key */, MapTimeItem> mItemMap GUARDED_BY(mLock);
Andy Hung06f3aba2019-12-03 16:36:42 -0800301};
302
303} // namespace android::mediametrics