1 /* 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 24 #include <android-base/thread_annotations.h> 25 #include <media/MediaMetricsItem.h> 26 27 namespace android::mediametrics { 28 29 /** 30 * The TransactionLog is used to record mediametrics::Items to present 31 * 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 */ 40 class TransactionLog final { // made final as we have copy constructor instead of dup() override. 41 public: 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 46 static inline constexpr size_t kLogItemsLowWater = 1700; 47 // high water mark 48 static inline constexpr size_t kLogItemsHighWater = 2000; 49 50 // Estimated max data usage is 1KB * kLogItemsHighWater. 51 52 TransactionLog() = default; 53 TransactionLog(size_t lowWaterMark,size_t highWaterMark)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 62 // 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 TransactionLog(const TransactionLog & other)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; 82 mGarbageCollectionCount = other.mGarbageCollectionCount.load(); 83 84 return *this; 85 } 86 87 /** 88 * Put an item in the TransactionLog. 89 */ put(const std::shared_ptr<const mediametrics::Item> & item)90 status_t put(const std::shared_ptr<const mediametrics::Item>& item) { 91 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 97 (void)gc(garbage); 98 mLog.emplace_hint(mLog.end(), time, item); 99 mItemMap[key].emplace_hint(mItemMap[key].end(), time, item); 100 return NO_ERROR; // no errors for now. 101 } 102 103 /** 104 * Returns all records within [startTime, endTime] 105 */ 106 std::vector<std::shared_ptr<const mediametrics::Item>> get( 107 int64_t startTime = 0, int64_t endTime = INT64_MAX) const { 108 std::lock_guard lock(mLock); 109 return getItemsInRange(mLog, startTime, endTime); 110 } 111 112 /** 113 * Returns all records for a key within [startTime, endTime] 114 */ 115 std::vector<std::shared_ptr<const mediametrics::Item>> get( 116 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 {}; 121 return getItemsInRange(mapIt->second, startTime, endTime); 122 } 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. 132 * \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) 134 */ 135 std::pair<std::string, int32_t> dump( 136 int32_t lines, int64_t sinceNs, const char *prefix = nullptr) const { 137 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 } 146 auto [s, l] = dumpMapTimeItem(mLog, ll, sinceNs, prefix); 147 ss << s; 148 ll -= l; 149 150 // Grouped by item key (category) 151 if (ll > 0) { 152 ss << "Categorized:\n"; 153 --ll; 154 } 155 156 for (auto it = prefix != nullptr ? mItemMap.lower_bound(prefix) : mItemMap.begin(); 157 it != mItemMap.end(); 158 ++it) { 159 if (ll <= 0) break; 160 if (prefix != nullptr && !startsWith(it->first, prefix)) break; 161 std::tie(s, l) = dumpMapTimeItem(it->second, ll - 1, sinceNs, prefix); 162 if (l == 0) continue; // don't show empty groups (due to sinceNs). 163 ss << " " << it->first << "\n" << s; 164 ll -= l + 1; 165 } 166 return { ss.str(), lines - ll }; 167 } 168 169 /** 170 * Returns number of Items in the TransactionLog. 171 */ size()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 clear()181 void clear() { 182 std::lock_guard lock(mLock); 183 mLog.clear(); 184 mItemMap.clear(); 185 mGarbageCollectionCount = 0; 186 } 187 getGarbageCollectionCount()188 size_t getGarbageCollectionCount() const { 189 return mGarbageCollectionCount; 190 } 191 192 private: 193 using MapTimeItem = 194 std::multimap<int64_t /* time */, std::shared_ptr<const mediametrics::Item>>; 195 196 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 214 /** 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 */ gc(std::vector<std::any> & garbage)222 bool gc(std::vector<std::any>& garbage) REQUIRES(mLock) { 223 if (mLog.size() < mHighWaterMark) return false; 224 225 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 230 std::vector<std::shared_ptr<const mediametrics::Item>> stale; 231 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); 272 ++mGarbageCollectionCount; 273 return true; 274 } 275 276 static std::vector<std::shared_ptr<const mediametrics::Item>> getItemsInRange( 277 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 284 std::vector<std::shared_ptr<const mediametrics::Item>> ret; 285 while (it != it2) { 286 ret.push_back(it->second); 287 ++it; 288 } 289 return ret; 290 } 291 292 const size_t mLowWaterMark = kLogItemsLowWater; 293 const size_t mHighWaterMark = kLogItemsHighWater; 294 295 std::atomic<size_t> mGarbageCollectionCount{}; 296 297 mutable std::mutex mLock; 298 299 MapTimeItem mLog GUARDED_BY(mLock); 300 std::map<std::string /* item_key */, MapTimeItem> mItemMap GUARDED_BY(mLock); 301 }; 302 303 } // namespace android::mediametrics 304