MediaMetrics: Add stack allocated Item

Enable byte string submit for low overhead.
Add tests.

Test: atest mediametrics_tests
Bug: 138583596
Change-Id: Idce65fd270e8ad45ba754a660734368416c8d1b5
gugelfrei
Andy Hung 5 years ago
parent d2572133b1
commit 1efc9c6f15

@ -34,8 +34,11 @@
namespace android {
// TODO: Currently ONE_WAY transactions, make both ONE_WAY and synchronous options.
enum {
SUBMIT_ITEM_ONEWAY = IBinder::FIRST_CALL_TRANSACTION,
SUBMIT_ITEM = IBinder::FIRST_CALL_TRANSACTION,
SUBMIT_BUFFER,
};
class BpMediaAnalyticsService: public BpInterface<IMediaAnalyticsService>
@ -62,7 +65,30 @@ public:
}
status = remote()->transact(
SUBMIT_ITEM_ONEWAY, data, nullptr /* reply */, IBinder::FLAG_ONEWAY);
SUBMIT_ITEM, data, nullptr /* reply */, IBinder::FLAG_ONEWAY);
ALOGW_IF(status != NO_ERROR, "%s: bad response from service for submit, status=%d",
__func__, status);
return status;
}
status_t submitBuffer(const char *buffer, size_t length) override
{
if (buffer == nullptr || length > INT32_MAX) {
return BAD_VALUE;
}
ALOGV("%s: (ONEWAY) length:%zu", __func__, length);
Parcel data;
data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
status_t status = data.writeInt32(length)
?: data.write((uint8_t*)buffer, length);
if (status != NO_ERROR) {
return status;
}
status = remote()->transact(
SUBMIT_BUFFER, data, nullptr /* reply */, IBinder::FLAG_ONEWAY);
ALOGW_IF(status != NO_ERROR, "%s: bad response from service for submit, status=%d",
__func__, status);
return status;
@ -76,10 +102,8 @@ IMPLEMENT_META_INTERFACE(MediaAnalyticsService, "android.media.IMediaAnalyticsSe
status_t BnMediaAnalyticsService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
const int clientPid = IPCThreadState::self()->getCallingPid();
switch (code) {
case SUBMIT_ITEM_ONEWAY: {
case SUBMIT_ITEM: {
CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
MediaAnalyticsItem * const item = MediaAnalyticsItem::create();
@ -87,12 +111,25 @@ status_t BnMediaAnalyticsService::onTransact(
if (status != NO_ERROR) { // assume failure logged in item
return status;
}
// TODO: remove this setPid.
item->setPid(clientPid);
status = submitInternal(item, true /* release */);
// assume failure logged by submitInternal
return NO_ERROR;
} break;
}
case SUBMIT_BUFFER: {
CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
int32_t length;
status_t status = data.readInt32(&length);
if (status != NO_ERROR || length <= 0) {
return BAD_VALUE;
}
const void *ptr = data.readInplace(length);
if (ptr == nullptr) {
return BAD_VALUE;
}
status = submitBuffer(static_cast<const char *>(ptr), length);
// assume failure logged by submitBuffer
return NO_ERROR;
}
default:
return BBinder::onTransact(code, data, reply, flags);

@ -35,6 +35,10 @@
#include <media/MediaAnalyticsItem.h>
#include <private/android_filesystem_config.h>
// Max per-property string size before truncation in toString().
// Do not make too large, as this is used for dumpsys purposes.
static constexpr size_t kMaxPropertyStringSize = 4096;
namespace android {
#define DEBUG_SERVICEACCESS 0
@ -367,70 +371,18 @@ std::string MediaAnalyticsItem::toString() const {
}
std::string MediaAnalyticsItem::toString(int version) const {
// v0 : released with 'o'
// v1 : bug fix (missing pid/finalized separator),
// adds apk name, apk version code
if (version <= PROTO_FIRST) {
// default to original v0 format, until proper parsers are in place
version = PROTO_V0;
} else if (version > PROTO_LAST) {
version = PROTO_LAST;
}
std::string result;
char buffer[512];
char buffer[kMaxPropertyStringSize];
if (version == PROTO_V0) {
result = "(";
} else {
snprintf(buffer, sizeof(buffer), "[%d:", version);
result.append(buffer);
}
// same order as we spill into the parcel, although not required
// key+session are our primary matching criteria
result.append(mKey.c_str());
result.append(":0:"); // sessionID
snprintf(buffer, sizeof(buffer), "%d:", mUid);
snprintf(buffer, sizeof(buffer), "[%d:%s:%d:%d:%lld:%s:%zu:",
version, mKey.c_str(), mPid, mUid, (long long)mTimestamp,
mPkgName.c_str(), mPropCount);
result.append(buffer);
if (version >= PROTO_V1) {
result.append(mPkgName);
snprintf(buffer, sizeof(buffer), ":%" PRId64 ":", mPkgVersionCode);
result.append(buffer);
}
// in 'o' (v1) , the separator between pid and finalized was omitted
if (version <= PROTO_V0) {
snprintf(buffer, sizeof(buffer), "%d", mPid);
} else {
snprintf(buffer, sizeof(buffer), "%d:", mPid);
}
result.append(buffer);
snprintf(buffer, sizeof(buffer), "%d:", 0 /* finalized */); // TODO: remove this.
result.append(buffer);
snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mTimestamp);
result.append(buffer);
// set of items
int count = mPropCount;
snprintf(buffer, sizeof(buffer), "%d:", count);
result.append(buffer);
for (int i = 0 ; i < count; i++ ) {
for (size_t i = 0 ; i < mPropCount; ++i) {
mProps[i].toString(buffer, sizeof(buffer));
result.append(buffer);
}
if (version == PROTO_V0) {
result.append(")");
} else {
result.append("]");
}
result.append("]");
return result;
}
@ -451,8 +403,9 @@ bool MediaAnalyticsItem::selfrecord() {
}
}
namespace mediametrics {
//static
bool MediaAnalyticsItem::isEnabled() {
bool BaseItem::isEnabled() {
// completely skip logging from certain UIDs. We do this here
// to avoid the multi-second timeouts while we learn that
// sepolicy will not let us find the service.
@ -481,25 +434,47 @@ bool MediaAnalyticsItem::isEnabled() {
class MediaMetricsDeathNotifier : public IBinder::DeathRecipient {
virtual void binderDied(const wp<IBinder> &) {
ALOGW("Reacquire service connection on next request");
MediaAnalyticsItem::dropInstance();
BaseItem::dropInstance();
}
};
static sp<MediaMetricsDeathNotifier> sNotifier;
// static
sp<IMediaAnalyticsService> MediaAnalyticsItem::sAnalyticsService;
sp<IMediaAnalyticsService> BaseItem::sAnalyticsService;
static std::mutex sServiceMutex;
static int sRemainingBindAttempts = SVC_TRIES;
// static
void MediaAnalyticsItem::dropInstance() {
void BaseItem::dropInstance() {
std::lock_guard _l(sServiceMutex);
sRemainingBindAttempts = SVC_TRIES;
sAnalyticsService = nullptr;
}
// static
bool BaseItem::submitBuffer(const char *buffer, size_t size) {
/*
MediaAnalyticsItem item;
status_t status = item.readFromByteString(buffer, size);
ALOGD("%s: status:%d, size:%zu, item:%s", __func__, status, size, item.toString().c_str());
return item.selfrecord();
*/
ALOGD_IF(DEBUG_API, "%s: delivering %zu bytes", __func__, size);
sp<IMediaAnalyticsService> svc = getInstance();
if (svc != nullptr) {
const status_t status = svc->submitBuffer(buffer, size);
if (status != NO_ERROR) {
ALOGW("%s: failed(%d) to record: %zu bytes", __func__, status, size);
return false;
}
return true;
}
return false;
}
//static
sp<IMediaAnalyticsService> MediaAnalyticsItem::getInstance() {
sp<IMediaAnalyticsService> BaseItem::getInstance() {
static const char *servicename = "media.metrics";
static const bool enabled = isEnabled(); // singleton initialized
@ -537,6 +512,8 @@ sp<IMediaAnalyticsService> MediaAnalyticsItem::getInstance() {
return sAnalyticsService;
}
} // namespace mediametrics
// merge the info from 'incoming' into this record.
// we finish with a union of this+incoming and special handling for collisions
bool MediaAnalyticsItem::merge(MediaAnalyticsItem *incoming) {
@ -633,6 +610,7 @@ status_t extract(char **val, const char **bufferpptr, const char *bufferptrmax)
while (*ptr != 0) {
if (ptr >= bufferptrmax) {
ALOGE("%s: buffer exceeded", __func__);
return BAD_VALUE;
}
++ptr;
}
@ -657,39 +635,41 @@ status_t MediaAnalyticsItem::writeToByteString(char **pbuffer, size_t *plength)
return INVALID_OPERATION;
}
const uint16_t version = 0;
const uint32_t header_len =
sizeof(uint32_t) // overall length
+ sizeof(header_len) // header length
+ sizeof(version) // encoding version
+ sizeof(uint16_t) // key length
const uint32_t header_size =
sizeof(uint32_t) // total size
+ sizeof(header_size) // header size
+ sizeof(version) // encoding version
+ sizeof(uint16_t) // key size
+ keySizeZeroTerminated // key, zero terminated
+ sizeof(int32_t) // pid
+ sizeof(int32_t) // uid
+ sizeof(int64_t) // timestamp
+ sizeof(int32_t) // pid
+ sizeof(int32_t) // uid
+ sizeof(int64_t) // timestamp
;
uint32_t len = header_len
uint32_t size = header_size
+ sizeof(uint32_t) // # properties
;
for (size_t i = 0 ; i < mPropCount; ++i) {
const size_t size = mProps[i].getByteStringSize();
if (size > UINT_MAX - 1) {
ALOGW("%s: prop %zu has size %zu", __func__, i, size);
const size_t propSize = mProps[i].getByteStringSize();
if (propSize > UINT16_MAX) {
ALOGW("%s: prop %zu size %zu too large", __func__, i, propSize);
return INVALID_OPERATION;
}
if (__builtin_add_overflow(size, propSize, &size)) {
ALOGW("%s: item size overflow at property %zu", __func__, i);
return INVALID_OPERATION;
}
len += size;
}
// TODO: consider package information and timestamp.
// now that we have a size... let's allocate and fill
char *build = (char *)calloc(1 /* nmemb */, len);
// since we fill every byte in the buffer (there is no padding),
// malloc is used here instead of calloc.
char * const build = (char *)malloc(size);
if (build == nullptr) return NO_MEMORY;
char *filling = build;
char *buildmax = build + len;
if (insert(len, &filling, buildmax) != NO_ERROR
|| insert(header_len, &filling, buildmax) != NO_ERROR
char *buildmax = build + size;
if (insert((uint32_t)size, &filling, buildmax) != NO_ERROR
|| insert(header_size, &filling, buildmax) != NO_ERROR
|| insert(version, &filling, buildmax) != NO_ERROR
|| insert((uint16_t)keySizeZeroTerminated, &filling, buildmax) != NO_ERROR
|| insert(mKey.c_str(), &filling, buildmax) != NO_ERROR
@ -697,26 +677,27 @@ status_t MediaAnalyticsItem::writeToByteString(char **pbuffer, size_t *plength)
|| insert((int32_t)mUid, &filling, buildmax) != NO_ERROR
|| insert((int64_t)mTimestamp, &filling, buildmax) != NO_ERROR
|| insert((uint32_t)mPropCount, &filling, buildmax) != NO_ERROR) {
ALOGD("%s:could not write header", __func__);
ALOGE("%s:could not write header", __func__); // shouldn't happen
free(build);
return INVALID_OPERATION;
}
for (size_t i = 0 ; i < mPropCount; ++i) {
if (mProps[i].writeToByteString(&filling, buildmax) != NO_ERROR) {
free(build);
ALOGD("%s:could not write prop %zu of %zu", __func__, i, mPropCount);
// shouldn't happen
ALOGE("%s:could not write prop %zu of %zu", __func__, i, mPropCount);
return INVALID_OPERATION;
}
}
if (filling != buildmax) {
ALOGE("problems populating; wrote=%d planned=%d",
(int)(filling - build), len);
ALOGE("%s: problems populating; wrote=%d planned=%d",
__func__, (int)(filling - build), (int)size);
free(build);
return INVALID_OPERATION;
}
*pbuffer = build;
*plength = len;
*plength = size;
return NO_ERROR;
}
@ -727,40 +708,41 @@ status_t MediaAnalyticsItem::readFromByteString(const char *bufferptr, size_t le
const char *read = bufferptr;
const char *readend = bufferptr + length;
uint32_t len;
uint32_t header_len;
int16_t version;
int16_t key_len;
uint32_t size;
uint32_t header_size;
uint16_t version;
uint16_t key_size;
char *key = nullptr;
int32_t pid;
int32_t uid;
int64_t timestamp;
uint32_t propCount;
if (extract(&len, &read, readend) != NO_ERROR
|| extract(&header_len, &read, readend) != NO_ERROR
if (extract(&size, &read, readend) != NO_ERROR
|| extract(&header_size, &read, readend) != NO_ERROR
|| extract(&version, &read, readend) != NO_ERROR
|| extract(&key_len, &read, readend) != NO_ERROR
|| extract(&key_size, &read, readend) != NO_ERROR
|| extract(&key, &read, readend) != NO_ERROR
|| extract(&pid, &read, readend) != NO_ERROR
|| extract(&uid, &read, readend) != NO_ERROR
|| extract(&timestamp, &read, readend) != NO_ERROR
|| len > length
|| header_len > len) {
|| size > length
|| strlen(key) + 1 != key_size
|| header_size > size) {
free(key);
ALOGD("%s: invalid header", __func__);
ALOGW("%s: invalid header", __func__);
return INVALID_OPERATION;
}
mKey = key;
free(key);
const size_t pos = read - bufferptr;
if (pos > header_len) {
ALOGD("%s: invalid header pos:%zu > header_len:%u",
__func__, pos, header_len);
if (pos > header_size) {
ALOGW("%s: invalid header pos:%zu > header_size:%u",
__func__, pos, header_size);
return INVALID_OPERATION;
} else if (pos < header_len) {
ALOGD("%s: mismatched header pos:%zu < header_len:%u, advancing",
__func__, pos, header_len);
read += (header_len - pos);
} else if (pos < header_size) {
ALOGW("%s: mismatched header pos:%zu < header_size:%u, advancing",
__func__, pos, header_size);
read += (header_size - pos);
}
if (extract(&propCount, &read, readend) != NO_ERROR) {
ALOGD("%s: cannot read prop count", __func__);
@ -772,7 +754,7 @@ status_t MediaAnalyticsItem::readFromByteString(const char *bufferptr, size_t le
for (size_t i = 0; i < propCount; ++i) {
Prop *prop = allocateProp();
if (prop->readFromByteString(&read, readend) != NO_ERROR) {
ALOGD("%s: cannot read prop %zu", __func__, i);
ALOGW("%s: cannot read prop %zu", __func__, i);
return INVALID_OPERATION;
}
}
@ -910,8 +892,10 @@ size_t MediaAnalyticsItem::Prop::getByteStringSize() const
return header + payload;
}
namespace mediametrics {
// TODO: fold into a template later.
status_t MediaAnalyticsItem::writeToByteString(
status_t BaseItem::writeToByteString(
const char *name, int32_t value, char **bufferpptr, char *bufferptrmax)
{
const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
@ -922,7 +906,7 @@ status_t MediaAnalyticsItem::writeToByteString(
?: insert(value, bufferpptr, bufferptrmax);
}
status_t MediaAnalyticsItem::writeToByteString(
status_t BaseItem::writeToByteString(
const char *name, int64_t value, char **bufferpptr, char *bufferptrmax)
{
const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
@ -933,7 +917,7 @@ status_t MediaAnalyticsItem::writeToByteString(
?: insert(value, bufferpptr, bufferptrmax);
}
status_t MediaAnalyticsItem::writeToByteString(
status_t BaseItem::writeToByteString(
const char *name, double value, char **bufferpptr, char *bufferptrmax)
{
const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
@ -944,7 +928,7 @@ status_t MediaAnalyticsItem::writeToByteString(
?: insert(value, bufferpptr, bufferptrmax);
}
status_t MediaAnalyticsItem::writeToByteString(
status_t BaseItem::writeToByteString(
const char *name, const std::pair<int64_t, int64_t> &value, char **bufferpptr, char *bufferptrmax)
{
const size_t len = 2 + 1 + strlen(name) + 1 + 8 + 8;
@ -956,8 +940,14 @@ status_t MediaAnalyticsItem::writeToByteString(
?: insert(value.second, bufferpptr, bufferptrmax);
}
status_t MediaAnalyticsItem::writeToByteString(
status_t BaseItem::writeToByteString(
const char *name, char * const &value, char **bufferpptr, char *bufferptrmax)
{
return writeToByteString(name, (const char *)value, bufferpptr, bufferptrmax);
}
status_t BaseItem::writeToByteString(
const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax)
{
const size_t len = 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
if (len > UINT16_MAX) return BAD_VALUE;
@ -967,7 +957,8 @@ status_t MediaAnalyticsItem::writeToByteString(
?: insert(value, bufferpptr, bufferptrmax);
}
status_t MediaAnalyticsItem::writeToByteString(
status_t BaseItem::writeToByteString(
const char *name, const none_t &, char **bufferpptr, char *bufferptrmax)
{
const size_t len = 2 + 1 + strlen(name) + 1;
@ -977,22 +968,24 @@ status_t MediaAnalyticsItem::writeToByteString(
?: insert(name, bufferpptr, bufferptrmax);
}
} // namespace mediametrics
status_t MediaAnalyticsItem::Prop::writeToByteString(
char **bufferpptr, char *bufferptrmax) const
{
switch (mType) {
case kTypeInt32:
return MediaAnalyticsItem::writeToByteString(mName, u.int32Value, bufferpptr, bufferptrmax);
return BaseItem::writeToByteString(mName, u.int32Value, bufferpptr, bufferptrmax);
case kTypeInt64:
return MediaAnalyticsItem::writeToByteString(mName, u.int64Value, bufferpptr, bufferptrmax);
return BaseItem::writeToByteString(mName, u.int64Value, bufferpptr, bufferptrmax);
case kTypeDouble:
return MediaAnalyticsItem::writeToByteString(mName, u.doubleValue, bufferpptr, bufferptrmax);
return BaseItem::writeToByteString(mName, u.doubleValue, bufferpptr, bufferptrmax);
case kTypeRate:
return MediaAnalyticsItem::writeToByteString(mName, u.rate, bufferpptr, bufferptrmax);
return BaseItem::writeToByteString(mName, u.rate, bufferpptr, bufferptrmax);
case kTypeCString:
return MediaAnalyticsItem::writeToByteString(mName, u.CStringValue, bufferpptr, bufferptrmax);
return BaseItem::writeToByteString(mName, u.CStringValue, bufferpptr, bufferptrmax);
case kTypeNone:
return MediaAnalyticsItem::writeToByteString(mName, none_t{}, bufferpptr, bufferptrmax);
return BaseItem::writeToByteString(mName, none_t{}, bufferpptr, bufferptrmax);
default:
ALOGE("%s: found bad prop type: %d, name %s",
__func__, mType, mName); // no payload sent

@ -49,6 +49,8 @@ public:
may be silent and return 0 - success).
*/
virtual status_t submit(MediaAnalyticsItem *item) = 0;
virtual status_t submitBuffer(const char *buffer, size_t length) = 0;
};
// ----------------------------------------------------------------------------

@ -36,50 +36,346 @@ class IMediaAnalyticsService;
class Parcel;
/*
* Media Metrics
* Byte String format for communication of MediaAnalyticsItem.
* MediaMetrics Item
*
* .... begin of item
* .... begin of header
* (uint32) length: including the length field itself
* (uint32) header length, including header_length and length fields.
* (uint16) version: 0
* (uint16) key length, including zero termination
* (int8)+ key string, including 0 termination
* Byte string format.
*
* For Java
* int64 corresponds to long
* int32, uint32 corresponds to int
* uint16 corresponds to char
* uint8, int8 corresponds to byte
*
* Hence uint8 and uint32 values are limited to INT8_MAX and INT32_MAX.
*
* Physical layout of integers and doubles within the MediaMetrics byte string
* is in Native / host order, which is nearly always little endian.
*
* -- begin of item
* -- begin of header
* (uint32) item size: including the item size field
* (uint32) header size, including the item size and header size fields.
* (uint16) version: exactly 0
* (uint16) key size, that is key strlen + 1 for zero termination.
* (int8)+ key string which is 0 terminated
* (int32) pid
* (int32) uid
* (int64) timestamp
* .... end of header
* .... begin body
* (uint32) properties
* #properties of the following:
* (uint16) property_length, including property_length field itself
* -- end of header
* -- begin body
* (uint32) number of properties
* -- repeat for number of properties
* (uint16) property size, including property size field itself
* (uint8) type of property
* (int8)+ key string, including 0 termination
* based on type of property (above), one of:
* based on type of property (given above), one of:
* (int32)
* (int64)
* (double)
* (int8)+ for cstring, including 0 termination
* (int64, int64) for rate
* .... end body
* .... end of item
* -- end body
* -- end of item
*/
namespace mediametrics {
// Type must match MediaMetrics.java
enum Type {
kTypeNone = 0,
kTypeInt32 = 1,
kTypeInt64 = 2,
kTypeDouble = 3,
kTypeCString = 4,
kTypeRate = 5,
};
template<size_t N>
static inline bool startsWith(const std::string &s, const char (&comp)[N]) {
return !strncmp(s.c_str(), comp, N-1);
}
/**
* Media Metrics BaseItem
*
* A base class which contains utility static functions to write to a byte stream
* and access the Media Metrics service.
*/
class BaseItem {
friend class MediaMetricsDeathNotifier; // for dropInstance
// enabled 1, disabled 0
public:
// are we collecting analytics data
static bool isEnabled();
protected:
static constexpr const char * const EnabledProperty = "media.metrics.enabled";
static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
static const int EnabledProperty_default = 1;
// let's reuse a binder connection
static sp<IMediaAnalyticsService> sAnalyticsService;
static sp<IMediaAnalyticsService> getInstance();
static void dropInstance();
static bool submitBuffer(const char *buffer, size_t len);
static status_t writeToByteString(
const char *name, int32_t value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, int64_t value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, double value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, const std::pair<int64_t, int64_t> &value,
char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, char * const &value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax);
struct none_t {}; // for kTypeNone
static status_t writeToByteString(
const char *name, const none_t &, char **bufferpptr, char *bufferptrmax);
template<typename T>
static status_t sizeOfByteString(const char *name, const T& value) {
return 2 + 1 + strlen(name) + 1 + sizeof(value);
}
template<> // static
status_t sizeOfByteString(const char *name, char * const &value) {
return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
}
template<> // static
status_t sizeOfByteString(const char *name, const char * const &value) {
return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
}
template<> // static
status_t sizeOfByteString(const char *name, const none_t &) {
return 2 + 1 + strlen(name) + 1;
}
};
/**
* Media Metrics BufferedItem
*
* A base class which represents a put-only Media Metrics item, storing
* the Media Metrics data in a buffer with begin and end pointers.
*
* If a property key is entered twice, it will be stored in the buffer twice,
* and (implementation defined) the last value for that key will be used
* by the Media Metrics service.
*
* For realloc, a baseRealloc pointer must be passed in either explicitly
* or implicitly in the constructor. This will be updated with the value used on realloc.
*/
class BufferedItem : public BaseItem {
public:
static inline constexpr uint16_t kVersion = 0;
virtual ~BufferedItem() = default;
BufferedItem(const BufferedItem&) = delete;
BufferedItem& operator=(const BufferedItem&) = delete;
BufferedItem(const std::string key, char *begin, char *end)
: BufferedItem(key.c_str(), begin, end) { }
BufferedItem(const char *key, char *begin, char *end)
: BufferedItem(key, begin, end, nullptr) { }
BufferedItem(const char *key, char **begin, char *end)
: BufferedItem(key, *begin, end, begin) { }
BufferedItem(const char *key, char *begin, char *end, char **baseRealloc)
: mBegin(begin)
, mEnd(end)
, mBaseRealloc(baseRealloc)
{
init(key);
}
template<typename T>
BufferedItem &set(const char *key, const T& value) {
reallocFor(sizeOfByteString(key, value));
if (mStatus == NO_ERROR) {
mStatus = BaseItem::writeToByteString(key, value, &mBptr, mEnd);
++mPropCount;
}
return *this;
}
template<typename T>
BufferedItem &set(const std::string& key, const T& value) {
return set(key.c_str(), value);
}
BufferedItem &setPid(pid_t pid) {
if (mStatus == NO_ERROR) {
copyTo(mBegin + mHeaderLen - 16, (int32_t)pid);
}
return *this;
}
BufferedItem &setUid(uid_t uid) {
if (mStatus == NO_ERROR) {
copyTo(mBegin + mHeaderLen - 12, (int32_t)uid);
}
return *this;
}
BufferedItem &setTimestamp(nsecs_t timestamp) {
if (mStatus == NO_ERROR) {
copyTo(mBegin + mHeaderLen - 8, (int64_t)timestamp);
}
return *this;
}
bool record() {
return updateHeader()
&& BaseItem::submitBuffer(getBuffer(), getLength());
}
bool isValid () const {
return mStatus == NO_ERROR;
}
char *getBuffer() const { return mBegin; }
size_t getLength() const { return mBptr - mBegin; }
size_t getRemaining() const { return mEnd - mBptr; }
size_t getCapacity() const { return mEnd - mBegin; }
bool updateHeader() {
if (mStatus != NO_ERROR) return false;
copyTo(mBegin + 0, (uint32_t)getLength());
copyTo(mBegin + 4, (uint32_t)mHeaderLen);
copyTo(mBegin + mHeaderLen, (uint32_t)mPropCount);
return true;
}
protected:
BufferedItem() = default;
void reallocFor(size_t required) {
if (mStatus != NO_ERROR) return;
const size_t remaining = getRemaining();
if (required <= remaining) return;
if (mBaseRealloc == nullptr) {
mStatus = NO_MEMORY;
return;
}
const size_t current = getLength();
size_t minimum = current + required;
if (minimum > SSIZE_MAX >> 1) {
mStatus = NO_MEMORY;
return;
}
minimum <<= 1;
void *newptr = realloc(*mBaseRealloc, minimum);
if (newptr == nullptr) {
mStatus = NO_MEMORY;
return;
}
if (newptr != *mBaseRealloc) {
// ALOGD("base changed! current:%zu new size %zu", current, minimum);
if (*mBaseRealloc == nullptr) {
memcpy(newptr, mBegin, current);
}
mBegin = (char *)newptr;
*mBaseRealloc = mBegin;
mEnd = mBegin + minimum;
mBptr = mBegin + current;
} else {
// ALOGD("base kept! current:%zu new size %zu", current, minimum);
mEnd = mBegin + minimum;
}
}
template<typename T>
void copyTo(char *ptr, const T& value) {
memcpy(ptr, &value, sizeof(value));
}
void init(const char *key) {
mBptr = mBegin;
const size_t keylen = strlen(key) + 1;
mHeaderLen = 4 + 4 + 2 + 2 + keylen + 4 + 4 + 8;
reallocFor(mHeaderLen);
if (mStatus != NO_ERROR) return;
mBptr = mBegin + mHeaderLen + 4; // this includes propcount.
if (mEnd < mBptr || keylen > UINT16_MAX) {
mStatus = NO_MEMORY;
mBptr = mEnd;
return;
}
copyTo(mBegin + 8, kVersion);
copyTo(mBegin + 10, (uint16_t)keylen);
strcpy(mBegin + 12, key);
// initialize some parameters (that could be overridden)
setPid(-1);
setUid(-1);
setTimestamp(0);
}
char *mBegin = nullptr;
char *mEnd = nullptr;
char **mBaseRealloc = nullptr; // set to an address if realloc should be done.
// upon return, that pointer is updated with
// whatever needs to be freed.
char *mBptr = nullptr;
status_t mStatus = NO_ERROR;
uint32_t mPropCount = 0;
uint32_t mHeaderLen = 0;
};
/**
* MediaMetrics Item is a stack allocated media analytics item used for
* fast logging. It falls over to a malloc if needed.
*
* This is templated with a buffer size to allocate on the stack.
*/
template <size_t N = 4096>
class Item : public BufferedItem {
public:
explicit Item(const std::string key) : Item(key.c_str()) { }
// Since this class will not be defined before the base class, we initialize variables
// in our own order.
explicit Item(const char *key) {
mBegin = mBuffer;
mEnd = mBuffer + N;
mBaseRealloc = &mReallocPtr;
init(key);
}
~Item() override {
if (mReallocPtr != nullptr) { // do the check before calling free to avoid overhead.
free(mReallocPtr);
}
}
private:
char *mReallocPtr = nullptr; // set non-null by base class if realloc happened.
char mBuffer[N];
};
} // mediametrics
/**
* Media Metrics MediaAnalyticsItem
*
* A mutable item representing an event or record that will be
* logged with the Media Metrics service.
* logged with the Media Metrics service. For client logging, one should
* use the mediametrics::Item.
*
* The MediaAnalyticsItem is designed for the service as it has getters.
*/
class MediaAnalyticsItem {
class MediaAnalyticsItem : public mediametrics::BaseItem {
friend class MediaMetricsJNI; // TODO: remove this access
friend class MediaMetricsDeathNotifier; // for dropInstance
public:
// TODO: remove this duplicate definition when frameworks base is updated.
enum Type {
kTypeNone = 0,
kTypeInt32 = 1,
@ -281,28 +577,12 @@ public:
status_t writeToByteString(char **bufferptr, size_t *length) const;
status_t readFromByteString(const char *bufferptr, size_t length);
static status_t writeToByteString(
const char *name, int32_t value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, int64_t value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, double value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, const std::pair<int64_t, int64_t> &value, char **bufferpptr, char *bufferptrmax);
static status_t writeToByteString(
const char *name, char * const &value, char **bufferpptr, char *bufferptrmax);
struct none_t {}; // for kTypeNone
static status_t writeToByteString(
const char *name, const none_t &, char **bufferpptr, char *bufferptrmax);
std::string toString() const;
std::string toString(int version) const;
const char *toCString();
const char *toCString(int version);
// are we collecting analytics data
static bool isEnabled();
protected:
// merge fields from arg into this
@ -316,15 +596,7 @@ private:
int32_t writeToParcel0(Parcel *) const;
int32_t readFromParcel0(const Parcel&);
// enabled 1, disabled 0
static constexpr const char * const EnabledProperty = "media.metrics.enabled";
static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
static const int EnabledProperty_default = 1;
// let's reuse a binder connection
static sp<IMediaAnalyticsService> sAnalyticsService;
static sp<IMediaAnalyticsService> getInstance();
static void dropInstance();
// checks equality even with nullptr.
static bool stringEquals(const char *a, const char *b) {
@ -435,7 +707,29 @@ public:
}
bool isNamed(const char *name) const {
return strcmp(name, mName) == 0;
return stringEquals(name, mName);
}
template <typename T> void visit(T f) const {
switch (mType) {
case MediaAnalyticsItem::kTypeInt32:
f(u.int32Value);
return;
case MediaAnalyticsItem::kTypeInt64:
f(u.int64Value);
return;
case MediaAnalyticsItem::kTypeDouble:
f(u.doubleValue);
return;
case MediaAnalyticsItem::kTypeRate:
f(u.rate);
return;
case MediaAnalyticsItem::kTypeCString:
f(u.CStringValue);
return;
default:
return;
}
}
template <typename T> bool get(T *value) const = delete;

@ -30,6 +30,8 @@
namespace android {
using namespace mediametrics;
// individual records kept in memory: age or count
// age: <= 28 hours (1 1/6 days)
// count: hard limit of # records

@ -45,6 +45,12 @@ public:
return submitInternal(item, false /* release */);
}
status_t submitBuffer(const char *buffer, size_t length) override {
MediaAnalyticsItem *item = new MediaAnalyticsItem();
return item->readFromByteString(buffer, length)
?: submitInternal(item, true /* release */);
}
status_t dump(int fd, const Vector<String16>& args) override;
static constexpr const char * const kServiceName = "media.metrics";

@ -281,3 +281,78 @@ TEST(mediametrics_tests, item_iteration) {
}
ASSERT_EQ(31, mask);
}
TEST(mediametrics_tests, item_expansion) {
mediametrics::Item<1> item("I");
item.set("i32", (int32_t)1)
.set("i64", (int64_t)2)
.set("double", (double)3.125)
.set("string", "abcdefghijklmnopqrstuvwxyz")
.set("rate", std::pair<int64_t, int64_t>(11, 12));
ASSERT_TRUE(item.updateHeader());
MediaAnalyticsItem item2;
item2.readFromByteString(item.getBuffer(), item.getLength());
ASSERT_EQ((pid_t)-1, item2.getPid());
ASSERT_EQ((uid_t)-1, item2.getUid());
int mask = 0;
for (auto &prop : item2) {
const char *name = prop.getName();
if (!strcmp(name, "i32")) {
int32_t i32;
ASSERT_TRUE(prop.get(&i32));
ASSERT_EQ(1, i32);
mask |= 1;
} else if (!strcmp(name, "i64")) {
int64_t i64;
ASSERT_TRUE(prop.get(&i64));
ASSERT_EQ(2, i64);
mask |= 2;
} else if (!strcmp(name, "double")) {
double d;
ASSERT_TRUE(prop.get(&d));
ASSERT_EQ(3.125, d);
mask |= 4;
} else if (!strcmp(name, "string")) {
const char *s;
ASSERT_TRUE(prop.get(&s));
ASSERT_EQ(0, strcmp(s, "abcdefghijklmnopqrstuvwxyz"));
mask |= 8;
} else if (!strcmp(name, "rate")) {
std::pair<int64_t, int64_t> r;
ASSERT_TRUE(prop.get(&r));
ASSERT_EQ(11, r.first);
ASSERT_EQ(12, r.second);
mask |= 16;
} else {
FAIL();
}
}
ASSERT_EQ(31, mask);
}
TEST(mediametrics_tests, item_expansion2) {
mediametrics::Item<1> item("Bigly");
item.setPid(123)
.setUid(456);
constexpr size_t count = 10000;
for (size_t i = 0; i < count; ++i) {
// printf("recording %zu, %p, len:%zu of %zu remaining:%zu \n", i, item.getBuffer(), item.getLength(), item.getCapacity(), item.getRemaining());
item.set(std::to_string(i).c_str(), (int32_t)i);
}
ASSERT_TRUE(item.updateHeader());
MediaAnalyticsItem item2;
printf("begin buffer:%p length:%zu\n", item.getBuffer(), item.getLength());
fflush(stdout);
item2.readFromByteString(item.getBuffer(), item.getLength());
ASSERT_EQ((pid_t)123, item2.getPid());
ASSERT_EQ((uid_t)456, item2.getUid());
for (size_t i = 0; i < count; ++i) {
int32_t i32;
ASSERT_TRUE(item2.getInt32(std::to_string(i).c_str(), &i32));
ASSERT_EQ((int32_t)i, i32);
}
}

Loading…
Cancel
Save