Merge "Add an API to read experiment IDs."
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 52ecdc8..0aacdf2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -65,8 +65,6 @@
// 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::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 @@
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 @@
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 @@
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 d24565a..38efa89 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -194,6 +194,11 @@
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 d9f5415..2b7dc8d 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -27,6 +27,9 @@
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 @@
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 @@
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 753a9a5..531ce29 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -60,8 +60,9 @@
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 @@
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 65b183c..cf8b974 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -36,9 +36,17 @@
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 @@
}
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 @@
// 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 @@
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::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;
-
- 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;
- }
-
- // 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 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 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 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 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;
- }
-
- // 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());
- close(fd);
- return false;
- }
-
- VLOG("Read train info file successful: %s", fullPath.c_str());
+ // 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 true;
+ return false;
}
- 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 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 # 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 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 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 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;
+ }
+
+ // 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);
+ }
+
+ // 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 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 88280cf..dfcea65 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -29,11 +29,6 @@
using android::util::ProtoOutputStream;
-struct TrainInfo {
- int64_t trainVersionCode;
- std::vector<uint8_t> experimentIds;
-};
-
class StorageManager : public virtual RefBase {
public:
/**
@@ -45,7 +40,7 @@
* 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 b03517e..504ee22 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -645,6 +645,22 @@
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 7aca2f2..2e14d03 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -411,6 +411,36 @@
}
/**
+ * 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 6d4c5a0..311c86d 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -217,4 +217,9 @@
*/
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();
}