diff options
| -rw-r--r-- | cmds/statsd/src/StatsService.cpp | 63 | ||||
| -rw-r--r-- | cmds/statsd/src/StatsService.h | 5 | ||||
| -rw-r--r-- | cmds/statsd/src/logd/LogEvent.cpp | 25 | ||||
| -rw-r--r-- | cmds/statsd/src/logd/LogEvent.h | 5 | ||||
| -rw-r--r-- | cmds/statsd/src/storage/StorageManager.cpp | 211 | ||||
| -rw-r--r-- | cmds/statsd/src/storage/StorageManager.h | 7 | ||||
| -rw-r--r-- | cmds/statsd/tests/LogEvent_test.cpp | 16 | ||||
| -rw-r--r-- | core/java/android/app/StatsManager.java | 30 | ||||
| -rw-r--r-- | core/java/android/os/IStatsManager.aidl | 5 |
9 files changed, 248 insertions, 119 deletions
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 52ecdc8425af..0aacdf2cbfbc 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -65,8 +65,6 @@ constexpr const char* kOpUsage = "android:get_usage_stats"; // for StatsDataDumpProto const int FIELD_ID_REPORTS_LIST = 1; -// for TrainInfo experiment id serialization -const int FIELD_ID_EXPERIMENT_ID = 1; static binder::Status ok() { return binder::Status::ok(); @@ -1181,7 +1179,7 @@ Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& p Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName, int64_t trainVersionCode, int options, int32_t state, - const std::vector<int64_t>& experimentIds) { + const std::vector<int64_t>& experimentIdsIn) { uid_t uid = IPCThreadState::self()->getCallingUid(); // For testing if (uid == AID_ROOT || uid == AID_SYSTEM || uid == AID_SHELL) { @@ -1201,7 +1199,7 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra bool readTrainInfoSuccess = false; InstallTrainInfo trainInfo; - if (trainVersionCode == -1 || experimentIds.empty() || trainName.size() == 0) { + if (trainVersionCode == -1 || experimentIdsIn.empty() || trainName.size() == 0) { readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo); } @@ -1209,27 +1207,19 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra trainVersionCode = trainInfo.trainVersionCode; } - vector<uint8_t> experimentIdsProtoBuffer; - if (readTrainInfoSuccess && experimentIds.empty()) { - experimentIdsProtoBuffer = trainInfo.experimentIds; + // Find the right experiment IDs + std::vector<int64_t> experimentIds; + if (readTrainInfoSuccess && experimentIdsIn.empty()) { + experimentIds = trainInfo.experimentIds; } else { - ProtoOutputStream proto; - for (const auto& expId : experimentIds) { - proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, - (long long)expId); - } - - experimentIdsProtoBuffer.resize(proto.size()); - size_t pos = 0; - sp<ProtoReader> reader = proto.data(); - while (reader->readBuffer() != NULL) { - size_t toRead = reader->currentToRead(); - std::memcpy(&(experimentIdsProtoBuffer[pos]), reader->readBuffer(), toRead); - pos += toRead; - reader->move(toRead); - } + experimentIds = experimentIdsIn; } + // Flatten the experiment IDs to proto + vector<uint8_t> experimentIdsProtoBuffer; + writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer); + + // Find the right train name std::string trainNameUtf8; if (readTrainInfoSuccess && trainName.size() == 0) { trainNameUtf8 = trainInfo.trainName; @@ -1244,7 +1234,34 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled, requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId); mProcessor->OnLogEvent(&event); - StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIdsProtoBuffer); + StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); + return Status::ok(); +} + +Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) { + uid_t uid = IPCThreadState::self()->getCallingUid(); + + // Caller must be granted these permissions + if (!checkCallingPermission(String16(kPermissionDump))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionDump)); + } + if (!checkCallingPermission(String16(kPermissionUsage))) { + return exception(binder::Status::EX_SECURITY, + StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage)); + } + // TODO: add verifier permission + + // Read the latest train info + InstallTrainInfo trainInfo; + if (!StorageManager::readTrainInfo(trainInfo)) { + // No train info means no experiment IDs, return an empty list + experimentIdsOut->clear(); + return Status::ok(); + } + + // Copy the experiment IDs to the out vector + experimentIdsOut->assign(trainInfo.experimentIds.begin(), trainInfo.experimentIds.end()); return Status::ok(); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index d24565a63054..38efa89fe8d5 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -194,6 +194,11 @@ public: int32_t state, const std::vector<int64_t>& experimentIds) override; /** + * Binder call to get registered experiment IDs. + */ + virtual Status getRegisteredExperimentIds(std::vector<int64_t>* expIdsOut); + + /** * Binder call to get SpeakerImpedance atom. */ virtual Return<void> reportSpeakerImpedance(const SpeakerImpedance& speakerImpedance) override; diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index d9f5415463e3..2b7dc8dcad89 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -27,6 +27,9 @@ namespace android { namespace os { namespace statsd { +// for TrainInfo experiment id serialization +const int FIELD_ID_EXPERIMENT_ID = 1; + using namespace android::util; using android::util::ProtoOutputStream; using std::string; @@ -241,7 +244,9 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back( FieldValue(Field(mTagId, getSimpleField(1)), Value(trainInfo.trainVersionCode))); - mValues.push_back(FieldValue(Field(mTagId, getSimpleField(2)), Value(trainInfo.experimentIds))); + 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))); } @@ -671,6 +676,24 @@ void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); } +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 diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 753a9a500c57..531ce299beef 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -60,8 +60,9 @@ struct InstallTrainInfo { int64_t trainVersionCode; std::string trainName; int32_t status; - std::vector<uint8_t> experimentIds; + std::vector<int64_t> experimentIds; }; + /** * Wrapper for the log_msg structure. */ @@ -239,6 +240,8 @@ private: uint32_t mLogUid; }; +void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index 65b183c6fc96..cf8b97494a06 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -36,9 +36,17 @@ using android::util::FIELD_COUNT_REPEATED; using android::util::FIELD_TYPE_MESSAGE; using std::map; +/** + * NOTE: these directories are protected by SELinux, any changes here must also update + * the SELinux policies. + */ #define STATS_DATA_DIR "/data/misc/stats-data" #define STATS_SERVICE_DIR "/data/misc/stats-service" #define TRAIN_INFO_DIR "/data/misc/train-info" +#define TRAIN_INFO_PATH "/data/misc/train-info/train-info.bin" + +// Magic word at the start of the train info file, change this if changing the file format +const uint32_t TRAIN_INFO_FILE_MAGIC = 0xff7447ff; // for ConfigMetricsReportList const int FIELD_ID_REPORTS = 2; @@ -96,27 +104,42 @@ void StorageManager::writeFile(const char* file, const void* buffer, int numByte } bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<uint8_t>& experimentIds) { + int32_t status, const std::vector<int64_t>& experimentIds) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); deleteAllFiles(TRAIN_INFO_DIR); - string file_name = StringPrintf("%s/%lld", TRAIN_INFO_DIR, (long long)trainVersionCode); - - int fd = open(file_name.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); + int fd = open(TRAIN_INFO_PATH, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR); if (fd == -1) { - VLOG("Attempt to access %s but failed", file_name.c_str()); + VLOG("Attempt to access %s but failed", TRAIN_INFO_PATH); return false; } size_t result; + // Write the magic word + result = write(fd, &TRAIN_INFO_FILE_MAGIC, sizeof(TRAIN_INFO_FILE_MAGIC)); + if (result != sizeof(TRAIN_INFO_FILE_MAGIC)) { + VLOG("Failed to wrtie train info magic"); + close(fd); + return false; + } + + // Write the train version + const size_t trainVersionCodeByteCount = sizeof(trainVersionCode); + result = write(fd, &trainVersionCode, trainVersionCodeByteCount); + if (result != trainVersionCodeByteCount) { + VLOG("Failed to wrtie train version code"); + close(fd); + return false; + } + // Write # of bytes in trainName to file const size_t trainNameSize = trainName.size(); const size_t trainNameSizeByteCount = sizeof(trainNameSize); result = write(fd, (uint8_t*)&trainNameSize, trainNameSizeByteCount); if (result != trainNameSizeByteCount) { - VLOG("Failed to write train name size for %s", file_name.c_str()); + VLOG("Failed to write train name size"); close(fd); return false; } @@ -124,7 +147,7 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& // Write trainName to file result = write(fd, trainName.c_str(), trainNameSize); if (result != trainNameSize) { - VLOG("Failed to write train name for%s", file_name.c_str()); + VLOG("Failed to write train name"); close(fd); return false; } @@ -133,34 +156,38 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& const size_t statusByteCount = sizeof(status); result = write(fd, (uint8_t*)&status, statusByteCount); if (result != statusByteCount) { - VLOG("Failed to write status for %s", file_name.c_str()); + VLOG("Failed to write status"); close(fd); return false; } - // Write experiment id size to file. - const size_t experimentIdSize = experimentIds.size(); - const size_t experimentIdsSizeByteCount = sizeof(experimentIdSize); - result = write(fd, (uint8_t*) &experimentIdSize, experimentIdsSizeByteCount); - if (result != experimentIdsSizeByteCount) { - VLOG("Failed to write experiment id size for %s", file_name.c_str()); + // Write experiment id count to file. + const size_t experimentIdsCount = experimentIds.size(); + const size_t experimentIdsCountByteCount = sizeof(experimentIdsCount); + result = write(fd, (uint8_t*) &experimentIdsCount, experimentIdsCountByteCount); + if (result != experimentIdsCountByteCount) { + VLOG("Failed to write experiment id count"); close(fd); return false; } // Write experimentIds to file - result = write(fd, experimentIds.data(), experimentIds.size()); - if (result == experimentIds.size()) { - VLOG("Successfully wrote %s", file_name.c_str()); - } else { - VLOG("Failed to write experiment ids for %s", file_name.c_str()); - close(fd); - return false; + for (size_t i = 0; i < experimentIdsCount; i++) { + const int64_t experimentId = experimentIds[i]; + const size_t experimentIdByteCount = sizeof(experimentId); + result = write(fd, &experimentId, experimentIdByteCount); + if (result == experimentIdByteCount) { + VLOG("Successfully wrote experiment IDs"); + } else { + VLOG("Failed to write experiment ids"); + close(fd); + return false; + } } result = fchown(fd, AID_STATSD, AID_STATSD); if (result) { - VLOG("Failed to chown %s to statsd", file_name.c_str()); + VLOG("Failed to chown train info file to statsd"); close(fd); return false; } @@ -172,88 +199,96 @@ bool StorageManager::writeTrainInfo(int64_t trainVersionCode, const std::string& bool StorageManager::readTrainInfo(InstallTrainInfo& trainInfo) { std::lock_guard<std::mutex> lock(sTrainInfoMutex); - unique_ptr<DIR, decltype(&closedir)> dir(opendir(TRAIN_INFO_DIR), closedir); - - if (dir == NULL) { - VLOG("Directory does not exist: %s", TRAIN_INFO_DIR); + int fd = open(TRAIN_INFO_PATH, O_RDONLY | O_CLOEXEC); + if (fd == -1) { + VLOG("Failed to open train-info.bin"); return false; } - dirent* de; - while ((de = readdir(dir.get()))) { - char* name = de->d_name; - if (name[0] == '.') { - continue; - } - - size_t result; + // Read the magic word + uint32_t magic; + size_t result = read(fd, &magic, sizeof(magic)); + if (result != sizeof(magic)) { + VLOG("Failed to read train info magic"); + close(fd); + return false; + } - trainInfo.trainVersionCode = StrToInt64(name); - string fullPath = StringPrintf("%s/%s", TRAIN_INFO_DIR, name); - int fd = open(fullPath.c_str(), O_RDONLY | O_CLOEXEC); - if (fd == -1) { - return false; - } + if (magic != TRAIN_INFO_FILE_MAGIC) { + VLOG("Train info magic was 0x%08x, expected 0x%08x", magic, TRAIN_INFO_FILE_MAGIC); + close(fd); + return false; + } - // Read # of bytes taken by trainName in the file. - size_t trainNameSize; - result = read(fd, &trainNameSize, sizeof(size_t)); - if (result != sizeof(size_t)) { - VLOG("Failed to read train name size from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read the train version code + const size_t trainVersionCodeByteCount(sizeof(trainInfo.trainVersionCode)); + result = read(fd, &trainInfo.trainVersionCode, trainVersionCodeByteCount); + if (result != trainVersionCodeByteCount) { + VLOG("Failed to read train version code from train info file"); + close(fd); + return false; + } - // Read trainName - trainInfo.trainName.resize(trainNameSize); - result = read(fd, trainInfo.trainName.data(), trainNameSize); - if (result != trainNameSize) { - VLOG("Failed to read train name from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read # of bytes taken by trainName in the file. + size_t trainNameSize; + result = read(fd, &trainNameSize, sizeof(size_t)); + if (result != sizeof(size_t)) { + VLOG("Failed to read train name size from train info file"); + close(fd); + return false; + } - // Read status - const size_t statusByteCount = sizeof(trainInfo.status); - result = read(fd, &trainInfo.status, statusByteCount); - if (result != statusByteCount) { - VLOG("Failed to read train status from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read trainName + trainInfo.trainName.resize(trainNameSize); + result = read(fd, trainInfo.trainName.data(), trainNameSize); + if (result != trainNameSize) { + VLOG("Failed to read train name from train info file"); + close(fd); + return false; + } - // Read experiment ids size. - size_t experimentIdSize; - result = read(fd, &experimentIdSize, sizeof(size_t)); - if (result != sizeof(size_t)) { - VLOG("Failed to read train experiment id size from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read status + const size_t statusByteCount = sizeof(trainInfo.status); + result = read(fd, &trainInfo.status, statusByteCount); + if (result != statusByteCount) { + VLOG("Failed to read train status from train info file"); + close(fd); + return false; + } - // Read experimentIds - trainInfo.experimentIds.resize(experimentIdSize); - result = read(fd, trainInfo.experimentIds.data(), experimentIdSize); - if (result != experimentIdSize) { - VLOG("Failed to read train experiment ids from file %s", fullPath.c_str()); - close(fd); - return false; - } + // Read experiment ids count. + size_t experimentIdsCount; + result = read(fd, &experimentIdsCount, sizeof(size_t)); + if (result != sizeof(size_t)) { + VLOG("Failed to read train experiment id count from train info file"); + close(fd); + return false; + } - // Expect to be at EOF. - char c; - result = read(fd, &c, 1); - if (result != 0) { - VLOG("Failed to read train info from file %s. Did not get expected EOF.", fullPath.c_str()); + // Read experimentIds + for (size_t i = 0; i < experimentIdsCount; i++) { + int64_t experimentId; + result = read(fd, &experimentId, sizeof(experimentId)); + if (result != sizeof(experimentId)) { + VLOG("Failed to read train experiment id from train info file"); close(fd); return false; } + trainInfo.experimentIds.push_back(experimentId); + } - VLOG("Read train info file successful: %s", fullPath.c_str()); + // Expect to be at EOF. + char c; + result = read(fd, &c, 1); + if (result != 0) { + VLOG("Failed to read train info from file. Did not get expected EOF."); close(fd); - return true; + return false; } - return false; + + VLOG("Read train info file successful"); + close(fd); + return true; } void StorageManager::deleteFile(const char* file) { diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 88280cf218b3..dfcea65b0872 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -29,11 +29,6 @@ namespace statsd { using android::util::ProtoOutputStream; -struct TrainInfo { - int64_t trainVersionCode; - std::vector<uint8_t> experimentIds; -}; - class StorageManager : public virtual RefBase { public: /** @@ -45,7 +40,7 @@ public: * Writes train info. */ static bool writeTrainInfo(int64_t trainVersionCode, const std::string& trainName, - int32_t status, const std::vector<uint8_t>& experimentIds); + int32_t status, const std::vector<int64_t>& experimentIds); /** * Reads train info. diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index b03517e607b3..504ee22f72e4 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -645,6 +645,22 @@ TEST(LogEventTest, TestBinaryFieldAtom_empty) { EXPECT_EQ(orig_str, result_str); } +TEST(LogEventTest, TestWriteExperimentIdsToProto) { + std::vector<int64_t> expIds; + expIds.push_back(5038); + std::vector<uint8_t> proto; + + writeExperimentIdsToProto(expIds, &proto); + + EXPECT_EQ(proto.size(), 3); + // Proto wire format for field ID 1, varint + EXPECT_EQ(proto[0], 0x08); + // varint of 5038, 2 bytes long + EXPECT_EQ(proto[1], 0xae); + EXPECT_EQ(proto[2], 0x27); +} + + } // namespace statsd } // namespace os } // namespace android diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java index 7746148d325a..29e9441d2c97 100644 --- a/core/java/android/app/StatsManager.java +++ b/core/java/android/app/StatsManager.java @@ -410,6 +410,36 @@ public final class StatsManager { } /** + * Returns the experiments IDs registered with statsd, or an empty array if there aren't any. + * + * @throws StatsUnavailableException if unsuccessful due to failing to connect to stats service + * @hide + */ + @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) + public long[] getRegisteredExperimentIds() + throws StatsUnavailableException { + synchronized (this) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) { + Slog.d(TAG, "Failed to find statsd when getting experiment IDs"); + } + return new long[0]; + } + return service.getRegisteredExperimentIds(); + } catch (RemoteException e) { + if (DEBUG) { + Slog.d(TAG, + "Failed to connect to StatsCompanionService when getting " + + "registered experiment IDs"); + } + return new long[0]; + } + } + } + + /** * Registers a callback for an atom when that atom is to be pulled. The stats service will * invoke pullData in the callback when the stats service determines that this atom needs to be * pulled. Currently, this only works for atoms with tags above 100,000 that do not have a uid. diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 6d4c5a034b54..311c86d08211 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -217,4 +217,9 @@ interface IStatsManager { */ oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, in int options, in int state, in long[] experimentId); + + /** + * Returns the most recently registered experiment IDs. + */ + long[] getRegisteredExperimentIds(); } |