/*
* Copyright (C) 2017 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.
*/
#define LOG_TAG "MetricsSummarizer"
#include <utils/Log.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <utils/threads.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
#include <utils/List.h>
#include <media/IMediaAnalyticsService.h>
#include "MetricsSummarizer.h"
namespace android {
#define DEBUG_SORT 0
#define DEBUG_QUEUE 0
MetricsSummarizer::MetricsSummarizer(const char *key)
: mIgnorables(NULL)
{
ALOGV("MetricsSummarizer::MetricsSummarizer");
if (key == NULL) {
mKey = key;
} else {
mKey = strdup(key);
}
mSummaries = new List<MediaAnalyticsItem *>();
}
MetricsSummarizer::~MetricsSummarizer()
{
ALOGV("MetricsSummarizer::~MetricsSummarizer");
if (mKey) {
free((void *)mKey);
mKey = NULL;
}
// clear the list of items we have saved
while (mSummaries->size() > 0) {
MediaAnalyticsItem * oitem = *(mSummaries->begin());
if (DEBUG_QUEUE) {
ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
oitem->getKey().c_str(), oitem->getSessionID(),
oitem->getTimestamp());
}
mSummaries->erase(mSummaries->begin());
delete oitem;
}
}
// so we know what summarizer we were using
const char *MetricsSummarizer::getKey() {
const char *value = mKey;
if (value == NULL) {
value = "unknown";
}
return value;
}
// should the record be given to this summarizer
bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
{
if (mKey == NULL)
return true;
AString itemKey = item.getKey();
if (strcmp(mKey, itemKey.c_str()) != 0) {
return false;
}
return true;
}
AString MetricsSummarizer::dumpSummary(int &slot)
{
return dumpSummary(slot, NULL);
}
AString MetricsSummarizer::dumpSummary(int &slot, const char *only)
{
AString value = "";
List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
if (it != mSummaries->end()) {
char buf[16]; // enough for "#####: "
for (; it != mSummaries->end(); it++) {
if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
continue;
}
AString entry = (*it)->toString();
snprintf(buf, sizeof(buf), "%5d: ", slot);
value.append(buf);
value.append(entry.c_str());
value.append("\n");
slot++;
}
}
return value;
}
void MetricsSummarizer::setIgnorables(const char **ignorables) {
mIgnorables = ignorables;
}
const char **MetricsSummarizer::getIgnorables() {
return mIgnorables;
}
void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
ALOGV("MetricsSummarizer::handleRecord() for %s",
item == NULL ? "<nothing>" : item->toString().c_str());
if (item == NULL) {
return;
}
List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
for (; it != mSummaries->end(); it++) {
bool good = sameAttributes((*it), item, getIgnorables());
ALOGV("Match against %s says %d",
(*it)->toString().c_str(), good);
if (good)
break;
}
if (it == mSummaries->end()) {
ALOGV("save new record");
item = item->dup();
if (item == NULL) {
ALOGE("unable to save MediaMetrics record");
}
sortProps(item);
item->setInt32("aggregated",1);
mSummaries->push_back(item);
} else {
ALOGV("increment existing record");
(*it)->addInt32("aggregated",1);
mergeRecord(*(*it), *item);
}
}
void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
// default is no further massaging.
ALOGV("MetricsSummarizer::mergeRecord() [default]");
return;
}
//
// Comparators
//
// testing that all of 'single' is in 'summ'
// and that the values match.
// 'summ' may have extra fields.
// 'ignorable' is a set of things that we don't worry about matching up
// (usually time- or count-based values we'll sum elsewhere)
bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
if (single == NULL || summ == NULL) {
return false;
}
ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
// this can be made better.
for(size_t i=0;i<single->mPropCount;i++) {
MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
const char *attrName = prop1->mName;
ALOGV("compare on attr '%s'", attrName);
// is it something we should ignore
if (ignorable != NULL) {
const char **ig = ignorable;
while (*ig) {
if (strcmp(*ig, attrName) == 0) {
break;
}
ig++;
}
if (*ig) {
ALOGV("we don't mind that it has attr '%s'", attrName);
continue;
}
}
MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
if (prop2 == NULL) {
ALOGV("summ doesn't have this attr");
return false;
}
if (prop1->mType != prop2->mType) {
ALOGV("mismatched attr types");
return false;
}
switch (prop1->mType) {
case MediaAnalyticsItem::kTypeInt32:
if (prop1->u.int32Value != prop2->u.int32Value)
return false;
break;
case MediaAnalyticsItem::kTypeInt64:
if (prop1->u.int64Value != prop2->u.int64Value)
return false;
break;
case MediaAnalyticsItem::kTypeDouble:
// XXX: watch out for floating point comparisons!
if (prop1->u.doubleValue != prop2->u.doubleValue)
return false;
break;
case MediaAnalyticsItem::kTypeCString:
if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0)
return false;
break;
case MediaAnalyticsItem::kTypeRate:
if (prop1->u.rate.count != prop2->u.rate.count)
return false;
if (prop1->u.rate.duration != prop2->u.rate.duration)
return false;
break;
default:
return false;
}
}
return true;
}
bool MetricsSummarizer::sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
// verify same user
if (summ->mPid != single->mPid)
return false;
// and finally do the more expensive validation of the attributes
return sameAttributes(summ, single, ignorable);
}
int MetricsSummarizer::PropSorter(const void *a, const void *b) {
MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
return strcmp(ai->mName, bi->mName);
}
// we sort in the summaries so that it looks pretty in the dumpsys
void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
if (item->mPropCount != 0) {
if (DEBUG_SORT) {
ALOGD("sortProps(pre): %s", item->toString().c_str());
}
qsort(item->mProps, item->mPropCount,
sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
if (DEBUG_SORT) {
ALOGD("sortProps(pst): %s", item->toString().c_str());
}
}
}
} // namespace android