Merge RQ2A.210405.006 to aosp-master - DO NOT MERGE

Merged-In: I735b41ed53ce7d374b5d36c4aee79f73d0334234
Merged-In: I9999bfe514fffa9f300643e74942db5ae556670a
Change-Id: If4b670a158e561d8feb03e12aef2d295c5109d43
This commit is contained in:
Bill Yi
2021-04-13 19:31:57 -07:00
12 changed files with 1551 additions and 30 deletions

View 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

View 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

View 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