223 lines
7.5 KiB
C++
223 lines
7.5 KiB
C++
/*
|
|
* 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_STATUS_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
|