MediaMetrics: Update to use STL containers
Test: dumpsys media.metrics sanity
Change-Id: Ifcada098e755ef3d679715ed23fab9d793ebde3e
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index b7856a6..c8713bf 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -234,10 +234,6 @@
return *this;
}
-MediaAnalyticsItem::Key MediaAnalyticsItem::getKey() {
- return mKey;
-}
-
// number of attributes we have in this record
int32_t MediaAnalyticsItem::count() const {
return mPropCount;
@@ -795,11 +791,11 @@
return strdup(val.c_str());
}
-std::string MediaAnalyticsItem::toString() {
+std::string MediaAnalyticsItem::toString() const {
return toString(PROTO_LAST);
}
-std::string MediaAnalyticsItem::toString(int version) {
+std::string MediaAnalyticsItem::toString(int version) const {
// v0 : released with 'o'
// v1 : bug fix (missing pid/finalized separator),
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index 42a2f5b..3024c84 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -119,7 +119,7 @@
// set the key discriminator for the record.
// most often initialized as part of the constructor
MediaAnalyticsItem &setKey(MediaAnalyticsItem::Key);
- MediaAnalyticsItem::Key getKey();
+ const MediaAnalyticsItem::Key& getKey() const { return mKey; }
// # of attributes in the record
int32_t count() const;
@@ -191,8 +191,8 @@
// supports the stable interface
bool dumpAttributes(char **pbuffer, size_t *plength);
- std::string toString();
- std::string toString(int version);
+ std::string toString() const;
+ std::string toString(int version) const;
const char *toCString();
const char *toCString(int version);
diff --git a/services/mediaanalytics/Android.bp b/services/mediaanalytics/Android.bp
index c27aced..80816d7 100644
--- a/services/mediaanalytics/Android.bp
+++ b/services/mediaanalytics/Android.bp
@@ -5,9 +5,9 @@
name: "mediametrics",
srcs: [
+ "iface_statsd.cpp",
"main_mediametrics.cpp",
"MediaAnalyticsService.cpp",
- "iface_statsd.cpp",
"statsd_audiopolicy.cpp",
"statsd_audiorecord.cpp",
"statsd_audiothread.cpp",
@@ -24,44 +24,28 @@
},
shared_libs: [
- "libcutils",
- "liblog",
- "libmedia",
- "libutils",
"libbinder",
- "libdl",
- "libgui",
- "libmedia",
- "libmediautils",
+ "liblog",
"libmediametrics",
- "libstagefright_foundation",
+ "libprotobuf-cpp-lite",
"libstatslog",
"libutils",
- "libprotobuf-cpp-lite",
],
static_libs: [
"libplatformprotos",
- "libregistermsext",
],
include_dirs: [
- "frameworks/av/media/libstagefright/include",
- "frameworks/av/media/libstagefright/rtsp",
- "frameworks/av/media/libstagefright/webm",
- "frameworks/av/include/media",
- "frameworks/av/camera/include/camera",
- "frameworks/native/include/media/openmax",
- "frameworks/native/include/media/hardware",
- "external/tremolo/Tremolo",
+ "system/media/audio_utils/include"
],
init_rc: ["mediametrics.rc"],
cflags: [
- "-Werror",
"-Wall",
- "-Wno-error=deprecated-declarations",
+ "-Werror",
+ "-Wextra",
],
clang: true,
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 988c06b..42afc6b 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -14,66 +14,20 @@
* limitations under the License.
*/
-// Proxy for media player implementations
-
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaAnalyticsService"
#include <utils/Log.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <dirent.h>
-#include <pthread.h>
-#include <unistd.h>
-
-#include <string.h>
-#include <pwd.h>
-
-#include <cutils/atomic.h>
-#include <cutils/properties.h> // for property_get
-
-#include <utils/misc.h>
-
-#include <android/content/pm/IPackageManagerNative.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/MemoryHeapBase.h>
-#include <binder/MemoryBase.h>
-#include <gui/Surface.h>
-#include <utils/Errors.h> // for status_t
-#include <utils/List.h>
-#include <utils/String8.h>
-#include <utils/SystemClock.h>
-#include <utils/Timers.h>
-#include <utils/Vector.h>
-
-#include <media/IMediaHTTPService.h>
-#include <media/IRemoteDisplay.h>
-#include <media/IRemoteDisplayClient.h>
-#include <media/MediaPlayerInterface.h>
-#include <media/mediarecorder.h>
-#include <media/MediaMetadataRetrieverInterface.h>
-#include <media/Metadata.h>
-#include <media/AudioTrack.h>
-#include <media/MemoryLeakTrackUtil.h>
-#include <media/stagefright/MediaCodecList.h>
-#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/Utils.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/ALooperRoster.h>
-#include <mediautils/BatteryNotifier.h>
-
-//#include <memunreachable/memunreachable.h>
-#include <system/audio.h>
-
-#include <private/android_filesystem_config.h>
-
#include "MediaAnalyticsService.h"
+#include <pwd.h> //getpwuid
+
+#include <audio_utils/clock.h> // clock conversions
+#include <android/content/pm/IPackageManagerNative.h> // package info
+#include <binder/IPCThreadState.h> // get calling uid
+#include <cutils/properties.h> // for property_get
+#include <private/android_filesystem_config.h> // UID
+
namespace android {
// individual records kept in memory: age or count
@@ -81,72 +35,45 @@
// count: hard limit of # records
// (0 for either of these disables that threshold)
//
-static constexpr nsecs_t kMaxRecordAgeNs = 28 * 3600 * (1000*1000*1000ll);
+static constexpr nsecs_t kMaxRecordAgeNs = 28 * 3600 * NANOS_PER_SECOND;
// 2019/6: average daily per device is currently 375-ish;
// setting this to 2000 is large enough to catch most devices
// we'll lose some data on very very media-active devices, but only for
// the gms collection; statsd will have already covered those for us.
// This also retains enough information to help with bugreports
-static constexpr int kMaxRecords = 2000;
+static constexpr size_t kMaxRecords = 2000;
// max we expire in a single call, to constrain how long we hold the
// mutex, which also constrains how long a client might wait.
-static constexpr int kMaxExpiredAtOnce = 50;
+static constexpr size_t kMaxExpiredAtOnce = 50;
// TODO: need to look at tuning kMaxRecords and friends for low-memory devices
-static const char *kServiceName = "media.metrics";
-
-void MediaAnalyticsService::instantiate() {
- defaultServiceManager()->addService(
- String16(kServiceName), new MediaAnalyticsService());
-}
-
MediaAnalyticsService::MediaAnalyticsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
mMaxRecordsExpiredAtOnce(kMaxExpiredAtOnce),
- mDumpProto(MediaAnalyticsItem::PROTO_V1),
- mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1) {
-
- ALOGD("MediaAnalyticsService created");
-
- mItemsSubmitted = 0;
- mItemsFinalized = 0;
- mItemsDiscarded = 0;
- mItemsDiscardedExpire = 0;
- mItemsDiscardedCount = 0;
-
- mLastSessionID = 0;
- // recover any persistency we set up
- // etc
+ mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1)
+{
+ ALOGD("%s", __func__);
}
-MediaAnalyticsService::~MediaAnalyticsService() {
- ALOGD("MediaAnalyticsService destroyed");
-
- while (mItems.size() > 0) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
- mItemsDiscardedCount++;
- }
+MediaAnalyticsService::~MediaAnalyticsService()
+{
+ ALOGD("%s", __func__);
+ // the class destructor clears anyhow, but we enforce clearing items first.
+ mItemsDiscarded += mItems.size();
+ mItems.clear();
}
-
MediaAnalyticsItem::SessionID_t MediaAnalyticsService::generateUniqueSessionID() {
- // generate a new sessionid
-
- Mutex::Autolock _l(mLock_ids);
- return (++mLastSessionID);
+ return ++mLastSessionID;
}
-// caller surrenders ownership of 'item'
-MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(MediaAnalyticsItem *item, bool forcenew)
+// TODO: consider removing forcenew.
+MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(
+ MediaAnalyticsItem *item, bool forcenew __unused)
{
- UNUSED(forcenew);
-
// fill in a sessionID if we do not yet have one
if (item->getSessionID() <= MediaAnalyticsItem::SessionIDNone) {
item->setSessionID(generateUniqueSessionID());
@@ -155,61 +82,60 @@
// we control these, generally not trusting user input
nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
// round nsecs to seconds
- now = ((now + 500000000) / 1000000000) * 1000000000;
+ now = (now + NANOS_PER_SECOND / 2) / NANOS_PER_SECOND * NANOS_PER_SECOND;
+ // TODO: if we convert to boot time, do we need to round timestamp?
item->setTimestamp(now);
- int pid = IPCThreadState::self()->getCallingPid();
- int uid = IPCThreadState::self()->getCallingUid();
- int uid_given = item->getUid();
- int pid_given = item->getPid();
+ const int pid = IPCThreadState::self()->getCallingPid();
+ const int uid = IPCThreadState::self()->getCallingUid();
+ const int uid_given = item->getUid();
+ const int pid_given = item->getPid();
- // although we do make exceptions for some trusted client uids
- bool isTrusted = false;
-
- ALOGV("caller has uid=%d, embedded uid=%d", uid, uid_given);
-
- switch (uid) {
- case AID_DRM:
- case AID_MEDIA:
- case AID_MEDIA_CODEC:
- case AID_MEDIA_EX:
- case AID_MEDIA_DRM:
- // trusted source, only override default values
- isTrusted = true;
- if (uid_given == (-1)) {
- item->setUid(uid);
- }
- if (pid_given == (-1)) {
- item->setPid(pid);
- }
- break;
- default:
- isTrusted = false;
- item->setPid(pid);
+ ALOGV("%s: caller has uid=%d, embedded uid=%d", __func__, uid, uid_given);
+ bool isTrusted;
+ switch (uid) {
+ case AID_DRM:
+ case AID_MEDIA:
+ case AID_MEDIA_CODEC:
+ case AID_MEDIA_EX:
+ case AID_MEDIA_DRM:
+ // trusted source, only override default values
+ isTrusted = true;
+ if (uid_given == -1) {
item->setUid(uid);
- break;
+ }
+ if (pid_given == -1) {
+ item->setPid(pid);
+ }
+ break;
+ default:
+ isTrusted = false;
+ item->setPid(pid);
+ item->setUid(uid);
+ break;
}
// Overwrite package name and version if the caller was untrusted.
if (!isTrusted) {
- setPkgInfo(item, item->getUid(), true, true);
+ mUidInfo.setPkgInfo(item, item->getUid(), true, true);
} else if (item->getPkgName().empty()) {
- // empty, so fill out both parts
- setPkgInfo(item, item->getUid(), true, true);
+ // empty, so fill out both parts
+ mUidInfo.setPkgInfo(item, item->getUid(), true, true);
} else {
- // trusted, provided a package, do nothing
+ // trusted, provided a package, do nothing
}
- ALOGV("given uid %d; sanitized uid: %d sanitized pkg: %s "
- "sanitized pkg version: %" PRId64,
+ ALOGV("%s: given uid %d; sanitized uid: %d sanitized pkg: %s "
+ "sanitized pkg version: %lld",
+ __func__,
uid_given, item->getUid(),
item->getPkgName().c_str(),
- item->getPkgVersionCode());
+ (long long)item->getPkgVersionCode());
mItemsSubmitted++;
// validate the record; we discard if we don't like it
- if (contentValid(item, isTrusted) == false) {
+ if (isContentValid(item, isTrusted) == false) {
delete item;
return MediaAnalyticsItem::SessionIDInvalid;
}
@@ -218,56 +144,46 @@
// sure it doesn't appear in the finalized list.
if (item->count() == 0) {
- ALOGV("dropping empty record...");
+ ALOGV("%s: dropping empty record...", __func__);
delete item;
- item = NULL;
return MediaAnalyticsItem::SessionIDInvalid;
}
- // save the new record
- //
- // send a copy to statsd
- dump2Statsd(item);
+ // send to statsd
+ extern bool dump2Statsd(MediaAnalyticsItem *item); // extern hook
+ (void)dump2Statsd(item); // failure should be logged in function.
- // and keep our copy for dumpsys
- MediaAnalyticsItem::SessionID_t id = item->getSessionID();
+ // keep our copy
+ const MediaAnalyticsItem::SessionID_t id = item->getSessionID();
saveItem(item);
- mItemsFinalized++;
-
return id;
}
-
status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
{
- const size_t SIZE = 512;
- char buffer[SIZE];
String8 result;
if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
- snprintf(buffer, SIZE, "Permission Denial: "
+ result.appendFormat("Permission Denial: "
"can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
IPCThreadState::self()->getCallingPid(),
IPCThreadState::self()->getCallingUid());
- result.append(buffer);
write(fd, result.string(), result.size());
return NO_ERROR;
}
// crack any parameters
- String16 protoOption("-proto");
+ const String16 protoOption("-proto");
int chosenProto = mDumpProtoDefault;
- String16 clearOption("-clear");
+ const String16 clearOption("-clear");
bool clear = false;
- String16 sinceOption("-since");
+ const String16 sinceOption("-since");
nsecs_t ts_since = 0;
- String16 helpOption("-help");
- String16 onlyOption("-only");
+ const String16 helpOption("-help");
+ const String16 onlyOption("-only");
std::string only;
- int n = args.size();
-
+ const int n = args.size();
for (int i = 0; i < n; i++) {
- String8 myarg(args[i]);
if (args[i] == clearOption) {
clear = true;
} else if (args[i] == protoOption) {
@@ -305,7 +221,7 @@
ts_since = 0;
}
// command line is milliseconds; internal units are nano-seconds
- ts_since *= 1000*1000;
+ ts_since *= NANOS_PER_MILLISECOND;
} else if (args[i] == onlyOption) {
i++;
if (i < n) {
@@ -313,6 +229,10 @@
only = value.string();
}
} else if (args[i] == helpOption) {
+ // TODO: consider function area dumping.
+ // dumpsys media.metrics audiotrack,codec
+ // or dumpsys media.metrics audiotrack codec
+
result.append("Recognized parameters:\n");
result.append("-help this help message\n");
result.append("-proto # dump using protocol #");
@@ -325,31 +245,18 @@
}
}
- Mutex::Autolock _l(mLock);
- // mutex between insertion and dumping the contents
+ {
+ std::lock_guard _l(mLock);
- mDumpProto = chosenProto;
+ result.appendFormat("Dump of the %s process:\n", kServiceName);
+ dumpHeaders_l(result, chosenProto, ts_since);
+ dumpRecent_l(result, chosenProto, ts_since, only.c_str());
- // we ALWAYS dump this piece
- snprintf(buffer, SIZE, "Dump of the %s process:\n", kServiceName);
- result.append(buffer);
-
- dumpHeaders(result, ts_since);
-
- dumpRecent(result, ts_since, only.c_str());
-
-
- if (clear) {
- // remove everything from the finalized queue
- while (mItems.size() > 0) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
+ if (clear) {
+ mItemsDiscarded += mItems.size();
+ mItems.clear();
+ // shall we clear the summary data too?
}
-
- // shall we clear the summary data too?
-
}
write(fd, result.string(), result.size());
@@ -357,275 +264,207 @@
}
// dump headers
-void MediaAnalyticsService::dumpHeaders(String8 &result, nsecs_t ts_since)
+void MediaAnalyticsService::dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since)
{
- const size_t SIZE = 512;
- char buffer[SIZE];
-
- snprintf(buffer, SIZE, "Protocol Version: %d\n", mDumpProto);
- result.append(buffer);
-
- int enabled = MediaAnalyticsItem::isEnabled();
- if (enabled) {
- snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
+ result.appendFormat("Protocol Version: %d\n", dumpProto);
+ if (MediaAnalyticsItem::isEnabled()) {
+ result.append("Metrics gathering: enabled\n");
} else {
- snprintf(buffer, SIZE, "Metrics gathering: DISABLED via property\n");
+ result.append("Metrics gathering: DISABLED via property\n");
}
- result.append(buffer);
-
- snprintf(buffer, SIZE,
- "Since Boot: Submissions: %8" PRId64
- " Accepted: %8" PRId64 "\n",
- mItemsSubmitted, mItemsFinalized);
- result.append(buffer);
- snprintf(buffer, SIZE,
- "Records Discarded: %8" PRId64
- " (by Count: %" PRId64 " by Expiration: %" PRId64 ")\n",
- mItemsDiscarded, mItemsDiscardedCount, mItemsDiscardedExpire);
- result.append(buffer);
+ result.appendFormat(
+ "Since Boot: Submissions: %lld Accepted: %lld\n",
+ (long long)mItemsSubmitted.load(), (long long)mItemsFinalized);
+ result.appendFormat(
+ "Records Discarded: %lld (by Count: %lld by Expiration: %lld)\n",
+ (long long)mItemsDiscarded, (long long)mItemsDiscardedCount,
+ (long long)mItemsDiscardedExpire);
if (ts_since != 0) {
- snprintf(buffer, SIZE,
- "Emitting Queue entries more recent than: %" PRId64 "\n",
- (int64_t) ts_since);
- result.append(buffer);
+ result.appendFormat(
+ "Emitting Queue entries more recent than: %lld\n",
+ (long long)ts_since);
}
}
-// the recent, detailed queues
-void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only)
+void MediaAnalyticsService::dumpRecent_l(
+ String8 &result, int dumpProto, nsecs_t ts_since, const char * only)
{
- const size_t SIZE = 512;
- char buffer[SIZE];
-
- if (only != NULL && *only == '\0') {
- only = NULL;
+ if (only != nullptr && *only == '\0') {
+ only = nullptr;
}
-
- // show the recently recorded records
- snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
- result.append(buffer);
- result.append(this->dumpQueue(ts_since, only));
+ result.append("\nFinalized Metrics (oldest first):\n");
+ dumpQueue_l(result, dumpProto, ts_since, only);
// show who is connected and injecting records?
// talk about # records fed to the 'readers'
// talk about # records we discarded, perhaps "discarded w/o reading" too
}
-// caller has locked mLock...
-String8 MediaAnalyticsService::dumpQueue() {
- return dumpQueue((nsecs_t) 0, NULL);
+void MediaAnalyticsService::dumpQueue_l(String8 &result, int dumpProto) {
+ dumpQueue_l(result, dumpProto, (nsecs_t) 0, nullptr /* only */);
}
-String8 MediaAnalyticsService::dumpQueue(nsecs_t ts_since, const char * only) {
- String8 result;
+void MediaAnalyticsService::dumpQueue_l(
+ String8 &result, int dumpProto, nsecs_t ts_since, const char * only) {
int slot = 0;
if (mItems.empty()) {
- result.append("empty\n");
+ result.append("empty\n");
} else {
- List<MediaAnalyticsItem *>::iterator it = mItems.begin();
- for (; it != mItems.end(); it++) {
- nsecs_t when = (*it)->getTimestamp();
+ for (const auto &item : mItems) {
+ nsecs_t when = item->getTimestamp();
if (when < ts_since) {
continue;
}
- if (only != NULL &&
- strcmp(only, (*it)->getKey().c_str()) != 0) {
- ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
+ // TODO: Only should be a set<string>
+ if (only != nullptr &&
+ item->getKey() /* std::string */ != only) {
+ ALOGV("%s: omit '%s', it's not '%s'",
+ __func__, item->getKey().c_str(), only);
continue;
}
- std::string entry = (*it)->toString(mDumpProto);
- result.appendFormat("%5d: %s\n", slot, entry.c_str());
+ result.appendFormat("%5d: %s\n",
+ slot, item->toString(dumpProto).c_str());
slot++;
}
}
-
- return result;
}
//
// Our Cheap in-core, non-persistent records management.
-
-// we hold mLock when we get here
// if item != NULL, it's the item we just inserted
// true == more items eligible to be recovered
bool MediaAnalyticsService::expirations_l(MediaAnalyticsItem *item)
{
bool more = false;
- int handled = 0;
- // keep removing old records the front until we're in-bounds (count)
- // since we invoke this with each insertion, it should be 0/1 iterations.
- if (mMaxRecords > 0) {
- while (mItems.size() > (size_t) mMaxRecords) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
- if (oitem == item) {
- break;
- }
- if (handled >= mMaxRecordsExpiredAtOnce) {
- // unlikely in this loop
- more = true;
- break;
- }
- handled++;
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
- mItemsDiscardedCount++;
+ // check queue size
+ size_t overlimit = 0;
+ if (mMaxRecords > 0 && mItems.size() > mMaxRecords) {
+ overlimit = mItems.size() - mMaxRecords;
+ if (overlimit > mMaxRecordsExpiredAtOnce) {
+ more = true;
+ overlimit = mMaxRecordsExpiredAtOnce;
}
}
- // keep removing old records the front until we're in-bounds (age)
- // limited to mMaxRecordsExpiredAtOnce items per invocation.
- if (mMaxRecordAgeNs > 0) {
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- while (mItems.size() > 0) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
+ // check queue times
+ size_t expired = 0;
+ if (!more && mMaxRecordAgeNs > 0) {
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ // we check one at a time, skip search would be more efficient.
+ size_t i = overlimit;
+ for (; i < mItems.size(); ++i) {
+ auto &oitem = mItems[i];
nsecs_t when = oitem->getTimestamp();
- if (oitem == item) {
+ if (oitem.get() == item) {
break;
}
- // careful about timejumps too
- if ((now > when) && (now-when) <= mMaxRecordAgeNs) {
- // this (and the rest) are recent enough to keep
- break;
+ if (now > when && (now - when) <= mMaxRecordAgeNs) {
+ break; // TODO: if we use BOOTTIME, should be monotonic.
}
- if (handled >= mMaxRecordsExpiredAtOnce) {
+ if (i >= mMaxRecordsExpiredAtOnce) {
// this represents "one too many"; tell caller there are
// more to be reclaimed.
more = true;
break;
}
- handled++;
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
- mItemsDiscardedExpire++;
}
+ expired = i - overlimit;
}
- // we only indicate whether there's more to clean;
- // caller chooses whether to schedule further cleanup.
+ if (const size_t toErase = overlimit + expired;
+ toErase > 0) {
+ mItemsDiscardedCount += overlimit;
+ mItemsDiscardedExpire += expired;
+ mItemsDiscarded += toErase;
+ mItems.erase(mItems.begin(), mItems.begin() + toErase); // erase from front
+ }
return more;
}
-// process expirations in bite sized chunks, allowing new insertions through
-// runs in a pthread specifically started for this (which then exits)
-bool MediaAnalyticsService::processExpirations()
+void MediaAnalyticsService::processExpirations()
{
bool more;
do {
sleep(1);
- {
- Mutex::Autolock _l(mLock);
- more = expirations_l(NULL);
- if (!more) {
- break;
- }
- }
+ std::lock_guard _l(mLock);
+ more = expirations_l(nullptr);
} while (more);
- return true; // value is for std::future thread synchronization
}
-// insert appropriately into queue
-void MediaAnalyticsService::saveItem(MediaAnalyticsItem * item)
+void MediaAnalyticsService::saveItem(MediaAnalyticsItem *item)
{
-
- Mutex::Autolock _l(mLock);
- // mutex between insertion and dumping the contents
-
- // we want to dump 'in FIFO order', so insert at the end
- mItems.push_back(item);
-
- // clean old stuff from the queue
- bool more = expirations_l(item);
-
- // consider scheduling some asynchronous cleaning, if not running
- if (more) {
- if (!mExpireFuture.valid()
- || mExpireFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
-
- mExpireFuture = std::async(std::launch::async, [this]()
- {return this->processExpirations();});
- }
+ std::lock_guard _l(mLock);
+ // we assume the items are roughly in time order.
+ mItems.emplace_back(item);
+ ++mItemsFinalized;
+ if (expirations_l(item)
+ && (!mExpireFuture.valid()
+ || mExpireFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready)) {
+ mExpireFuture = std::async(std::launch::async, [this] { processExpirations(); });
}
}
-static std::string allowedKeys[] =
+/* static */
+bool MediaAnalyticsService::isContentValid(const MediaAnalyticsItem *item, bool isTrusted)
{
- "audiopolicy",
- "audiorecord",
- "audiothread",
- "audiotrack",
- "codec",
- "extractor",
- "nuplayer",
-};
-
-static const int nAllowedKeys = sizeof(allowedKeys) / sizeof(allowedKeys[0]);
-
-// are the contents good
-bool MediaAnalyticsService::contentValid(MediaAnalyticsItem *item, bool isTrusted) {
-
+ if (isTrusted) return true;
// untrusted uids can only send us a limited set of keys
- if (isTrusted == false) {
- // restrict to a specific set of keys
- std::string key = item->getKey();
-
- size_t i;
- for(i = 0; i < nAllowedKeys; i++) {
- if (key == allowedKeys[i]) {
- break;
- }
- }
- if (i == nAllowedKeys) {
- ALOGD("Ignoring (key): %s", item->toString().c_str());
- return false;
+ const std::string &key = item->getKey();
+ for (const char *allowedKey : {
+ "audiopolicy",
+ "audiorecord",
+ "audiothread",
+ "audiotrack",
+ "codec",
+ "extractor",
+ "nuplayer",
+ }) {
+ if (key == allowedKey) {
+ return true;
}
}
-
- // internal consistency
-
- return true;
-}
-
-// are we rate limited, normally false
-bool MediaAnalyticsService::rateLimited(MediaAnalyticsItem *) {
-
+ ALOGD("%s: invalid key: %s", __func__, item->toString().c_str());
return false;
}
-// how long we hold package info before we re-fetch it
-#define PKG_EXPIRATION_NS (30*60*1000000000ll) // 30 minutes, in nsecs
+// are we rate limited, normally false
+bool MediaAnalyticsService::isRateLimited(MediaAnalyticsItem *) const
+{
+ return false;
+}
+
+// How long we hold package info before we re-fetch it
+constexpr nsecs_t PKG_EXPIRATION_NS = 30 * 60 * NANOS_PER_SECOND; // 30 minutes
// give me the package name, perhaps going to find it
// manages its own mutex operations internally
-void MediaAnalyticsService::setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion)
+void MediaAnalyticsService::UidInfo::setPkgInfo(
+ MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion)
{
- ALOGV("asking for packagename to go with uid=%d", uid);
+ ALOGV("%s: uid=%d", __func__, uid);
if (!setName && !setVersion) {
- // setting nothing? strange
- return;
+ return; // setting nothing? strange
}
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- struct UidToPkgMap mapping;
- mapping.uid = (uid_t)(-1);
-
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ struct UidToPkgInfo mapping;
{
- Mutex::Autolock _l(mLock_mappings);
- int i = mPkgMappings.indexOfKey(uid);
- if (i >= 0) {
- mapping = mPkgMappings.valueAt(i);
- ALOGV("Expiration? uid %d expiration %" PRId64 " now %" PRId64,
- uid, mapping.expiration, now);
+ std::lock_guard _l(mUidInfoLock);
+ auto it = mPkgMappings.find(uid);
+ if (it != mPkgMappings.end()) {
+ mapping = it->second;
+ ALOGV("%s: uid %d expiration %lld now %lld",
+ __func__, uid, (long long)mapping.expiration, (long long)now);
if (mapping.expiration <= now) {
// purge the stale entry and fall into re-fetching
- ALOGV("entry for uid %d expired, now= %" PRId64 "", uid, now);
- mPkgMappings.removeItemsAt(i);
- mapping.uid = (uid_t)(-1);
+ ALOGV("%s: entry for uid %d expired, now %lld",
+ __func__, uid, (long long)now);
+ mPkgMappings.erase(it);
+ mapping.uid = (uid_t)-1; // this is always fully overwritten
}
}
}
@@ -633,115 +472,103 @@
// if we did not find it
if (mapping.uid == (uid_t)(-1)) {
std::string pkg;
- std::string installer = "";
+ std::string installer;
int64_t versionCode = 0;
- struct passwd *pw = getpwuid(uid);
+ const struct passwd *pw = getpwuid(uid);
if (pw) {
pkg = pw->pw_name;
}
- // find the proper value
-
- sp<IBinder> binder = NULL;
sp<IServiceManager> sm = defaultServiceManager();
- if (sm == NULL) {
- ALOGE("defaultServiceManager failed");
+ sp<content::pm::IPackageManagerNative> package_mgr;
+ if (sm.get() == nullptr) {
+ ALOGE("%s: Cannot find service manager", __func__);
} else {
- binder = sm->getService(String16("package_native"));
- if (binder == NULL) {
- ALOGE("getService package_native failed");
+ sp<IBinder> binder = sm->getService(String16("package_native"));
+ if (binder.get() == nullptr) {
+ ALOGE("%s: Cannot find package_native", __func__);
+ } else {
+ package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
}
}
- if (binder != NULL) {
- sp<content::pm::IPackageManagerNative> package_mgr =
- interface_cast<content::pm::IPackageManagerNative>(binder);
- binder::Status status;
-
+ if (package_mgr != nullptr) {
std::vector<int> uids;
std::vector<std::string> names;
-
uids.push_back(uid);
-
- status = package_mgr->getNamesForUids(uids, &names);
+ binder::Status status = package_mgr->getNamesForUids(uids, &names);
if (!status.isOk()) {
- ALOGE("package_native::getNamesForUids failed: %s",
- status.exceptionMessage().c_str());
- } else {
- if (!names[0].empty()) {
- pkg = names[0].c_str();
- }
+ ALOGE("%s: getNamesForUids failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ }
+ if (!names[0].empty()) {
+ pkg = names[0].c_str();
+ }
+ }
+
+ // strip any leading "shared:" strings that came back
+ if (pkg.compare(0, 7, "shared:") == 0) {
+ pkg.erase(0, 7);
+ }
+ // determine how pkg was installed and the versionCode
+ if (pkg.empty()) {
+ pkg = std::to_string(uid); // no name for us to manage
+ } else if (strchr(pkg.c_str(), '.') == NULL) {
+ // not of form 'com.whatever...'; assume internal and ok
+ } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
+ // android.* packages are assumed fine
+ } else if (package_mgr.get() != nullptr) {
+ String16 pkgName16(pkg.c_str());
+ binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+ if (!status.isOk()) {
+ ALOGE("%s: getInstallerForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
}
- // strip any leading "shared:" strings that came back
- if (pkg.compare(0, 7, "shared:") == 0) {
- pkg.erase(0, 7);
- }
-
- // determine how pkg was installed and the versionCode
- //
- if (pkg.empty()) {
- // no name for us to manage
- } else if (strchr(pkg.c_str(), '.') == NULL) {
- // not of form 'com.whatever...'; assume internal and ok
- } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
- // android.* packages are assumed fine
- } else {
- String16 pkgName16(pkg.c_str());
- status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+ // skip if we didn't get an installer
+ if (status.isOk()) {
+ status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
if (!status.isOk()) {
- ALOGE("package_native::getInstallerForPackage failed: %s",
- status.exceptionMessage().c_str());
- }
-
- // skip if we didn't get an installer
- if (status.isOk()) {
- status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
- if (!status.isOk()) {
- ALOGE("package_native::getVersionCodeForPackage failed: %s",
- status.exceptionMessage().c_str());
- }
- }
-
-
- ALOGV("package '%s' installed by '%s' versioncode %" PRId64 " / %" PRIx64,
- pkg.c_str(), installer.c_str(), versionCode, versionCode);
-
- if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
- // from play store, we keep info
- } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
- // some google source, we keep info
- } else if (strcmp(installer.c_str(), "preload") == 0) {
- // preloads, we keep the info
- } else if (installer.c_str()[0] == '\0') {
- // sideload (no installer); do not report
- pkg = "";
- versionCode = 0;
- } else {
- // unknown installer; do not report
- pkg = "";
- versionCode = 0;
+ ALOGE("%s: getVersionCodeForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
}
}
+
+ ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
+ __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
+
+ if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
+ // from play store, we keep info
+ } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
+ // some google source, we keep info
+ } else if (strcmp(installer.c_str(), "preload") == 0) {
+ // preloads, we keep the info
+ } else if (installer.c_str()[0] == '\0') {
+ // sideload (no installer); report UID only
+ pkg = std::to_string(uid);
+ versionCode = 0;
+ } else {
+ // unknown installer; report UID only
+ pkg = std::to_string(uid);
+ versionCode = 0;
+ }
+ } else {
+ // unvalidated by package_mgr just send uid.
+ pkg = std::to_string(uid);
}
// add it to the map, to save a subsequent lookup
- if (!pkg.empty()) {
- Mutex::Autolock _l(mLock_mappings);
- ALOGV("Adding uid %d pkg '%s'", uid, pkg.c_str());
- ssize_t i = mPkgMappings.indexOfKey(uid);
- if (i < 0) {
- mapping.uid = uid;
- mapping.pkg = pkg;
- mapping.installer = installer.c_str();
- mapping.versionCode = versionCode;
- mapping.expiration = now + PKG_EXPIRATION_NS;
- ALOGV("expiration for uid %d set to %" PRId64 "", uid, mapping.expiration);
-
- mPkgMappings.add(uid, mapping);
- }
- }
+ std::lock_guard _l(mUidInfoLock);
+ // always overwrite
+ mapping.uid = uid;
+ mapping.pkg = std::move(pkg);
+ mapping.installer = std::move(installer);
+ mapping.versionCode = versionCode;
+ mapping.expiration = now + PKG_EXPIRATION_NS;
+ ALOGV("%s: adding uid %d pkg '%s' expiration: %lld",
+ __func__, uid, pkg.c_str(), (long long)mapping.expiration);
+ mPkgMappings[uid] = mapping;
}
if (mapping.uid != (uid_t)(-1)) {
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index 6c9cbaa..1101a8c 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -14,109 +14,99 @@
* limitations under the License.
*/
+#pragma once
-#ifndef ANDROID_MEDIAANALYTICSSERVICE_H
-#define ANDROID_MEDIAANALYTICSSERVICE_H
-
-#include <arpa/inet.h>
-
-#include <utils/threads.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
-#include <utils/List.h>
-
+#include <atomic>
+#include <deque>
#include <future>
+#include <mutex>
+#include <unordered_map>
+// IMediaAnalyticsService must include Vector, String16, Errors
#include <media/IMediaAnalyticsService.h>
+#include <utils/String8.h>
namespace android {
class MediaAnalyticsService : public BnMediaAnalyticsService
{
+public:
+ MediaAnalyticsService();
+ ~MediaAnalyticsService() override;
- public:
+ // caller surrenders ownership of item, MediaAnalyticsService will delete.
+ int64_t submit(MediaAnalyticsItem *item, bool forcenew) override;
- // on this side, caller surrenders ownership
- virtual int64_t submit(MediaAnalyticsItem *item, bool forcenew);
+ status_t dump(int fd, const Vector<String16>& args) override;
- static void instantiate();
- virtual status_t dump(int fd, const Vector<String16>& args);
+ static constexpr const char * const kServiceName = "media.metrics";
- MediaAnalyticsService();
- virtual ~MediaAnalyticsService();
-
- bool processExpirations();
-
- private:
+private:
+ void processExpirations();
MediaAnalyticsItem::SessionID_t generateUniqueSessionID();
+ // input validation after arrival from client
+ static bool isContentValid(const MediaAnalyticsItem *item, bool isTrusted);
+ bool isRateLimited(MediaAnalyticsItem *) const;
+ void saveItem(MediaAnalyticsItem *);
- // statistics about our analytics
- int64_t mItemsSubmitted;
- int64_t mItemsFinalized;
- int64_t mItemsDiscarded;
- int64_t mItemsDiscardedExpire;
- int64_t mItemsDiscardedCount;
- MediaAnalyticsItem::SessionID_t mLastSessionID;
+ // The following methods are GUARDED_BY(mLock)
+ bool expirations_l(MediaAnalyticsItem *);
- // partitioned a bit so we don't over serialize
- mutable Mutex mLock;
- mutable Mutex mLock_ids;
- mutable Mutex mLock_mappings;
+ // support for generating output
+ void dumpQueue_l(String8 &result, int dumpProto);
+ void dumpQueue_l(String8 &result, int dumpProto, nsecs_t, const char *only);
+ void dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since);
+ void dumpSummaries_l(String8 &result, int dumpProto, nsecs_t ts_since, const char * only);
+ void dumpRecent_l(String8 &result, int dumpProto, nsecs_t ts_since, const char * only);
+
+ // The following variables accessed without mLock
// limit how many records we'll retain
// by count (in each queue (open, finalized))
- int32_t mMaxRecords;
- // by time (none older than this long agan
- nsecs_t mMaxRecordAgeNs;
+ const size_t mMaxRecords;
+ // by time (none older than this)
+ const nsecs_t mMaxRecordAgeNs;
// max to expire per expirations_l() invocation
- int32_t mMaxRecordsExpiredAtOnce;
- //
- // # of sets of summaries
- int32_t mMaxRecordSets;
- // nsecs until we start a new record set
- nsecs_t mNewSetInterval;
+ const size_t mMaxRecordsExpiredAtOnce;
+ const int mDumpProtoDefault;
- // input validation after arrival from client
- bool contentValid(MediaAnalyticsItem *item, bool isTrusted);
- bool rateLimited(MediaAnalyticsItem *);
+ std::atomic<MediaAnalyticsItem::SessionID_t> mLastSessionID{};
- // (oldest at front) so it prints nicely for dumpsys
- List<MediaAnalyticsItem *> mItems;
- void saveItem(MediaAnalyticsItem *);
+ class UidInfo {
+ public:
+ void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
- bool expirations_l(MediaAnalyticsItem *);
- std::future<bool> mExpireFuture;
+ private:
+ std::mutex mUidInfoLock;
- // support for generating output
- int mDumpProto;
- int mDumpProtoDefault;
- String8 dumpQueue();
- String8 dumpQueue(nsecs_t, const char *only);
+ struct UidToPkgInfo {
+ uid_t uid = -1;
+ std::string pkg;
+ std::string installer;
+ int64_t versionCode = 0;
+ nsecs_t expiration = 0; // TODO: remove expiration.
+ };
- void dumpHeaders(String8 &result, nsecs_t ts_since);
- void dumpSummaries(String8 &result, nsecs_t ts_since, const char * only);
- void dumpRecent(String8 &result, nsecs_t ts_since, const char * only);
+ // TODO: use concurrent hashmap with striped lock.
+ std::unordered_map<uid_t, struct UidToPkgInfo> mPkgMappings; // GUARDED_BY(mUidInfoLock)
+ } mUidInfo; // mUidInfo can be accessed without lock (locked internally)
- // mapping uids to package names
- struct UidToPkgMap {
- uid_t uid;
- std::string pkg;
- std::string installer;
- int64_t versionCode;
- nsecs_t expiration;
- };
+ std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
- KeyedVector<uid_t,struct UidToPkgMap> mPkgMappings;
- void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
+ std::mutex mLock;
+ // statistics about our analytics
+ int64_t mItemsFinalized = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscarded = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscardedExpire = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscardedCount = 0; // GUARDED_BY(mLock)
+ // If we have a worker thread to garbage collect
+ std::future<void> mExpireFuture; // GUARDED_BY(mLock)
+
+ // Our item queue, generally (oldest at front)
+ // TODO: Make separate class, use segmented queue, write lock only end.
+ // Note: Another analytics module might have ownership of an item longer than the log.
+ std::deque<std::shared_ptr<const MediaAnalyticsItem>> mItems; // GUARDED_BY(mLock)
};
-// hook to send things off to the statsd subsystem
-extern bool dump2Statsd(MediaAnalyticsItem *item);
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_MEDIAANALYTICSSERVICE_H
+} // namespace android
diff --git a/services/mediaanalytics/OWNERS b/services/mediaanalytics/OWNERS
index 9af258b..e37a1f8 100644
--- a/services/mediaanalytics/OWNERS
+++ b/services/mediaanalytics/OWNERS
@@ -1 +1,2 @@
essick@google.com
+hunga@google.com
diff --git a/services/mediaanalytics/iface_statsd.cpp b/services/mediaanalytics/iface_statsd.cpp
index 6fd4415..e02c9cf 100644
--- a/services/mediaanalytics/iface_statsd.cpp
+++ b/services/mediaanalytics/iface_statsd.cpp
@@ -52,7 +52,7 @@
};
// keep this sorted, so we can do binary searches
-struct statsd_hooks statsd_handlers[] =
+static constexpr struct statsd_hooks statsd_handlers[] =
{
{ "audiopolicy", statsd_audiopolicy },
{ "audiorecord", statsd_audiorecord },
@@ -68,7 +68,6 @@
{ "recorder", statsd_recorder },
};
-
// give me a record, i'll look at the type and upload appropriately
bool dump2Statsd(MediaAnalyticsItem *item) {
if (item == NULL) return false;
@@ -81,10 +80,9 @@
return false;
}
- int i;
- for(i = 0;i < sizeof(statsd_handlers) / sizeof(statsd_handlers[0]) ; i++) {
- if (key == statsd_handlers[i].key) {
- return (*statsd_handlers[i].handler)(item);
+ for (const auto &statsd_handler : statsd_handlers) {
+ if (key == statsd_handler.key) {
+ return statsd_handler.handler(item);
}
}
return false;
diff --git a/services/mediaanalytics/main_mediametrics.cpp b/services/mediaanalytics/main_mediametrics.cpp
index 8020a03..6833fe2 100644
--- a/services/mediaanalytics/main_mediametrics.cpp
+++ b/services/mediaanalytics/main_mediametrics.cpp
@@ -16,33 +16,33 @@
#define LOG_TAG "mediametrics"
//#define LOG_NDEBUG 0
-
-#include <binder/IPCThreadState.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
#include <utils/Log.h>
-//#include "RegisterExtensions.h"
-// from LOCAL_C_INCLUDES
#include "MediaAnalyticsService.h"
-using namespace android;
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
int main(int argc __unused, char **argv __unused)
{
+ using namespace android;
+
signal(SIGPIPE, SIG_IGN);
// to match the service name
// we're replacing "/system/bin/mediametrics" with "media.metrics"
// we add a ".", but discard the path components: we finish with a shorter string
- strcpy(argv[0], "media.metrics");
+ strcpy(argv[0], MediaAnalyticsService::kServiceName);
- sp<ProcessState> proc(ProcessState::self());
- sp<IServiceManager> sm(defaultServiceManager());
- ALOGI("ServiceManager: %p", sm.get());
+ defaultServiceManager()->addService(
+ String16(MediaAnalyticsService::kServiceName), new MediaAnalyticsService());
- MediaAnalyticsService::instantiate();
-
- ProcessState::self()->startThreadPool();
+ sp<ProcessState> processState(ProcessState::self());
+ // processState->setThreadPoolMaxThreadCount(8);
+ processState->startThreadPool();
IPCThreadState::self()->joinThreadPool();
+
+ return EXIT_SUCCESS;
}