1 /*
2  * Copyright (C) 2017 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 #define LOG_TAG "MetricsSummarizer"
18 #include <utils/Log.h>
19 
20 #include <stdlib.h>
21 #include <stdint.h>
22 #include <inttypes.h>
23 
24 #include <utils/threads.h>
25 #include <utils/Errors.h>
26 #include <utils/KeyedVector.h>
27 #include <utils/String8.h>
28 #include <utils/List.h>
29 
30 #include <media/IMediaAnalyticsService.h>
31 
32 #include "MetricsSummarizer.h"
33 
34 
35 namespace android {
36 
37 #define DEBUG_SORT      0
38 #define DEBUG_QUEUE     0
39 
40 
MetricsSummarizer(const char * key)41 MetricsSummarizer::MetricsSummarizer(const char *key)
42     : mIgnorables(NULL)
43 {
44     ALOGV("MetricsSummarizer::MetricsSummarizer");
45 
46     if (key == NULL) {
47         mKey = key;
48     } else {
49         mKey = strdup(key);
50     }
51 
52     mSummaries = new List<MediaAnalyticsItem *>();
53 }
54 
~MetricsSummarizer()55 MetricsSummarizer::~MetricsSummarizer()
56 {
57     ALOGV("MetricsSummarizer::~MetricsSummarizer");
58     if (mKey) {
59         free((void *)mKey);
60         mKey = NULL;
61     }
62 
63     // clear the list of items we have saved
64     while (mSummaries->size() > 0) {
65         MediaAnalyticsItem * oitem = *(mSummaries->begin());
66         if (DEBUG_QUEUE) {
67             ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
68                 oitem->getKey().c_str(), oitem->getSessionID(),
69                 oitem->getTimestamp());
70         }
71         mSummaries->erase(mSummaries->begin());
72         delete oitem;
73     }
74 }
75 
76 // so we know what summarizer we were using
getKey()77 const char *MetricsSummarizer::getKey() {
78     const char *value = mKey;
79     if (value == NULL) {
80         value = "unknown";
81     }
82     return value;
83 }
84 
85 // should the record be given to this summarizer
isMine(MediaAnalyticsItem & item)86 bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
87 {
88     if (mKey == NULL)
89         return true;
90     AString itemKey = item.getKey();
91     if (strcmp(mKey, itemKey.c_str()) != 0) {
92         return false;
93     }
94     return true;
95 }
96 
dumpSummary(int & slot)97 AString MetricsSummarizer::dumpSummary(int &slot)
98 {
99     return dumpSummary(slot, NULL);
100 }
101 
dumpSummary(int & slot,const char * only)102 AString MetricsSummarizer::dumpSummary(int &slot, const char *only)
103 {
104     AString value = "";
105 
106     List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
107     if (it != mSummaries->end()) {
108         char buf[16];   // enough for "#####: "
109         for (; it != mSummaries->end(); it++) {
110             if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
111                 continue;
112             }
113             AString entry = (*it)->toString();
114             snprintf(buf, sizeof(buf), "%5d: ", slot);
115             value.append(buf);
116             value.append(entry.c_str());
117             value.append("\n");
118             slot++;
119         }
120     }
121     return value;
122 }
123 
setIgnorables(const char ** ignorables)124 void MetricsSummarizer::setIgnorables(const char **ignorables) {
125     mIgnorables = ignorables;
126 }
127 
getIgnorables()128 const char **MetricsSummarizer::getIgnorables() {
129     return mIgnorables;
130 }
131 
handleRecord(MediaAnalyticsItem * item)132 void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
133 
134     ALOGV("MetricsSummarizer::handleRecord() for %s",
135                 item == NULL ? "<nothing>" : item->toString().c_str());
136 
137     if (item == NULL) {
138         return;
139     }
140 
141     List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
142     for (; it != mSummaries->end(); it++) {
143         bool good = sameAttributes((*it), item, getIgnorables());
144         ALOGV("Match against %s says %d",
145               (*it)->toString().c_str(), good);
146         if (good)
147             break;
148     }
149     if (it == mSummaries->end()) {
150             ALOGV("save new record");
151             item = item->dup();
152             if (item == NULL) {
153                 ALOGE("unable to save MediaMetrics record");
154             }
155             sortProps(item);
156             item->setInt32("aggregated",1);
157             mSummaries->push_back(item);
158     } else {
159             ALOGV("increment existing record");
160             (*it)->addInt32("aggregated",1);
161             mergeRecord(*(*it), *item);
162     }
163 }
164 
mergeRecord(MediaAnalyticsItem &,MediaAnalyticsItem &)165 void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
166     // default is no further massaging.
167     ALOGV("MetricsSummarizer::mergeRecord() [default]");
168     return;
169 }
170 
171 
172 //
173 // Comparators
174 //
175 
176 // testing that all of 'single' is in 'summ'
177 // and that the values match.
178 // 'summ' may have extra fields.
179 // 'ignorable' is a set of things that we don't worry about matching up
180 // (usually time- or count-based values we'll sum elsewhere)
sameAttributes(MediaAnalyticsItem * summ,MediaAnalyticsItem * single,const char ** ignorable)181 bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
182 
183     if (single == NULL || summ == NULL) {
184         return false;
185     }
186     ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
187     ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
188 
189     // this can be made better.
190     for(size_t i=0;i<single->mPropCount;i++) {
191         MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
192         const char *attrName = prop1->mName;
193         ALOGV("compare on attr '%s'", attrName);
194 
195         // is it something we should ignore
196         if (ignorable != NULL) {
197             const char **ig = ignorable;
198             while (*ig) {
199                 if (strcmp(*ig, attrName) == 0) {
200                     break;
201                 }
202                 ig++;
203             }
204             if (*ig) {
205                 ALOGV("we don't mind that it has attr '%s'", attrName);
206                 continue;
207             }
208         }
209 
210         MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
211         if (prop2 == NULL) {
212             ALOGV("summ doesn't have this attr");
213             return false;
214         }
215         if (prop1->mType != prop2->mType) {
216             ALOGV("mismatched attr types");
217             return false;
218         }
219         switch (prop1->mType) {
220             case MediaAnalyticsItem::kTypeInt32:
221                 if (prop1->u.int32Value != prop2->u.int32Value)
222                     return false;
223                 break;
224             case MediaAnalyticsItem::kTypeInt64:
225                 if (prop1->u.int64Value != prop2->u.int64Value)
226                     return false;
227                 break;
228             case MediaAnalyticsItem::kTypeDouble:
229                 // XXX: watch out for floating point comparisons!
230                 if (prop1->u.doubleValue != prop2->u.doubleValue)
231                     return false;
232                 break;
233             case MediaAnalyticsItem::kTypeCString:
234                 if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0)
235                     return false;
236                 break;
237             case MediaAnalyticsItem::kTypeRate:
238                 if (prop1->u.rate.count != prop2->u.rate.count)
239                     return false;
240                 if (prop1->u.rate.duration != prop2->u.rate.duration)
241                     return false;
242                 break;
243             default:
244                 return false;
245         }
246     }
247 
248     return true;
249 }
250 
sameAttributesId(MediaAnalyticsItem * summ,MediaAnalyticsItem * single,const char ** ignorable)251 bool MetricsSummarizer::sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
252 
253     // verify same user
254     if (summ->mPid != single->mPid)
255         return false;
256 
257     // and finally do the more expensive validation of the attributes
258     return sameAttributes(summ, single, ignorable);
259 }
260 
PropSorter(const void * a,const void * b)261 int MetricsSummarizer::PropSorter(const void *a, const void *b) {
262     MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
263     MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
264     return strcmp(ai->mName, bi->mName);
265 }
266 
267 // we sort in the summaries so that it looks pretty in the dumpsys
sortProps(MediaAnalyticsItem * item)268 void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
269     if (item->mPropCount != 0) {
270         if (DEBUG_SORT) {
271             ALOGD("sortProps(pre): %s", item->toString().c_str());
272         }
273         qsort(item->mProps, item->mPropCount,
274               sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
275         if (DEBUG_SORT) {
276             ALOGD("sortProps(pst): %s", item->toString().c_str());
277         }
278     }
279 }
280 
281 } // namespace android
282