MediaMetrics: Update to use STL containers

Test: dumpsys media.metrics sanity
Change-Id: Ifcada098e755ef3d679715ed23fab9d793ebde3e
gugelfrei
Andy Hung 5 years ago
parent dbfd6388dd
commit 17dbaf2806

@ -234,10 +234,6 @@ MediaAnalyticsItem &MediaAnalyticsItem::setKey(MediaAnalyticsItem::Key key) {
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 @@ const char * MediaAnalyticsItem::toCString(int version) {
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),

@ -119,7 +119,7 @@ class MediaAnalyticsItem {
// 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 @@ class MediaAnalyticsItem {
// 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);

@ -5,9 +5,9 @@ cc_binary {
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 @@ cc_binary {
},
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,

@ -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 @@ namespace android {
// 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 @@ MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(MediaAnalyticsItem
// 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();
// 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);
const int pid = IPCThreadState::self()->getCallingPid();
const int uid = IPCThreadState::self()->getCallingUid();
const int uid_given = item->getUid();
const int pid_given = item->getPid();
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 @@ MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(MediaAnalyticsItem
// 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 @@ status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
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 @@ status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
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 @@ status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
}
}
Mutex::Autolock _l(mLock);
// mutex between insertion and dumping the contents
mDumpProto = chosenProto;
// 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());
{
std::lock_guard _l(mLock);
result.appendFormat("Dump of the %s process:\n", kServiceName);
dumpHeaders_l(result, chosenProto, ts_since);
dumpRecent_l(result, chosenProto, 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 @@ status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
}
// 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;
ALOGD("%s: invalid key: %s", __func__, item->toString().c_str());
return false;
}
// are we rate limited, normally false
bool MediaAnalyticsService::rateLimited(MediaAnalyticsItem *) {
bool MediaAnalyticsService::isRateLimited(MediaAnalyticsItem *) const
{
return false;
}
// how long we hold package info before we re-fetch it
#define PKG_EXPIRATION_NS (30*60*1000000000ll) // 30 minutes, in nsecs
// 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 @@ void MediaAnalyticsService::setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool
// 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);
// 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());
}
// 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());
ALOGE("%s: getVersionCodeForPackage failed: %s",
__func__, 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;
}
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)) {

@ -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();
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 *);
bool processExpirations();
// The following methods are GUARDED_BY(mLock)
bool expirations_l(MediaAnalyticsItem *);
private:
MediaAnalyticsItem::SessionID_t generateUniqueSessionID();
// 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);
// statistics about our analytics
int64_t mItemsSubmitted;
int64_t mItemsFinalized;
int64_t mItemsDiscarded;
int64_t mItemsDiscardedExpire;
int64_t mItemsDiscardedCount;
MediaAnalyticsItem::SessionID_t mLastSessionID;
// partitioned a bit so we don't over serialize
mutable Mutex mLock;
mutable Mutex mLock_ids;
mutable Mutex mLock_mappings;
// 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 *);
// (oldest at front) so it prints nicely for dumpsys
List<MediaAnalyticsItem *> mItems;
void saveItem(MediaAnalyticsItem *);
std::atomic<MediaAnalyticsItem::SessionID_t> mLastSessionID{};
bool expirations_l(MediaAnalyticsItem *);
std::future<bool> mExpireFuture;
class UidInfo {
public:
void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
// support for generating output
int mDumpProto;
int mDumpProtoDefault;
String8 dumpQueue();
String8 dumpQueue(nsecs_t, const char *only);
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);
// mapping uids to package names
struct UidToPkgMap {
uid_t uid;
std::string pkg;
std::string installer;
int64_t versionCode;
nsecs_t expiration;
};
KeyedVector<uid_t,struct UidToPkgMap> mPkgMappings;
void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
private:
std::mutex mUidInfoLock;
};
struct UidToPkgInfo {
uid_t uid = -1;
std::string pkg;
std::string installer;
int64_t versionCode = 0;
nsecs_t expiration = 0; // TODO: remove expiration.
};
// hook to send things off to the statsd subsystem
extern bool dump2Statsd(MediaAnalyticsItem *item);
// 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)
// ----------------------------------------------------------------------------
std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
}; // namespace android
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)
};
#endif // ANDROID_MEDIAANALYTICSSERVICE_H
} // namespace android

@ -1 +1,2 @@
essick@google.com
hunga@google.com

@ -52,7 +52,7 @@ struct statsd_hooks {
};
// 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 @@ struct statsd_hooks statsd_handlers[] =
{ "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 @@ bool dump2Statsd(MediaAnalyticsItem *item) {
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;

@ -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;
}

Loading…
Cancel
Save