Loading media/libmediametrics/MediaMetricsItem.cpp +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ static const std::unordered_map<std::string, int32_t>& getErrorStringMap() { // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h static std::unordered_map<std::string, int32_t> map{ {"", NO_ERROR}, {AMEDIAMETRICS_PROP_ERROR_VALUE_OK, NO_ERROR}, {AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, BAD_VALUE}, {AMEDIAMETRICS_PROP_ERROR_VALUE_IO, DEAD_OBJECT}, {AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY, NO_MEMORY}, Loading media/libmediametrics/include/MediaMetricsConstants.h +3 −2 Original line number Diff line number Diff line Loading @@ -238,8 +238,9 @@ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioSystem.java;drc=3ac246c43294d7f7012bdcb0ccb7bae1aa695bd4;l=785 // https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libaaudio/include/aaudio/AAudio.h;drc=cfd3a6fa3aaaf712a890dc02452b38ef401083b8;l=120 // Error category: // An empty error string indicates no error. // Status errors: // An empty status string or "ok" is interpreted as no error. #define AMEDIAMETRICS_PROP_ERROR_VALUE_OK "ok" // Error category: argument // IllegalArgumentException Loading services/mediametrics/AudioAnalytics.h +16 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include "AnalyticsActions.h" #include "AnalyticsState.h" #include "AudioPowerUsage.h" #include "HeatMap.h" #include "StatsdLog.h" #include "TimedAction.h" #include "Wrap.h" Loading Loading @@ -73,11 +74,23 @@ public: std::pair<std::string, int32_t> dump( int32_t lines = INT32_MAX, int64_t sinceNs = 0, const char *prefix = nullptr) const; /** * Returns a pair consisting of the dump string and the number of lines in the string. * * HeatMap dump. */ std::pair<std::string, int32_t> dumpHeatMap(int32_t lines = INT32_MAX) const { return mHeatMap.dump(lines); } void clear() { // underlying state is locked. mPreviousAnalyticsState->clear(); mAnalyticsState->clear(); // Clears the status map mHeatMap.clear(); // Clear power usage state. mAudioPowerUsage.clear(); } Loading Loading @@ -124,6 +137,9 @@ private: TimedAction mTimedAction; // locked internally const std::shared_ptr<StatsdLog> mStatsdLog; // locked internally, ok for multiple threads. static constexpr size_t kHeatEntries = 100; HeatMap mHeatMap{kHeatEntries}; // locked internally, ok for multiple threads. // DeviceUse is a nested class which handles audio device usage accounting. // We define this class at the end to ensure prior variables all properly constructed. // TODO: Track / Thread interaction Loading services/mediametrics/HeatMap.h 0 → 100644 +222 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <iomanip> #include <map> #include <sstream> #include "MediaMetricsConstants.h" namespace android::mediametrics { /** * HeatData accumulates statistics on the status reported for a given key. * * HeatData is a helper class used by HeatMap to represent statistics. We expose it * here for testing purposes currently. * * Note: This class is not thread safe, so mutual exclusion should be obtained by the caller * which in this case is HeatMap. HeatMap getData() returns a local copy of HeatData, so use * of that is thread-safe. */ class HeatData { /* HeatData for a key is stored in a map based on the event (e.g. "start", "pause", create) * and then another map based on the status (e.g. "ok", "argument", "state"). */ std::map<std::string /* event */, std::map<std::string /* status name */, size_t /* count, nonzero */>> mMap; public: /** * Add status data. * * \param suffix (ignored) the suffix to the key that was stripped, if any. * \param event the event (e.g. create, start, pause, stop, etc.). * \param uid (ignored) the uid associated with the error. * \param message (ignored) the status message, if any. * \param subCode (ignored) the status subcode, if any. */ void add(const std::string& suffix, const std::string& event, const std::string& status, uid_t uid, const std::string& message, int32_t subCode) { // Perhaps there could be a more detailed print. (void)suffix; (void)uid; (void)message; (void)subCode; ++mMap[event][status]; } /** Returns the number of event names with status. */ size_t size() const { return mMap.size(); } /** * Returns a deque with pairs indicating the count of Oks and Errors. * The first pair is total, the other pairs are in order of mMap. * * Example return value of {ok, error} pairs: * total key1 key2 * { { 2, 1 }, { 1, 0 }, { 1, 1 } } */ std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heatCount() const { size_t totalOk = 0; size_t totalError = 0; std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heat; for (const auto &eventPair : mMap) { size_t ok = 0; size_t error = 0; for (const auto &[name, count] : eventPair.second) { if (name == AMEDIAMETRICS_PROP_ERROR_VALUE_OK) { ok += count; } else { error += count; } } totalOk += ok; totalError += error; heat.emplace_back(ok, error); } heat.emplace_front(totalOk, totalError); return heat; } /** Returns the error fraction from a pair <oks, errors>, a float between 0.f to 1.f. */ static float fraction(const std::pair<size_t, size_t>& count) { return (float)count.second / (count.first + count.second); } /** Returns the HeatMap information in a single line string. */ std::string dump() const { const auto heat = heatCount(); auto it = heat.begin(); std::stringstream ss; ss << "{ "; float errorFraction = fraction(*it++); if (errorFraction > 0.f) { ss << std::fixed << std::setprecision(2) << errorFraction << " "; } for (const auto &eventPair : mMap) { ss << eventPair.first << ": { "; errorFraction = fraction(*it++); if (errorFraction > 0.f) { ss << std::fixed << std::setprecision(2) << errorFraction << " "; } for (const auto &[name, count]: eventPair.second) { ss << "[ " << name << " : " << count << " ] "; } ss << "} "; } ss << " }"; return ss.str(); } }; /** * HeatMap is a thread-safe collection that counts activity of status errors per key. * * The classic heat map is a 2D picture with intensity shown by color. * Here we accumulate the status results from keys to see if there are consistent * failures in the system. * * TODO(b/210855555): Heatmap improvements. * 1) heat decays in intensity in time for past events, currently we don't decay. */ class HeatMap { const size_t mMaxSize; mutable std::mutex mLock; size_t mRejected GUARDED_BY(mLock) = 0; std::map<std::string, HeatData> mMap GUARDED_BY(mLock); public: /** * Constructs a HeatMap. * * \param maxSize the maximum number of elements that are tracked. */ explicit HeatMap(size_t maxSize) : mMaxSize(maxSize) { } /** Returns the number of keys. */ size_t size() const { std::lock_guard l(mLock); return mMap.size(); } /** Clears error history. */ void clear() { std::lock_guard l(mLock); return mMap.clear(); } /** Returns number of keys rejected due to space. */ size_t rejected() const { std::lock_guard l(mLock); return mRejected; } /** Returns a copy of the heat data associated with key. */ HeatData getData(const std::string& key) const { std::lock_guard l(mLock); return mMap.count(key) == 0 ? HeatData{} : mMap.at(key); } /** * Adds a new entry. * \param key the key category (e.g. audio.track). * \param suffix (ignored) the suffix to the key that was stripped, if any. * \param event the event (e.g. create, start, pause, stop, etc.). * \param uid (ignored) the uid associated with the error. * \param message (ignored) the status message, if any. * \param subCode (ignored) the status subcode, if any. */ void add(const std::string& key, const std::string& suffix, const std::string& event, const std::string& status, uid_t uid, const std::string& message, int32_t subCode) { std::lock_guard l(mLock); // Hard limit on heat map entries. // TODO: have better GC. if (mMap.size() == mMaxSize && mMap.count(key) == 0) { ++mRejected; return; } mMap[key].add(suffix, event, status, uid, message, subCode); } /** * Returns a pair consisting of the dump string and the number of lines in the string. */ std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const { std::stringstream ss; int32_t ll = lines; std::lock_guard l(mLock); if (ll > 0) { ss << "Error Heat Map (rejected: " << mRejected << "):\n"; --ll; } // TODO: restriction is implemented alphabetically not on priority. for (const auto& [name, data] : mMap) { if (ll <= 0) break; ss << name << ": " << data.dump() << "\n"; --ll; } return { ss.str(), lines - ll }; } }; } // namespace android::mediametrics services/mediametrics/MediaMetricsService.cpp +10 −2 Original line number Diff line number Diff line Loading @@ -319,11 +319,19 @@ status_t MediaMetricsService::dump(int fd, const Vector<String16>& args) result << "-- some lines may be truncated --\n"; } result << "LogSessionId:\n" const int32_t heatLinesToDump = all ? INT32_MAX : 20; const auto [ heatDumpString, heatLines] = mAudioAnalytics.dumpHeatMap(heatLinesToDump); result << "\n" << heatDumpString; if (heatLines == heatLinesToDump) { result << "-- some lines may be truncated --\n"; } result << "\nLogSessionId:\n" << mediametrics::ValidateId::get()->dump(); // Dump the statsd atoms we sent out. result << "Statsd atoms:\n" result << "\nStatsd atoms:\n" << mStatsdLog->dumpToString(" " /* prefix */, all ? STATSD_LOG_LINES_MAX : STATSD_LOG_LINES_DUMP); } Loading Loading
media/libmediametrics/MediaMetricsItem.cpp +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ static const std::unordered_map<std::string, int32_t>& getErrorStringMap() { // This may be found in frameworks/av/media/libmediametrics/include/MediaMetricsConstants.h static std::unordered_map<std::string, int32_t> map{ {"", NO_ERROR}, {AMEDIAMETRICS_PROP_ERROR_VALUE_OK, NO_ERROR}, {AMEDIAMETRICS_PROP_ERROR_VALUE_ARGUMENT, BAD_VALUE}, {AMEDIAMETRICS_PROP_ERROR_VALUE_IO, DEAD_OBJECT}, {AMEDIAMETRICS_PROP_ERROR_VALUE_MEMORY, NO_MEMORY}, Loading
media/libmediametrics/include/MediaMetricsConstants.h +3 −2 Original line number Diff line number Diff line Loading @@ -238,8 +238,9 @@ // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/media/java/android/media/AudioSystem.java;drc=3ac246c43294d7f7012bdcb0ccb7bae1aa695bd4;l=785 // https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libaaudio/include/aaudio/AAudio.h;drc=cfd3a6fa3aaaf712a890dc02452b38ef401083b8;l=120 // Error category: // An empty error string indicates no error. // Status errors: // An empty status string or "ok" is interpreted as no error. #define AMEDIAMETRICS_PROP_ERROR_VALUE_OK "ok" // Error category: argument // IllegalArgumentException Loading
services/mediametrics/AudioAnalytics.h +16 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ #include "AnalyticsActions.h" #include "AnalyticsState.h" #include "AudioPowerUsage.h" #include "HeatMap.h" #include "StatsdLog.h" #include "TimedAction.h" #include "Wrap.h" Loading Loading @@ -73,11 +74,23 @@ public: std::pair<std::string, int32_t> dump( int32_t lines = INT32_MAX, int64_t sinceNs = 0, const char *prefix = nullptr) const; /** * Returns a pair consisting of the dump string and the number of lines in the string. * * HeatMap dump. */ std::pair<std::string, int32_t> dumpHeatMap(int32_t lines = INT32_MAX) const { return mHeatMap.dump(lines); } void clear() { // underlying state is locked. mPreviousAnalyticsState->clear(); mAnalyticsState->clear(); // Clears the status map mHeatMap.clear(); // Clear power usage state. mAudioPowerUsage.clear(); } Loading Loading @@ -124,6 +137,9 @@ private: TimedAction mTimedAction; // locked internally const std::shared_ptr<StatsdLog> mStatsdLog; // locked internally, ok for multiple threads. static constexpr size_t kHeatEntries = 100; HeatMap mHeatMap{kHeatEntries}; // locked internally, ok for multiple threads. // DeviceUse is a nested class which handles audio device usage accounting. // We define this class at the end to ensure prior variables all properly constructed. // TODO: Track / Thread interaction Loading
services/mediametrics/HeatMap.h 0 → 100644 +222 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include <iomanip> #include <map> #include <sstream> #include "MediaMetricsConstants.h" namespace android::mediametrics { /** * HeatData accumulates statistics on the status reported for a given key. * * HeatData is a helper class used by HeatMap to represent statistics. We expose it * here for testing purposes currently. * * Note: This class is not thread safe, so mutual exclusion should be obtained by the caller * which in this case is HeatMap. HeatMap getData() returns a local copy of HeatData, so use * of that is thread-safe. */ class HeatData { /* HeatData for a key is stored in a map based on the event (e.g. "start", "pause", create) * and then another map based on the status (e.g. "ok", "argument", "state"). */ std::map<std::string /* event */, std::map<std::string /* status name */, size_t /* count, nonzero */>> mMap; public: /** * Add status data. * * \param suffix (ignored) the suffix to the key that was stripped, if any. * \param event the event (e.g. create, start, pause, stop, etc.). * \param uid (ignored) the uid associated with the error. * \param message (ignored) the status message, if any. * \param subCode (ignored) the status subcode, if any. */ void add(const std::string& suffix, const std::string& event, const std::string& status, uid_t uid, const std::string& message, int32_t subCode) { // Perhaps there could be a more detailed print. (void)suffix; (void)uid; (void)message; (void)subCode; ++mMap[event][status]; } /** Returns the number of event names with status. */ size_t size() const { return mMap.size(); } /** * Returns a deque with pairs indicating the count of Oks and Errors. * The first pair is total, the other pairs are in order of mMap. * * Example return value of {ok, error} pairs: * total key1 key2 * { { 2, 1 }, { 1, 0 }, { 1, 1 } } */ std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heatCount() const { size_t totalOk = 0; size_t totalError = 0; std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heat; for (const auto &eventPair : mMap) { size_t ok = 0; size_t error = 0; for (const auto &[name, count] : eventPair.second) { if (name == AMEDIAMETRICS_PROP_ERROR_VALUE_OK) { ok += count; } else { error += count; } } totalOk += ok; totalError += error; heat.emplace_back(ok, error); } heat.emplace_front(totalOk, totalError); return heat; } /** Returns the error fraction from a pair <oks, errors>, a float between 0.f to 1.f. */ static float fraction(const std::pair<size_t, size_t>& count) { return (float)count.second / (count.first + count.second); } /** Returns the HeatMap information in a single line string. */ std::string dump() const { const auto heat = heatCount(); auto it = heat.begin(); std::stringstream ss; ss << "{ "; float errorFraction = fraction(*it++); if (errorFraction > 0.f) { ss << std::fixed << std::setprecision(2) << errorFraction << " "; } for (const auto &eventPair : mMap) { ss << eventPair.first << ": { "; errorFraction = fraction(*it++); if (errorFraction > 0.f) { ss << std::fixed << std::setprecision(2) << errorFraction << " "; } for (const auto &[name, count]: eventPair.second) { ss << "[ " << name << " : " << count << " ] "; } ss << "} "; } ss << " }"; return ss.str(); } }; /** * HeatMap is a thread-safe collection that counts activity of status errors per key. * * The classic heat map is a 2D picture with intensity shown by color. * Here we accumulate the status results from keys to see if there are consistent * failures in the system. * * TODO(b/210855555): Heatmap improvements. * 1) heat decays in intensity in time for past events, currently we don't decay. */ class HeatMap { const size_t mMaxSize; mutable std::mutex mLock; size_t mRejected GUARDED_BY(mLock) = 0; std::map<std::string, HeatData> mMap GUARDED_BY(mLock); public: /** * Constructs a HeatMap. * * \param maxSize the maximum number of elements that are tracked. */ explicit HeatMap(size_t maxSize) : mMaxSize(maxSize) { } /** Returns the number of keys. */ size_t size() const { std::lock_guard l(mLock); return mMap.size(); } /** Clears error history. */ void clear() { std::lock_guard l(mLock); return mMap.clear(); } /** Returns number of keys rejected due to space. */ size_t rejected() const { std::lock_guard l(mLock); return mRejected; } /** Returns a copy of the heat data associated with key. */ HeatData getData(const std::string& key) const { std::lock_guard l(mLock); return mMap.count(key) == 0 ? HeatData{} : mMap.at(key); } /** * Adds a new entry. * \param key the key category (e.g. audio.track). * \param suffix (ignored) the suffix to the key that was stripped, if any. * \param event the event (e.g. create, start, pause, stop, etc.). * \param uid (ignored) the uid associated with the error. * \param message (ignored) the status message, if any. * \param subCode (ignored) the status subcode, if any. */ void add(const std::string& key, const std::string& suffix, const std::string& event, const std::string& status, uid_t uid, const std::string& message, int32_t subCode) { std::lock_guard l(mLock); // Hard limit on heat map entries. // TODO: have better GC. if (mMap.size() == mMaxSize && mMap.count(key) == 0) { ++mRejected; return; } mMap[key].add(suffix, event, status, uid, message, subCode); } /** * Returns a pair consisting of the dump string and the number of lines in the string. */ std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const { std::stringstream ss; int32_t ll = lines; std::lock_guard l(mLock); if (ll > 0) { ss << "Error Heat Map (rejected: " << mRejected << "):\n"; --ll; } // TODO: restriction is implemented alphabetically not on priority. for (const auto& [name, data] : mMap) { if (ll <= 0) break; ss << name << ": " << data.dump() << "\n"; --ll; } return { ss.str(), lines - ll }; } }; } // namespace android::mediametrics
services/mediametrics/MediaMetricsService.cpp +10 −2 Original line number Diff line number Diff line Loading @@ -319,11 +319,19 @@ status_t MediaMetricsService::dump(int fd, const Vector<String16>& args) result << "-- some lines may be truncated --\n"; } result << "LogSessionId:\n" const int32_t heatLinesToDump = all ? INT32_MAX : 20; const auto [ heatDumpString, heatLines] = mAudioAnalytics.dumpHeatMap(heatLinesToDump); result << "\n" << heatDumpString; if (heatLines == heatLinesToDump) { result << "-- some lines may be truncated --\n"; } result << "\nLogSessionId:\n" << mediametrics::ValidateId::get()->dump(); // Dump the statsd atoms we sent out. result << "Statsd atoms:\n" result << "\nStatsd atoms:\n" << mStatsdLog->dumpToString(" " /* prefix */, all ? STATSD_LOG_LINES_MAX : STATSD_LOG_LINES_DUMP); } Loading