Merge RQ2A.210405.006 to aosp-master - DO NOT MERGE
Merged-In: I735b41ed53ce7d374b5d36c4aee79f73d0334234 Merged-In: I9999bfe514fffa9f300643e74942db5ae556670a Change-Id: If4b670a158e561d8feb03e12aef2d295c5109d43
This commit is contained in:
392
cmds/statsd/src/external/StatsPullerManager.cpp
vendored
Normal file
392
cmds/statsd/src/external/StatsPullerManager.cpp
vendored
Normal file
@@ -0,0 +1,392 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define DEBUG false
|
||||
#include "Log.h"
|
||||
|
||||
#include "StatsPullerManager.h"
|
||||
|
||||
#include <cutils/log.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "../StatsService.h"
|
||||
#include "../logd/LogEvent.h"
|
||||
#include "../stats_log_util.h"
|
||||
#include "../statscompanion_util.h"
|
||||
#include "StatsCallbackPuller.h"
|
||||
#include "TrainInfoPuller.h"
|
||||
#include "statslog_statsd.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
|
||||
// pullAtomCallbackDied is never called.
|
||||
struct PullAtomCallbackDeathCookie {
|
||||
PullAtomCallbackDeathCookie(const wp<StatsPullerManager>& pullerManager,
|
||||
const PullerKey& pullerKey, const wp<StatsPuller>& puller) :
|
||||
mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
|
||||
}
|
||||
|
||||
wp<StatsPullerManager> mPullerManager;
|
||||
PullerKey mPullerKey;
|
||||
wp<StatsPuller> mPuller;
|
||||
};
|
||||
|
||||
void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
|
||||
PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
|
||||
sp<StatsPullerManager> thiz = cookie_->mPullerManager.promote();
|
||||
if (!thiz) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PullerKey& pullerKey = cookie_->mPullerKey;
|
||||
wp<StatsPuller> puller = cookie_->mPuller;
|
||||
|
||||
// Erase the mapping from the puller key to the puller if the mapping still exists.
|
||||
// Note that we are removing the StatsPuller object, which internally holds the binder
|
||||
// IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
|
||||
lock_guard<mutex> lock(thiz->mLock);
|
||||
const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
|
||||
if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
|
||||
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
|
||||
/*registered=*/false);
|
||||
thiz->kAllPullAtomInfo.erase(pullerKey);
|
||||
}
|
||||
// The death recipient corresponding to this specific IPullAtomCallback can never
|
||||
// be triggered again, so free up resources.
|
||||
delete cookie_;
|
||||
}
|
||||
|
||||
// Values smaller than this may require to update the alarm.
|
||||
const int64_t NO_ALARM_UPDATE = INT64_MAX;
|
||||
|
||||
StatsPullerManager::StatsPullerManager()
|
||||
: kAllPullAtomInfo({
|
||||
// TrainInfo.
|
||||
{{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
|
||||
}),
|
||||
mNextPullTimeNs(NO_ALARM_UPDATE),
|
||||
mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
|
||||
}
|
||||
|
||||
bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey, const int64_t eventTimeNs,
|
||||
vector<shared_ptr<LogEvent>>* data, bool useUids) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
return PullLocked(tagId, configKey, eventTimeNs, data, useUids);
|
||||
}
|
||||
|
||||
bool StatsPullerManager::Pull(int tagId, const vector<int32_t>& uids, const int64_t eventTimeNs,
|
||||
vector<std::shared_ptr<LogEvent>>* data, bool useUids) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
return PullLocked(tagId, uids, eventTimeNs, data, useUids);
|
||||
}
|
||||
|
||||
bool StatsPullerManager::PullLocked(int tagId, const ConfigKey& configKey,
|
||||
const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data,
|
||||
bool useUids) {
|
||||
vector<int32_t> uids;
|
||||
if (useUids) {
|
||||
auto uidProviderIt = mPullUidProviders.find(configKey);
|
||||
if (uidProviderIt == mPullUidProviders.end()) {
|
||||
ALOGE("Error pulling tag %d. No pull uid provider for config key %s", tagId,
|
||||
configKey.ToString().c_str());
|
||||
StatsdStats::getInstance().notePullUidProviderNotFound(tagId);
|
||||
return false;
|
||||
}
|
||||
sp<PullUidProvider> pullUidProvider = uidProviderIt->second.promote();
|
||||
if (pullUidProvider == nullptr) {
|
||||
ALOGE("Error pulling tag %d, pull uid provider for config %s is gone.", tagId,
|
||||
configKey.ToString().c_str());
|
||||
StatsdStats::getInstance().notePullUidProviderNotFound(tagId);
|
||||
return false;
|
||||
}
|
||||
uids = pullUidProvider->getPullAtomUids(tagId);
|
||||
}
|
||||
return PullLocked(tagId, uids, eventTimeNs, data, useUids);
|
||||
}
|
||||
|
||||
bool StatsPullerManager::PullLocked(int tagId, const vector<int32_t>& uids,
|
||||
const int64_t eventTimeNs, vector<shared_ptr<LogEvent>>* data,
|
||||
bool useUids) {
|
||||
VLOG("Initiating pulling %d", tagId);
|
||||
if (useUids) {
|
||||
for (int32_t uid : uids) {
|
||||
PullerKey key = {.atomTag = tagId, .uid = uid};
|
||||
auto pullerIt = kAllPullAtomInfo.find(key);
|
||||
if (pullerIt != kAllPullAtomInfo.end()) {
|
||||
bool ret = pullerIt->second->Pull(eventTimeNs, data);
|
||||
VLOG("pulled %zu items", data->size());
|
||||
if (!ret) {
|
||||
StatsdStats::getInstance().notePullFailed(tagId);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
StatsdStats::getInstance().notePullerNotFound(tagId);
|
||||
ALOGW("StatsPullerManager: Unknown tagId %d", tagId);
|
||||
return false; // Return early since we don't know what to pull.
|
||||
} else {
|
||||
PullerKey key = {.atomTag = tagId, .uid = -1};
|
||||
auto pullerIt = kAllPullAtomInfo.find(key);
|
||||
if (pullerIt != kAllPullAtomInfo.end()) {
|
||||
bool ret = pullerIt->second->Pull(eventTimeNs, data);
|
||||
VLOG("pulled %zu items", data->size());
|
||||
if (!ret) {
|
||||
StatsdStats::getInstance().notePullFailed(tagId);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
ALOGW("StatsPullerManager: Unknown tagId %d", tagId);
|
||||
return false; // Return early since we don't know what to pull.
|
||||
}
|
||||
}
|
||||
|
||||
bool StatsPullerManager::PullerForMatcherExists(int tagId) const {
|
||||
// Pulled atoms might be registered after we parse the config, so just make sure the id is in
|
||||
// an appropriate range.
|
||||
return isVendorPulledAtom(tagId) || isPulledAtom(tagId);
|
||||
}
|
||||
|
||||
void StatsPullerManager::updateAlarmLocked() {
|
||||
if (mNextPullTimeNs == NO_ALARM_UPDATE) {
|
||||
VLOG("No need to set alarms. Skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/151045771): do not hold a lock while making a binder call
|
||||
if (mStatsCompanionService != nullptr) {
|
||||
mStatsCompanionService->setPullingAlarm(mNextPullTimeNs / 1000000);
|
||||
} else {
|
||||
VLOG("StatsCompanionService not available. Alarm not set.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void StatsPullerManager::SetStatsCompanionService(
|
||||
shared_ptr<IStatsCompanionService> statsCompanionService) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
shared_ptr<IStatsCompanionService> tmpForLock = mStatsCompanionService;
|
||||
mStatsCompanionService = statsCompanionService;
|
||||
for (const auto& pulledAtom : kAllPullAtomInfo) {
|
||||
pulledAtom.second->SetStatsCompanionService(statsCompanionService);
|
||||
}
|
||||
if (mStatsCompanionService != nullptr) {
|
||||
updateAlarmLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void StatsPullerManager::RegisterReceiver(int tagId, const ConfigKey& configKey,
|
||||
wp<PullDataReceiver> receiver, int64_t nextPullTimeNs,
|
||||
int64_t intervalNs) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
auto& receivers = mReceivers[{.atomTag = tagId, .configKey = configKey}];
|
||||
for (auto it = receivers.begin(); it != receivers.end(); it++) {
|
||||
if (it->receiver == receiver) {
|
||||
VLOG("Receiver already registered of %d", (int)receivers.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
ReceiverInfo receiverInfo;
|
||||
receiverInfo.receiver = receiver;
|
||||
|
||||
// Round it to the nearest minutes. This is the limit of alarm manager.
|
||||
// In practice, we should always have larger buckets.
|
||||
int64_t roundedIntervalNs = intervalNs / NS_PER_SEC / 60 * NS_PER_SEC * 60;
|
||||
// Scheduled pulling should be at least 1 min apart.
|
||||
// This can be lower in cts tests, in which case we round it to 1 min.
|
||||
if (roundedIntervalNs < 60 * (int64_t)NS_PER_SEC) {
|
||||
roundedIntervalNs = 60 * (int64_t)NS_PER_SEC;
|
||||
}
|
||||
|
||||
receiverInfo.intervalNs = roundedIntervalNs;
|
||||
receiverInfo.nextPullTimeNs = nextPullTimeNs;
|
||||
receivers.push_back(receiverInfo);
|
||||
|
||||
// There is only one alarm for all pulled events. So only set it to the smallest denom.
|
||||
if (nextPullTimeNs < mNextPullTimeNs) {
|
||||
VLOG("Updating next pull time %lld", (long long)mNextPullTimeNs);
|
||||
mNextPullTimeNs = nextPullTimeNs;
|
||||
updateAlarmLocked();
|
||||
}
|
||||
VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size());
|
||||
}
|
||||
|
||||
void StatsPullerManager::UnRegisterReceiver(int tagId, const ConfigKey& configKey,
|
||||
wp<PullDataReceiver> receiver) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
auto receiversIt = mReceivers.find({.atomTag = tagId, .configKey = configKey});
|
||||
if (receiversIt == mReceivers.end()) {
|
||||
VLOG("Unknown pull code or no receivers: %d", tagId);
|
||||
return;
|
||||
}
|
||||
std::list<ReceiverInfo>& receivers = receiversIt->second;
|
||||
for (auto it = receivers.begin(); it != receivers.end(); it++) {
|
||||
if (receiver == it->receiver) {
|
||||
receivers.erase(it);
|
||||
VLOG("Puller for tagId %d unregistered of %d", tagId, (int)receivers.size());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StatsPullerManager::RegisterPullUidProvider(const ConfigKey& configKey,
|
||||
wp<PullUidProvider> provider) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
mPullUidProviders[configKey] = provider;
|
||||
}
|
||||
|
||||
void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey,
|
||||
wp<PullUidProvider> provider) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
const auto& it = mPullUidProviders.find(configKey);
|
||||
if (it != mPullUidProviders.end() && it->second == provider) {
|
||||
mPullUidProviders.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
int64_t wallClockNs = getWallClockNs();
|
||||
|
||||
int64_t minNextPullTimeNs = NO_ALARM_UPDATE;
|
||||
|
||||
vector<pair<const ReceiverKey*, vector<ReceiverInfo*>>> needToPull;
|
||||
for (auto& pair : mReceivers) {
|
||||
vector<ReceiverInfo*> receivers;
|
||||
if (pair.second.size() != 0) {
|
||||
for (ReceiverInfo& receiverInfo : pair.second) {
|
||||
if (receiverInfo.nextPullTimeNs <= elapsedTimeNs) {
|
||||
receivers.push_back(&receiverInfo);
|
||||
} else {
|
||||
if (receiverInfo.nextPullTimeNs < minNextPullTimeNs) {
|
||||
minNextPullTimeNs = receiverInfo.nextPullTimeNs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (receivers.size() > 0) {
|
||||
needToPull.push_back(make_pair(&pair.first, receivers));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& pullInfo : needToPull) {
|
||||
vector<shared_ptr<LogEvent>> data;
|
||||
bool pullSuccess = PullLocked(pullInfo.first->atomTag, pullInfo.first->configKey,
|
||||
elapsedTimeNs, &data);
|
||||
if (!pullSuccess) {
|
||||
VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs);
|
||||
}
|
||||
|
||||
// Convention is to mark pull atom timestamp at request time.
|
||||
// If we pull at t0, puller starts at t1, finishes at t2, and send back
|
||||
// at t3, we mark t0 as its timestamp, which should correspond to its
|
||||
// triggering event, such as condition change at t0.
|
||||
// Here the triggering event is alarm fired from AlarmManager.
|
||||
// In ValueMetricProducer and GaugeMetricProducer we do same thing
|
||||
// when pull on condition change, etc.
|
||||
for (auto& event : data) {
|
||||
event->setElapsedTimestampNs(elapsedTimeNs);
|
||||
event->setLogdWallClockTimestampNs(wallClockNs);
|
||||
}
|
||||
|
||||
for (const auto& receiverInfo : pullInfo.second) {
|
||||
sp<PullDataReceiver> receiverPtr = receiverInfo->receiver.promote();
|
||||
if (receiverPtr != nullptr) {
|
||||
receiverPtr->onDataPulled(data, pullSuccess, elapsedTimeNs);
|
||||
// We may have just come out of a coma, compute next pull time.
|
||||
int numBucketsAhead =
|
||||
(elapsedTimeNs - receiverInfo->nextPullTimeNs) / receiverInfo->intervalNs;
|
||||
receiverInfo->nextPullTimeNs += (numBucketsAhead + 1) * receiverInfo->intervalNs;
|
||||
if (receiverInfo->nextPullTimeNs < minNextPullTimeNs) {
|
||||
minNextPullTimeNs = receiverInfo->nextPullTimeNs;
|
||||
}
|
||||
} else {
|
||||
VLOG("receiver already gone.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VLOG("mNextPullTimeNs: %lld updated to %lld", (long long)mNextPullTimeNs,
|
||||
(long long)minNextPullTimeNs);
|
||||
mNextPullTimeNs = minNextPullTimeNs;
|
||||
updateAlarmLocked();
|
||||
}
|
||||
|
||||
int StatsPullerManager::ForceClearPullerCache() {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
int totalCleared = 0;
|
||||
for (const auto& pulledAtom : kAllPullAtomInfo) {
|
||||
totalCleared += pulledAtom.second->ForceClearCache();
|
||||
}
|
||||
return totalCleared;
|
||||
}
|
||||
|
||||
int StatsPullerManager::ClearPullerCacheIfNecessary(int64_t timestampNs) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
int totalCleared = 0;
|
||||
for (const auto& pulledAtom : kAllPullAtomInfo) {
|
||||
totalCleared += pulledAtom.second->ClearCacheIfNecessary(timestampNs);
|
||||
}
|
||||
return totalCleared;
|
||||
}
|
||||
|
||||
void StatsPullerManager::RegisterPullAtomCallback(const int uid, const int32_t atomTag,
|
||||
const int64_t coolDownNs, const int64_t timeoutNs,
|
||||
const vector<int32_t>& additiveFields,
|
||||
const shared_ptr<IPullAtomCallback>& callback,
|
||||
bool useUid) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
|
||||
|
||||
if (callback == nullptr) {
|
||||
ALOGW("SetPullAtomCallback called with null callback for atom %d.", atomTag);
|
||||
return;
|
||||
}
|
||||
|
||||
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
|
||||
int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
|
||||
int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
|
||||
|
||||
sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
|
||||
actualTimeoutNs, additiveFields);
|
||||
PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1};
|
||||
AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
|
||||
new PullAtomCallbackDeathCookie(this, key, puller));
|
||||
kAllPullAtomInfo[key] = puller;
|
||||
}
|
||||
|
||||
void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag,
|
||||
bool useUids) {
|
||||
std::lock_guard<std::mutex> _l(mLock);
|
||||
PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1};
|
||||
if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
|
||||
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
|
||||
/*registered=*/false);
|
||||
kAllPullAtomInfo.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
618
cmds/statsd/src/logd/LogEvent.cpp
Normal file
618
cmds/statsd/src/logd/LogEvent.cpp
Normal file
@@ -0,0 +1,618 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define DEBUG false // STOPSHIP if true
|
||||
#include "logd/LogEvent.h"
|
||||
|
||||
#include <android-base/stringprintf.h>
|
||||
#include <android/binder_ibinder.h>
|
||||
#include <log/log.h>
|
||||
#include <private/android_filesystem_config.h>
|
||||
|
||||
#include "annotations.h"
|
||||
#include "stats_log_util.h"
|
||||
#include "statslog_statsd.h"
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
// for TrainInfo experiment id serialization
|
||||
const int FIELD_ID_EXPERIMENT_ID = 1;
|
||||
|
||||
using namespace android::util;
|
||||
using android::base::StringPrintf;
|
||||
using android::util::ProtoOutputStream;
|
||||
using std::string;
|
||||
using std::vector;
|
||||
|
||||
// stats_event.h socket types. Keep in sync.
|
||||
/* ERRORS */
|
||||
#define ERROR_NO_TIMESTAMP 0x1
|
||||
#define ERROR_NO_ATOM_ID 0x2
|
||||
#define ERROR_OVERFLOW 0x4
|
||||
#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
|
||||
#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
|
||||
#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
|
||||
#define ERROR_INVALID_ANNOTATION_ID 0x40
|
||||
#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
|
||||
#define ERROR_TOO_MANY_ANNOTATIONS 0x100
|
||||
#define ERROR_TOO_MANY_FIELDS 0x200
|
||||
#define ERROR_INVALID_VALUE_TYPE 0x400
|
||||
#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
|
||||
|
||||
/* TYPE IDS */
|
||||
#define INT32_TYPE 0x00
|
||||
#define INT64_TYPE 0x01
|
||||
#define STRING_TYPE 0x02
|
||||
#define LIST_TYPE 0x03
|
||||
#define FLOAT_TYPE 0x04
|
||||
#define BOOL_TYPE 0x05
|
||||
#define BYTE_ARRAY_TYPE 0x06
|
||||
#define OBJECT_TYPE 0x07
|
||||
#define KEY_VALUE_PAIRS_TYPE 0x08
|
||||
#define ATTRIBUTION_CHAIN_TYPE 0x09
|
||||
#define ERROR_TYPE 0x0F
|
||||
|
||||
LogEvent::LogEvent(int32_t uid, int32_t pid)
|
||||
: mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
|
||||
}
|
||||
|
||||
LogEvent::LogEvent(const string& trainName, int64_t trainVersionCode, bool requiresStaging,
|
||||
bool rollbackEnabled, bool requiresLowLatencyMonitor, int32_t state,
|
||||
const std::vector<uint8_t>& experimentIds, int32_t userId) {
|
||||
mLogdTimestampNs = getWallClockNs();
|
||||
mElapsedTimestampNs = getElapsedRealtimeNs();
|
||||
mTagId = util::BINARY_PUSH_STATE_CHANGED;
|
||||
mLogUid = AIBinder_getCallingUid();
|
||||
mLogPid = AIBinder_getCallingPid();
|
||||
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(1)), Value(trainName)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainVersionCode)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value((int)requiresStaging)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value((int)rollbackEnabled)));
|
||||
mValues.push_back(
|
||||
FieldValue(Field(mTagId, getSimpleField(5)), Value((int)requiresLowLatencyMonitor)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(6)), Value(state)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(7)), Value(experimentIds)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(8)), Value(userId)));
|
||||
}
|
||||
|
||||
LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
|
||||
const InstallTrainInfo& trainInfo) {
|
||||
mLogdTimestampNs = wallClockTimestampNs;
|
||||
mElapsedTimestampNs = elapsedTimestampNs;
|
||||
mTagId = util::TRAIN_INFO;
|
||||
|
||||
mValues.push_back(
|
||||
FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode)));
|
||||
std::vector<uint8_t> experimentIdsProto;
|
||||
writeExperimentIdsToProto(trainInfo.experimentIds, &experimentIdsProto);
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(experimentIdsProto)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(3)), Value(trainInfo.trainName)));
|
||||
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
|
||||
}
|
||||
|
||||
void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t value = readNextValue<int32_t>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseInt64(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int64_t value = readNextValue<int64_t>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseString(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t numBytes = readNextValue<int32_t>();
|
||||
if ((uint32_t)numBytes > mRemainingLen) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
string value = string((char*)mBuf, numBytes);
|
||||
mBuf += numBytes;
|
||||
mRemainingLen -= numBytes;
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseFloat(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
float value = readNextValue<float>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseBool(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
// cast to int32_t because FieldValue does not support bools
|
||||
int32_t value = (int32_t)readNextValue<uint8_t>();
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t numBytes = readNextValue<int32_t>();
|
||||
if ((uint32_t)numBytes > mRemainingLen) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
vector<uint8_t> value(mBuf, mBuf + numBytes);
|
||||
mBuf += numBytes;
|
||||
mRemainingLen -= numBytes;
|
||||
addToValues(pos, depth, value, last);
|
||||
parseAnnotations(numAnnotations);
|
||||
}
|
||||
|
||||
void LogEvent::parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
|
||||
int32_t numPairs = readNextValue<uint8_t>();
|
||||
|
||||
for (pos[1] = 1; pos[1] <= numPairs; pos[1]++) {
|
||||
last[1] = (pos[1] == numPairs);
|
||||
|
||||
// parse key
|
||||
pos[2] = 1;
|
||||
parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
|
||||
// parse value
|
||||
last[2] = true;
|
||||
|
||||
uint8_t typeInfo = readNextValue<uint8_t>();
|
||||
switch (getTypeId(typeInfo)) {
|
||||
case INT32_TYPE:
|
||||
pos[2] = 2; // pos[2] determined by index of type in KeyValuePair in atoms.proto
|
||||
parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
case INT64_TYPE:
|
||||
pos[2] = 3;
|
||||
parseInt64(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
case STRING_TYPE:
|
||||
pos[2] = 4;
|
||||
parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
case FLOAT_TYPE:
|
||||
pos[2] = 5;
|
||||
parseFloat(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
break;
|
||||
default:
|
||||
mValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
parseAnnotations(numAnnotations);
|
||||
|
||||
pos[1] = pos[2] = 1;
|
||||
last[1] = last[2] = false;
|
||||
}
|
||||
|
||||
void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
|
||||
uint8_t numAnnotations) {
|
||||
const unsigned int firstUidInChainIndex = mValues.size();
|
||||
const int32_t numNodes = readNextValue<uint8_t>();
|
||||
for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
|
||||
last[1] = (pos[1] == numNodes);
|
||||
|
||||
// parse uid
|
||||
pos[2] = 1;
|
||||
parseInt32(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
|
||||
// parse tag
|
||||
pos[2] = 2;
|
||||
last[2] = true;
|
||||
parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
|
||||
}
|
||||
|
||||
if (mValues.size() - 1 > INT8_MAX) {
|
||||
mValid = false;
|
||||
} else if (mValues.size() - 1 > firstUidInChainIndex) {
|
||||
// At least one node was successfully parsed.
|
||||
mAttributionChainStartIndex = static_cast<int8_t>(firstUidInChainIndex);
|
||||
mAttributionChainEndIndex = static_cast<int8_t>(mValues.size() - 1);
|
||||
}
|
||||
|
||||
if (mValid) {
|
||||
parseAnnotations(numAnnotations, firstUidInChainIndex);
|
||||
}
|
||||
|
||||
pos[1] = pos[2] = 1;
|
||||
last[1] = last[2] = false;
|
||||
}
|
||||
|
||||
// Assumes that mValues is not empty
|
||||
bool LogEvent::checkPreviousValueType(Type expected) {
|
||||
return mValues[mValues.size() - 1].mValue.getType() == expected;
|
||||
}
|
||||
|
||||
void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || mValues.size() - 1 > INT8_MAX || !checkPreviousValueType(INT)
|
||||
|| annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool isUid = readNextValue<uint8_t>();
|
||||
if (isUid) mUidFieldIndex = static_cast<int8_t>(mValues.size() - 1);
|
||||
mValues[mValues.size() - 1].mAnnotations.setUidField(isUid);
|
||||
}
|
||||
|
||||
void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
|
||||
if (!mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mTruncateTimestamp = readNextValue<uint8_t>();
|
||||
}
|
||||
|
||||
void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool primaryField = readNextValue<uint8_t>();
|
||||
mValues[mValues.size() - 1].mAnnotations.setPrimaryField(primaryField);
|
||||
}
|
||||
|
||||
void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType,
|
||||
int firstUidInChainIndex) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (static_cast<int>(mValues.size() - 1) < firstUidInChainIndex) { // AttributionChain is empty.
|
||||
mValid = false;
|
||||
android_errorWriteLog(0x534e4554, "174485572");
|
||||
return;
|
||||
}
|
||||
|
||||
const bool primaryField = readNextValue<uint8_t>();
|
||||
mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField);
|
||||
}
|
||||
|
||||
void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mValues.size() - 1 > INT8_MAX) {
|
||||
android_errorWriteLog(0x534e4554, "174488848");
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool exclusiveState = readNextValue<uint8_t>();
|
||||
mExclusiveStateFieldIndex = static_cast<int8_t>(mValues.size() - 1);
|
||||
mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState);
|
||||
}
|
||||
|
||||
void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != INT32_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mResetState = readNextValue<int32_t>();
|
||||
}
|
||||
|
||||
void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
|
||||
if (mValues.empty() || annotationType != BOOL_TYPE) {
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool nested = readNextValue<uint8_t>();
|
||||
mValues[mValues.size() - 1].mAnnotations.setNested(nested);
|
||||
}
|
||||
|
||||
// firstUidInChainIndex is a default parameter that is only needed when parsing
|
||||
// annotations for attribution chains.
|
||||
void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
|
||||
for (uint8_t i = 0; i < numAnnotations; i++) {
|
||||
uint8_t annotationId = readNextValue<uint8_t>();
|
||||
uint8_t annotationType = readNextValue<uint8_t>();
|
||||
|
||||
switch (annotationId) {
|
||||
case ANNOTATION_ID_IS_UID:
|
||||
parseIsUidAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
|
||||
parseTruncateTimestampAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_PRIMARY_FIELD:
|
||||
parsePrimaryFieldAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
|
||||
parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex);
|
||||
break;
|
||||
case ANNOTATION_ID_EXCLUSIVE_STATE:
|
||||
parseExclusiveStateAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_TRIGGER_STATE_RESET:
|
||||
parseTriggerStateResetAnnotation(annotationType);
|
||||
break;
|
||||
case ANNOTATION_ID_STATE_NESTED:
|
||||
parseStateNestedAnnotation(annotationType);
|
||||
break;
|
||||
default:
|
||||
mValid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This parsing logic is tied to the encoding scheme used in StatsEvent.java and
|
||||
// stats_event.c
|
||||
bool LogEvent::parseBuffer(uint8_t* buf, size_t len) {
|
||||
mBuf = buf;
|
||||
mRemainingLen = (uint32_t)len;
|
||||
|
||||
int32_t pos[] = {1, 1, 1};
|
||||
bool last[] = {false, false, false};
|
||||
|
||||
// Beginning of buffer is OBJECT_TYPE | NUM_FIELDS | TIMESTAMP | ATOM_ID
|
||||
uint8_t typeInfo = readNextValue<uint8_t>();
|
||||
if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false;
|
||||
|
||||
uint8_t numElements = readNextValue<uint8_t>();
|
||||
if (numElements < 2 || numElements > 127) mValid = false;
|
||||
|
||||
typeInfo = readNextValue<uint8_t>();
|
||||
if (getTypeId(typeInfo) != INT64_TYPE) mValid = false;
|
||||
mElapsedTimestampNs = readNextValue<int64_t>();
|
||||
numElements--;
|
||||
|
||||
typeInfo = readNextValue<uint8_t>();
|
||||
if (getTypeId(typeInfo) != INT32_TYPE) mValid = false;
|
||||
mTagId = readNextValue<int32_t>();
|
||||
numElements--;
|
||||
parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
|
||||
|
||||
for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
|
||||
last[0] = (pos[0] == numElements);
|
||||
|
||||
typeInfo = readNextValue<uint8_t>();
|
||||
uint8_t typeId = getTypeId(typeInfo);
|
||||
|
||||
switch (typeId) {
|
||||
case BOOL_TYPE:
|
||||
parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case INT32_TYPE:
|
||||
parseInt32(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case INT64_TYPE:
|
||||
parseInt64(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case FLOAT_TYPE:
|
||||
parseFloat(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case BYTE_ARRAY_TYPE:
|
||||
parseByteArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case STRING_TYPE:
|
||||
parseString(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case KEY_VALUE_PAIRS_TYPE:
|
||||
parseKeyValuePairs(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case ATTRIBUTION_CHAIN_TYPE:
|
||||
parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
|
||||
break;
|
||||
case ERROR_TYPE:
|
||||
/* mErrorBitmask =*/ readNextValue<int32_t>();
|
||||
mValid = false;
|
||||
break;
|
||||
default:
|
||||
mValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mRemainingLen != 0) mValid = false;
|
||||
mBuf = nullptr;
|
||||
return mValid;
|
||||
}
|
||||
|
||||
uint8_t LogEvent::getTypeId(uint8_t typeInfo) {
|
||||
return typeInfo & 0x0F; // type id in lower 4 bytes
|
||||
}
|
||||
|
||||
uint8_t LogEvent::getNumAnnotations(uint8_t typeInfo) {
|
||||
return (typeInfo >> 4) & 0x0F; // num annotations in upper 4 bytes
|
||||
}
|
||||
|
||||
int64_t LogEvent::GetLong(size_t key, status_t* err) const {
|
||||
// TODO(b/110561208): encapsulate the magical operations in Field struct as static functions
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == LONG) {
|
||||
return value.mValue.long_value;
|
||||
} else if (value.mValue.getType() == INT) {
|
||||
return value.mValue.int_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LogEvent::GetInt(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == INT) {
|
||||
return value.mValue.int_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* LogEvent::GetString(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == STRING) {
|
||||
return value.mValue.str_value.c_str();
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool LogEvent::GetBool(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == INT) {
|
||||
return value.mValue.int_value != 0;
|
||||
} else if (value.mValue.getType() == LONG) {
|
||||
return value.mValue.long_value != 0;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return false;
|
||||
}
|
||||
|
||||
float LogEvent::GetFloat(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == FLOAT) {
|
||||
return value.mValue.float_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const {
|
||||
int field = getSimpleField(key);
|
||||
for (const auto& value : mValues) {
|
||||
if (value.mField.getField() == field) {
|
||||
if (value.mValue.getType() == STORAGE) {
|
||||
return value.mValue.storage_value;
|
||||
} else {
|
||||
*err = BAD_TYPE;
|
||||
return vector<uint8_t>();
|
||||
}
|
||||
}
|
||||
if ((size_t)value.mField.getPosAtDepth(0) > key) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*err = BAD_INDEX;
|
||||
return vector<uint8_t>();
|
||||
}
|
||||
|
||||
string LogEvent::ToString() const {
|
||||
string result;
|
||||
result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
|
||||
(long long)mElapsedTimestampNs, mTagId);
|
||||
for (const auto& value : mValues) {
|
||||
result +=
|
||||
StringPrintf("%#x", value.mField.getField()) + "->" + value.mValue.toString() + " ";
|
||||
}
|
||||
result += " }";
|
||||
return result;
|
||||
}
|
||||
|
||||
void LogEvent::ToProto(ProtoOutputStream& protoOutput) const {
|
||||
writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput);
|
||||
}
|
||||
|
||||
bool LogEvent::hasAttributionChain(std::pair<int, int>* indexRange) const {
|
||||
if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nullptr != indexRange) {
|
||||
indexRange->first = static_cast<int>(mAttributionChainStartIndex);
|
||||
indexRange->second = static_cast<int>(mAttributionChainEndIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds,
|
||||
std::vector<uint8_t>* protoOut) {
|
||||
ProtoOutputStream proto;
|
||||
for (const auto& expId : experimentIds) {
|
||||
proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID,
|
||||
(long long)expId);
|
||||
}
|
||||
|
||||
protoOut->resize(proto.size());
|
||||
size_t pos = 0;
|
||||
sp<ProtoReader> reader = proto.data();
|
||||
while (reader->readBuffer() != NULL) {
|
||||
size_t toRead = reader->currentToRead();
|
||||
std::memcpy(protoOut->data() + pos, reader->readBuffer(), toRead);
|
||||
pos += toRead;
|
||||
reader->move(toRead);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
481
cmds/statsd/tests/LogEvent_test.cpp
Normal file
481
cmds/statsd/tests/LogEvent_test.cpp
Normal file
@@ -0,0 +1,481 @@
|
||||
// Copyright (C) 2017 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/logd/LogEvent.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "frameworks/base/cmds/statsd/src/atoms.pb.h"
|
||||
#include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h"
|
||||
#include "log/log_event_list.h"
|
||||
#include "stats_event.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
namespace android {
|
||||
namespace os {
|
||||
namespace statsd {
|
||||
|
||||
using std::string;
|
||||
using std::vector;
|
||||
using util::ProtoOutputStream;
|
||||
using util::ProtoReader;
|
||||
|
||||
namespace {
|
||||
|
||||
Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vector<bool>& last) {
|
||||
Field f(tag, (int32_t*)pos.data(), depth);
|
||||
|
||||
// For loop starts at 1 because the last field at depth 0 is not decorated.
|
||||
for (int i = 1; i < depth; i++) {
|
||||
if (last[i]) f.decorateLastPos(i);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
|
||||
bool annotationValue) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
|
||||
AStatsEvent_writeInt32(statsEvent, 10);
|
||||
AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
|
||||
AStatsEvent_build(statsEvent);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
|
||||
EXPECT_TRUE(logEvent->parseBuffer(buf, size));
|
||||
|
||||
AStatsEvent_release(statsEvent);
|
||||
}
|
||||
|
||||
void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
|
||||
int annotationValue) {
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
|
||||
AStatsEvent_writeInt32(statsEvent, 10);
|
||||
AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
|
||||
AStatsEvent_build(statsEvent);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
|
||||
EXPECT_TRUE(logEvent->parseBuffer(buf, size));
|
||||
|
||||
AStatsEvent_release(statsEvent);
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST(LogEventTest, TestPrimitiveParsing) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
AStatsEvent_writeInt32(event, 10);
|
||||
AStatsEvent_writeInt64(event, 0x123456789);
|
||||
AStatsEvent_writeFloat(event, 2.0);
|
||||
AStatsEvent_writeBool(event, true);
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_TRUE(logEvent.parseBuffer(buf, size));
|
||||
|
||||
EXPECT_EQ(100, logEvent.GetTagId());
|
||||
EXPECT_EQ(1000, logEvent.GetUid());
|
||||
EXPECT_EQ(1001, logEvent.GetPid());
|
||||
EXPECT_FALSE(logEvent.hasAttributionChain());
|
||||
|
||||
const vector<FieldValue>& values = logEvent.getValues();
|
||||
ASSERT_EQ(4, values.size());
|
||||
|
||||
const FieldValue& int32Item = values[0];
|
||||
Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false});
|
||||
EXPECT_EQ(expectedField, int32Item.mField);
|
||||
EXPECT_EQ(Type::INT, int32Item.mValue.getType());
|
||||
EXPECT_EQ(10, int32Item.mValue.int_value);
|
||||
|
||||
const FieldValue& int64Item = values[1];
|
||||
expectedField = getField(100, {2, 1, 1}, 0, {false, false, false});
|
||||
EXPECT_EQ(expectedField, int64Item.mField);
|
||||
EXPECT_EQ(Type::LONG, int64Item.mValue.getType());
|
||||
EXPECT_EQ(0x123456789, int64Item.mValue.long_value);
|
||||
|
||||
const FieldValue& floatItem = values[2];
|
||||
expectedField = getField(100, {3, 1, 1}, 0, {false, false, false});
|
||||
EXPECT_EQ(expectedField, floatItem.mField);
|
||||
EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType());
|
||||
EXPECT_EQ(2.0, floatItem.mValue.float_value);
|
||||
|
||||
const FieldValue& boolItem = values[3];
|
||||
expectedField = getField(100, {4, 1, 1}, 0, {true, false, false});
|
||||
EXPECT_EQ(expectedField, boolItem.mField);
|
||||
EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type
|
||||
EXPECT_EQ(1, boolItem.mValue.int_value);
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestStringAndByteArrayParsing) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
string str = "test";
|
||||
AStatsEvent_writeString(event, str.c_str());
|
||||
AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length());
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_TRUE(logEvent.parseBuffer(buf, size));
|
||||
|
||||
EXPECT_EQ(100, logEvent.GetTagId());
|
||||
EXPECT_EQ(1000, logEvent.GetUid());
|
||||
EXPECT_EQ(1001, logEvent.GetPid());
|
||||
EXPECT_FALSE(logEvent.hasAttributionChain());
|
||||
|
||||
const vector<FieldValue>& values = logEvent.getValues();
|
||||
ASSERT_EQ(2, values.size());
|
||||
|
||||
const FieldValue& stringItem = values[0];
|
||||
Field expectedField = getField(100, {1, 1, 1}, 0, {false, false, false});
|
||||
EXPECT_EQ(expectedField, stringItem.mField);
|
||||
EXPECT_EQ(Type::STRING, stringItem.mValue.getType());
|
||||
EXPECT_EQ(str, stringItem.mValue.str_value);
|
||||
|
||||
const FieldValue& storageItem = values[1];
|
||||
expectedField = getField(100, {2, 1, 1}, 0, {true, false, false});
|
||||
EXPECT_EQ(expectedField, storageItem.mField);
|
||||
EXPECT_EQ(Type::STORAGE, storageItem.mValue.getType());
|
||||
vector<uint8_t> expectedValue = {'t', 'e', 's', 't'};
|
||||
EXPECT_EQ(expectedValue, storageItem.mValue.storage_value);
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestEmptyString) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
string empty = "";
|
||||
AStatsEvent_writeString(event, empty.c_str());
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_TRUE(logEvent.parseBuffer(buf, size));
|
||||
|
||||
EXPECT_EQ(100, logEvent.GetTagId());
|
||||
EXPECT_EQ(1000, logEvent.GetUid());
|
||||
EXPECT_EQ(1001, logEvent.GetPid());
|
||||
EXPECT_FALSE(logEvent.hasAttributionChain());
|
||||
|
||||
const vector<FieldValue>& values = logEvent.getValues();
|
||||
ASSERT_EQ(1, values.size());
|
||||
|
||||
const FieldValue& item = values[0];
|
||||
Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false});
|
||||
EXPECT_EQ(expectedField, item.mField);
|
||||
EXPECT_EQ(Type::STRING, item.mValue.getType());
|
||||
EXPECT_EQ(empty, item.mValue.str_value);
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestByteArrayWithNullCharacter) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
uint8_t message[] = {'\t', 'e', '\0', 's', 't'};
|
||||
AStatsEvent_writeByteArray(event, message, 5);
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_TRUE(logEvent.parseBuffer(buf, size));
|
||||
|
||||
EXPECT_EQ(100, logEvent.GetTagId());
|
||||
EXPECT_EQ(1000, logEvent.GetUid());
|
||||
EXPECT_EQ(1001, logEvent.GetPid());
|
||||
|
||||
const vector<FieldValue>& values = logEvent.getValues();
|
||||
ASSERT_EQ(1, values.size());
|
||||
|
||||
const FieldValue& item = values[0];
|
||||
Field expectedField = getField(100, {1, 1, 1}, 0, {true, false, false});
|
||||
EXPECT_EQ(expectedField, item.mField);
|
||||
EXPECT_EQ(Type::STORAGE, item.mValue.getType());
|
||||
vector<uint8_t> expectedValue(message, message + 5);
|
||||
EXPECT_EQ(expectedValue, item.mValue.storage_value);
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestAttributionChain) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
|
||||
string tag1 = "tag1";
|
||||
string tag2 = "tag2";
|
||||
|
||||
uint32_t uids[] = {1001, 1002};
|
||||
const char* tags[] = {tag1.c_str(), tag2.c_str()};
|
||||
|
||||
AStatsEvent_writeAttributionChain(event, uids, tags, 2);
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_TRUE(logEvent.parseBuffer(buf, size));
|
||||
|
||||
EXPECT_EQ(100, logEvent.GetTagId());
|
||||
EXPECT_EQ(1000, logEvent.GetUid());
|
||||
EXPECT_EQ(1001, logEvent.GetPid());
|
||||
|
||||
const vector<FieldValue>& values = logEvent.getValues();
|
||||
ASSERT_EQ(4, values.size()); // 2 per attribution node
|
||||
|
||||
std::pair<int, int> attrIndexRange;
|
||||
EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange));
|
||||
EXPECT_EQ(0, attrIndexRange.first);
|
||||
EXPECT_EQ(3, attrIndexRange.second);
|
||||
|
||||
// Check first attribution node
|
||||
const FieldValue& uid1Item = values[0];
|
||||
Field expectedField = getField(100, {1, 1, 1}, 2, {true, false, false});
|
||||
EXPECT_EQ(expectedField, uid1Item.mField);
|
||||
EXPECT_EQ(Type::INT, uid1Item.mValue.getType());
|
||||
EXPECT_EQ(1001, uid1Item.mValue.int_value);
|
||||
|
||||
const FieldValue& tag1Item = values[1];
|
||||
expectedField = getField(100, {1, 1, 2}, 2, {true, false, true});
|
||||
EXPECT_EQ(expectedField, tag1Item.mField);
|
||||
EXPECT_EQ(Type::STRING, tag1Item.mValue.getType());
|
||||
EXPECT_EQ(tag1, tag1Item.mValue.str_value);
|
||||
|
||||
// Check second attribution nodes
|
||||
const FieldValue& uid2Item = values[2];
|
||||
expectedField = getField(100, {1, 2, 1}, 2, {true, true, false});
|
||||
EXPECT_EQ(expectedField, uid2Item.mField);
|
||||
EXPECT_EQ(Type::INT, uid2Item.mValue.getType());
|
||||
EXPECT_EQ(1002, uid2Item.mValue.int_value);
|
||||
|
||||
const FieldValue& tag2Item = values[3];
|
||||
expectedField = getField(100, {1, 2, 2}, 2, {true, true, true});
|
||||
EXPECT_EQ(expectedField, tag2Item.mField);
|
||||
EXPECT_EQ(Type::STRING, tag2Item.mValue.getType());
|
||||
EXPECT_EQ(tag2, tag2Item.mValue.str_value);
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestAnnotationIdIsUid) {
|
||||
LogEvent event(/*uid=*/0, /*pid=*/0);
|
||||
createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true);
|
||||
|
||||
const vector<FieldValue>& values = event.getValues();
|
||||
ASSERT_EQ(values.size(), 1);
|
||||
EXPECT_EQ(event.getUidFieldIndex(), 0);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestAnnotationIdStateNested) {
|
||||
LogEvent event(/*uid=*/0, /*pid=*/0);
|
||||
createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true);
|
||||
|
||||
const vector<FieldValue>& values = event.getValues();
|
||||
ASSERT_EQ(values.size(), 1);
|
||||
EXPECT_TRUE(values[0].mAnnotations.isNested());
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestPrimaryFieldAnnotation) {
|
||||
LogEvent event(/*uid=*/0, /*pid=*/0);
|
||||
createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true);
|
||||
|
||||
const vector<FieldValue>& values = event.getValues();
|
||||
ASSERT_EQ(values.size(), 1);
|
||||
EXPECT_TRUE(values[0].mAnnotations.isPrimaryField());
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestExclusiveStateAnnotation) {
|
||||
LogEvent event(/*uid=*/0, /*pid=*/0);
|
||||
createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true);
|
||||
|
||||
const vector<FieldValue>& values = event.getValues();
|
||||
ASSERT_EQ(values.size(), 1);
|
||||
EXPECT_TRUE(values[0].mAnnotations.isExclusiveState());
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
|
||||
// Event has 10 ints and then an attribution chain
|
||||
int numInts = 10;
|
||||
int firstUidInChainIndex = numInts;
|
||||
string tag1 = "tag1";
|
||||
string tag2 = "tag2";
|
||||
uint32_t uids[] = {1001, 1002};
|
||||
const char* tags[] = {tag1.c_str(), tag2.c_str()};
|
||||
|
||||
// Construct AStatsEvent
|
||||
AStatsEvent* statsEvent = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(statsEvent, 100);
|
||||
for (int i = 0; i < numInts; i++) {
|
||||
AStatsEvent_writeInt32(statsEvent, 10);
|
||||
}
|
||||
AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
|
||||
AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
|
||||
AStatsEvent_build(statsEvent);
|
||||
|
||||
// Construct LogEvent
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
|
||||
LogEvent logEvent(/*uid=*/0, /*pid=*/0);
|
||||
EXPECT_TRUE(logEvent.parseBuffer(buf, size));
|
||||
AStatsEvent_release(statsEvent);
|
||||
|
||||
// Check annotation
|
||||
const vector<FieldValue>& values = logEvent.getValues();
|
||||
ASSERT_EQ(values.size(), numInts + 4);
|
||||
EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField());
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestResetStateAnnotation) {
|
||||
int32_t resetState = 10;
|
||||
LogEvent event(/*uid=*/0, /*pid=*/0);
|
||||
createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState);
|
||||
|
||||
const vector<FieldValue>& values = event.getValues();
|
||||
ASSERT_EQ(values.size(), 1);
|
||||
EXPECT_EQ(event.getResetState(), resetState);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestExclusiveStateAnnotationAfterTooManyFields) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
|
||||
const unsigned int numAttributionNodes = 64;
|
||||
|
||||
uint32_t uids[numAttributionNodes];
|
||||
const char* tags[numAttributionNodes];
|
||||
|
||||
for (unsigned int i = 1; i <= numAttributionNodes; i++) {
|
||||
uids[i-1] = i;
|
||||
tags[i-1] = std::to_string(i).c_str();
|
||||
}
|
||||
|
||||
AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes);
|
||||
AStatsEvent_writeInt32(event, 1);
|
||||
AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_EXCLUSIVE_STATE, true);
|
||||
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_FALSE(logEvent.parseBuffer(buf, size));
|
||||
EXPECT_EQ(-1, logEvent.getExclusiveStateFieldIndex());
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestUidAnnotationAfterTooManyFields) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
|
||||
const unsigned int numAttributionNodes = 64;
|
||||
|
||||
uint32_t uids[numAttributionNodes];
|
||||
const char* tags[numAttributionNodes];
|
||||
|
||||
for (unsigned int i = 1; i <= numAttributionNodes; i++) {
|
||||
uids[i-1] = i;
|
||||
tags[i-1] = std::to_string(i).c_str();
|
||||
}
|
||||
|
||||
AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes);
|
||||
AStatsEvent_writeInt32(event, 1);
|
||||
AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true);
|
||||
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_FALSE(logEvent.parseBuffer(buf, size));
|
||||
EXPECT_EQ(-1, logEvent.getUidFieldIndex());
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestAttributionChainEndIndexAfterTooManyFields) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
|
||||
const unsigned int numAttributionNodes = 65;
|
||||
|
||||
uint32_t uids[numAttributionNodes];
|
||||
const char* tags[numAttributionNodes];
|
||||
|
||||
for (unsigned int i = 1; i <= numAttributionNodes; i++) {
|
||||
uids[i-1] = i;
|
||||
tags[i-1] = std::to_string(i).c_str();
|
||||
}
|
||||
|
||||
AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes);
|
||||
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_FALSE(logEvent.parseBuffer(buf, size));
|
||||
EXPECT_FALSE(logEvent.hasAttributionChain());
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
TEST(LogEventTest, TestEmptyAttributionChainWithPrimaryFieldFirstUidAnnotation) {
|
||||
AStatsEvent* event = AStatsEvent_obtain();
|
||||
AStatsEvent_setAtomId(event, 100);
|
||||
|
||||
uint32_t uids[] = {};
|
||||
const char* tags[] = {};
|
||||
|
||||
AStatsEvent_writeInt32(event, 10);
|
||||
AStatsEvent_writeAttributionChain(event, uids, tags, 0);
|
||||
AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true);
|
||||
|
||||
AStatsEvent_build(event);
|
||||
|
||||
size_t size;
|
||||
uint8_t* buf = AStatsEvent_getBuffer(event, &size);
|
||||
|
||||
LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
|
||||
EXPECT_FALSE(logEvent.parseBuffer(buf, size));
|
||||
|
||||
AStatsEvent_release(event);
|
||||
}
|
||||
|
||||
} // namespace statsd
|
||||
} // namespace os
|
||||
} // namespace android
|
||||
#else
|
||||
GTEST_LOG_(INFO) << "This test does nothing.\n";
|
||||
#endif
|
||||
Reference in New Issue
Block a user