blob: 7a520d96a7074bce9a87010738dd93159086838b [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 */
39class TransactionLog {
40public:
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
61 /**
62 * Put an item in the TransactionLog.
63 */
Ray Essickf27e9872019-12-07 06:28:46 -080064 status_t put(const std::shared_ptr<const mediametrics::Item>& item) {
Andy Hung06f3aba2019-12-03 16:36:42 -080065 const std::string& key = item->getKey();
66 const int64_t time = item->getTimestamp();
67
68 std::vector<std::any> garbage; // objects destroyed after lock.
69 std::lock_guard lock(mLock);
70
71 (void)gc_l(garbage);
72 mLog.emplace(time, item);
73 mItemMap[key].emplace(time, item);
74 return NO_ERROR; // no errors for now.
75 }
76
77 /**
78 * Returns all records within [startTime, endTime]
79 */
Ray Essickf27e9872019-12-07 06:28:46 -080080 std::vector<std::shared_ptr<const mediametrics::Item>> get(
Andy Hung06f3aba2019-12-03 16:36:42 -080081 int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
82 std::lock_guard lock(mLock);
83 return getItemsInRange_l(mLog, startTime, endTime);
84 }
85
86 /**
87 * Returns all records for a key within [startTime, endTime]
88 */
Ray Essickf27e9872019-12-07 06:28:46 -080089 std::vector<std::shared_ptr<const mediametrics::Item>> get(
Andy Hung06f3aba2019-12-03 16:36:42 -080090 const std::string& key,
91 int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
92 std::lock_guard lock(mLock);
93 auto mapIt = mItemMap.find(key);
94 if (mapIt == mItemMap.end()) return {};
95 return getItemsInRange_l(mapIt->second, startTime, endTime);
96 }
97
98 /**
99 * Returns a pair consisting of the Transaction Log as a string
100 * and the number of lines in the string.
101 *
102 * The number of lines in the returned pair is used as an optimization
103 * for subsequent line limiting.
104 *
105 * \param lines the maximum number of lines in the string returned.
106 */
107 std::pair<std::string, int32_t> dump(int32_t lines) const {
108 std::stringstream ss;
109 int32_t ll = lines;
110 std::lock_guard lock(mLock);
111
112 // All audio items in time order.
113 if (ll > 0) {
114 ss << "Consolidated:\n";
115 --ll;
116 }
117 for (const auto &log : mLog) {
118 if (ll <= 0) break;
119 ss << " " << log.second->toString() << "\n";
120 --ll;
121 }
122
123 // Grouped by item key (category)
124 if (ll > 0) {
125 ss << "Categorized:\n";
126 --ll;
127 }
128 for (const auto &itemMap : mItemMap) {
129 if (ll <= 0) break;
130 ss << " " << itemMap.first << "\n";
131 --ll;
132 for (const auto &item : itemMap.second) {
133 if (ll <= 0) break;
134 ss << " { " << item.first << ", " << item.second->toString() << " }\n";
135 --ll;
136 }
137 }
138 return { ss.str(), lines - ll };
139 }
140
141 /**
142 * Returns number of Items in the TransactionLog.
143 */
144 size_t size() const {
145 std::lock_guard lock(mLock);
146 return mLog.size();
147 }
148
149 /**
150 * Clears all Items from the TransactionLog.
151 */
152 // TODO: Garbage Collector, sweep and expire old values
153 void clear() {
154 std::lock_guard lock(mLock);
155 mLog.clear();
156 mItemMap.clear();
157 }
158
159private:
160 using MapTimeItem =
Ray Essickf27e9872019-12-07 06:28:46 -0800161 std::multimap<int64_t /* time */, std::shared_ptr<const mediametrics::Item>>;
Andy Hung06f3aba2019-12-03 16:36:42 -0800162
163 // GUARDED_BY mLock
164 /**
165 * Garbage collects if the TimeMachine size exceeds the high water mark.
166 *
167 * \param garbage a type-erased vector of elements to be destroyed
168 * outside of lock. Move large items to be destroyed here.
169 *
170 * \return true if garbage collection was done.
171 */
172 bool gc_l(std::vector<std::any>& garbage) {
173 if (mLog.size() < mHighWaterMark) return false;
174
175 ALOGD("%s: garbage collection", __func__);
176
177 auto eraseEnd = mLog.begin();
178 size_t toRemove = mLog.size() - mLowWaterMark;
179 // remove at least those elements.
180
181 // use a stale vector with precise type to avoid type erasure overhead in garbage
Ray Essickf27e9872019-12-07 06:28:46 -0800182 std::vector<std::shared_ptr<const mediametrics::Item>> stale;
Andy Hung06f3aba2019-12-03 16:36:42 -0800183
184 for (size_t i = 0; i < toRemove; ++i) {
185 stale.emplace_back(std::move(eraseEnd->second));
186 ++eraseEnd; // amortized O(1)
187 }
188 // ensure that eraseEnd is an lower bound on timeToErase.
189 const int64_t timeToErase = eraseEnd->first;
190 while (eraseEnd != mLog.end()) {
191 auto it = eraseEnd;
192 --it; // amortized O(1)
193 if (it->first != timeToErase) {
194 break; // eraseEnd represents a unique time jump.
195 }
196 stale.emplace_back(std::move(eraseEnd->second));
197 ++eraseEnd;
198 }
199
200 mLog.erase(mLog.begin(), eraseEnd); // O(ptr_diff)
201
202 size_t itemMapCount = 0;
203 for (auto it = mItemMap.begin(); it != mItemMap.end();) {
204 auto &keyHist = it->second;
205 auto it2 = keyHist.lower_bound(timeToErase);
206 if (it2 == keyHist.end()) {
207 garbage.emplace_back(std::move(keyHist)); // directly move keyhist to garbage
208 it = mItemMap.erase(it);
209 } else {
210 for (auto it3 = keyHist.begin(); it3 != it2; ++it3) {
211 stale.emplace_back(std::move(it3->second));
212 }
213 keyHist.erase(keyHist.begin(), it2);
214 itemMapCount += keyHist.size();
215 ++it;
216 }
217 }
218
219 garbage.emplace_back(std::move(stale));
220
221 ALOGD("%s(%zu, %zu): log size:%zu item map size:%zu, item map items:%zu",
222 __func__, mLowWaterMark, mHighWaterMark,
223 mLog.size(), mItemMap.size(), itemMapCount);
224 return true;
225 }
226
Ray Essickf27e9872019-12-07 06:28:46 -0800227 static std::vector<std::shared_ptr<const mediametrics::Item>> getItemsInRange_l(
Andy Hung06f3aba2019-12-03 16:36:42 -0800228 const MapTimeItem& map,
229 int64_t startTime = 0, int64_t endTime = INT64_MAX) {
230 auto it = map.lower_bound(startTime);
231 if (it == map.end()) return {};
232
233 auto it2 = map.upper_bound(endTime);
234
Ray Essickf27e9872019-12-07 06:28:46 -0800235 std::vector<std::shared_ptr<const mediametrics::Item>> ret;
Andy Hung06f3aba2019-12-03 16:36:42 -0800236 while (it != it2) {
237 ret.push_back(it->second);
238 ++it;
239 }
240 return ret;
241 }
242
243 const size_t mLowWaterMark = kLogItemsHighWater;
244 const size_t mHighWaterMark = kLogItemsHighWater;
245
246 mutable std::mutex mLock;
247
248 // GUARDED_BY mLock
249 MapTimeItem mLog;
250
251 // GUARDED_BY mLock
252 std::map<std::string /* item_key */, MapTimeItem> mItemMap;
253};
254
255} // namespace android::mediametrics