• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 <iomanip>
20 #include <map>
21 #include <sstream>
22 #include "MediaMetricsConstants.h"
23 
24 namespace android::mediametrics {
25 
26 /**
27  * HeatData accumulates statistics on the status reported for a given key.
28  *
29  * HeatData is a helper class used by HeatMap to represent statistics.  We expose it
30  * here for testing purposes currently.
31  *
32  * Note: This class is not thread safe, so mutual exclusion should be obtained by the caller
33  * which in this case is HeatMap.  HeatMap getData() returns a local copy of HeatData, so use
34  * of that is thread-safe.
35  */
36 class HeatData {
37     /* HeatData for a key is stored in a map based on the event (e.g. "start", "pause", create)
38      * and then another map based on the status (e.g. "ok", "argument", "state").
39      */
40     std::map<std::string /* event */,
41              std::map<std::string /* status name */, size_t /* count, nonzero */>> mMap;
42 
43 public:
44     /**
45      * Add status data.
46      *
47      * \param suffix  (ignored) the suffix to the key that was stripped, if any.
48      * \param event             the event (e.g. create, start, pause, stop, etc.).
49      * \param uid     (ignored) the uid associated with the error.
50      * \param message (ignored) the status message, if any.
51      * \param subCode (ignored) the status subcode, if any.
52      */
add(const std::string & suffix,const std::string & event,const std::string & status,uid_t uid,const std::string & message,int32_t subCode)53     void add(const std::string& suffix, const std::string& event, const std::string& status,
54             uid_t uid, const std::string& message, int32_t subCode) {
55         // Perhaps there could be a more detailed print.
56         (void)suffix;
57         (void)uid;
58         (void)message;
59         (void)subCode;
60         ++mMap[event][status];
61     }
62 
63     /** Returns the number of event names with status. */
size()64     size_t size() const {
65         return mMap.size();
66     }
67 
68     /**
69      * Returns a deque with pairs indicating the count of Oks and Errors.
70      * The first pair is total, the other pairs are in order of mMap.
71      *
72      * Example return value of {ok, error} pairs:
73      *     total     key1      key2
74      * { { 2, 1 }, { 1, 0 }, { 1, 1 } }
75      */
heatCount()76     std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heatCount() const {
77         size_t totalOk = 0;
78         size_t totalError = 0;
79         std::deque<std::pair<size_t /* oks */, size_t /* errors */>> heat;
80         for (const auto &eventPair : mMap) {
81             size_t ok = 0;
82             size_t error = 0;
83             for (const auto &[name, count] : eventPair.second) {
84                 if (name == AMEDIAMETRICS_PROP_STATUS_VALUE_OK) {
85                     ok += count;
86                 } else {
87                     error += count;
88                 }
89             }
90             totalOk += ok;
91             totalError += error;
92             heat.emplace_back(ok, error);
93         }
94         heat.emplace_front(totalOk, totalError);
95         return heat;
96     }
97 
98     /** Returns the error fraction from a pair <oks, errors>, a float between 0.f to 1.f. */
fraction(const std::pair<size_t,size_t> & count)99     static float fraction(const std::pair<size_t, size_t>& count) {
100         return (float)count.second / (count.first + count.second);
101     }
102 
103     /** Returns the HeatMap information in a single line string. */
dump()104     std::string dump() const {
105         const auto heat = heatCount();
106         auto it = heat.begin();
107         std::stringstream ss;
108         ss << "{ ";
109         float errorFraction = fraction(*it++);
110         if (errorFraction > 0.f) {
111             ss << std::fixed << std::setprecision(2) << errorFraction << " ";
112         }
113         for (const auto &eventPair : mMap) {
114             ss << eventPair.first << ": { ";
115             errorFraction = fraction(*it++);
116             if (errorFraction > 0.f) {
117                 ss << std::fixed << std::setprecision(2) << errorFraction << " ";
118             }
119             for (const auto &[name, count]: eventPair.second) {
120                 ss << "[ " << name << " : " << count << " ] ";
121             }
122             ss << "} ";
123         }
124         ss << " }";
125         return ss.str();
126     }
127 };
128 
129 /**
130  * HeatMap is a thread-safe collection that counts activity of status errors per key.
131  *
132  * The classic heat map is a 2D picture with intensity shown by color.
133  * Here we accumulate the status results from keys to see if there are consistent
134  * failures in the system.
135  *
136  * TODO(b/210855555): Heatmap improvements.
137  *   1) heat decays in intensity in time for past events, currently we don't decay.
138  */
139 
140 class HeatMap {
141     const size_t mMaxSize;
142     mutable std::mutex mLock;
143     size_t mRejected GUARDED_BY(mLock) = 0;
144     std::map<std::string, HeatData> mMap GUARDED_BY(mLock);
145 
146 public:
147     /**
148      * Constructs a HeatMap.
149      *
150      * \param maxSize the maximum number of elements that are tracked.
151      */
HeatMap(size_t maxSize)152     explicit HeatMap(size_t maxSize) : mMaxSize(maxSize) {
153     }
154 
155     /** Returns the number of keys. */
size()156     size_t size() const {
157         std::lock_guard l(mLock);
158         return mMap.size();
159     }
160 
161     /** Clears error history. */
clear()162     void clear() {
163         std::lock_guard l(mLock);
164         return mMap.clear();
165     }
166 
167     /** Returns number of keys rejected due to space. */
rejected()168     size_t rejected() const {
169         std::lock_guard l(mLock);
170         return mRejected;
171     }
172 
173     /** Returns a copy of the heat data associated with key. */
getData(const std::string & key)174     HeatData getData(const std::string& key) const {
175         std::lock_guard l(mLock);
176         return mMap.count(key) == 0 ? HeatData{} : mMap.at(key);
177     }
178 
179     /**
180      * Adds a new entry.
181      * \param key               the key category (e.g. audio.track).
182      * \param suffix  (ignored) the suffix to the key that was stripped, if any.
183      * \param event             the event (e.g. create, start, pause, stop, etc.).
184      * \param uid     (ignored) the uid associated with the error.
185      * \param message (ignored) the status message, if any.
186      * \param subCode (ignored) the status subcode, if any.
187      */
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)188     void add(const std::string& key, const std::string& suffix, const std::string& event,
189             const std::string& status, uid_t uid, const std::string& message, int32_t subCode) {
190         std::lock_guard l(mLock);
191 
192         // Hard limit on heat map entries.
193         // TODO: have better GC.
194         if (mMap.size() == mMaxSize && mMap.count(key) == 0) {
195             ++mRejected;
196             return;
197         }
198         mMap[key].add(suffix, event, status, uid, message, subCode);
199     }
200 
201     /**
202      * Returns a pair consisting of the dump string and the number of lines in the string.
203      */
204     std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const {
205         std::stringstream ss;
206         int32_t ll = lines;
207         std::lock_guard l(mLock);
208         if (ll > 0) {
209             ss << "Error Heat Map (rejected: " << mRejected << "):\n";
210             --ll;
211         }
212         // TODO: restriction is implemented alphabetically not on priority.
213         for (const auto& [name, data] : mMap) {
214             if (ll <= 0) break;
215             ss << name << ": " << data.dump() << "\n";
216             --ll;
217         }
218         return { ss.str(), lines - ll };
219     }
220 };
221 
222 } // namespace android::mediametrics
223