UID mapping to provide app name and version.
The UID map is updated by StatsCompanionService, which listens to broadcast
updates indicating that an app was updated/installed or removed. Also,
the entire map is updated when statsd first connects to the companion
service. Also, there is a way for metrics producers to subscribe to
updates, so that they can know when an app was upgraded.
Test: Created new unit-test for mapping and manually tested for install
and remove. Did not manually test the app upgrade.
Change-Id: I6676ae5c93b75c72d9badabb36aa9c40006db07d
diff --git a/cmds/statsd/src/PackageInfoListener.h b/cmds/statsd/src/PackageInfoListener.h
new file mode 100644
index 0000000..476c1d9
--- /dev/null
+++ b/cmds/statsd/src/PackageInfoListener.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef STATSD_PACKAGE_INFO_LISTENER_H
+#define STATSD_PACKAGE_INFO_LISTENER_H
+
+#include <utils/RefBase.h>
+#include <string>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class PackageInfoListener : public virtual android::RefBase {
+public:
+ // Uid map will notify this listener that the app with apk name and uid has been upgraded to
+ // the specified version.
+ virtual void notifyAppUpgrade(const std::string& apk, const int uid, const int version) = 0;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //STATSD_PACKAGE_INFO_LISTENER_H
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 1d2f586..f877ef3 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -31,7 +31,9 @@
namespace os {
namespace statsd {
-StatsLogProcessor::StatsLogProcessor() : m_dropbox_writer("all-logs") {
+StatsLogProcessor::StatsLogProcessor(const sp<UidMap> &uidMap)
+ : m_dropbox_writer("all-logs"), m_UidMap(uidMap)
+{
// hardcoded config
// this should be called from StatsService when it receives a statsd_config
UpdateConfig(0, buildFakeConfig());
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index ab1b44e..05e441c 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -21,6 +21,7 @@
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "metrics/MetricsManager.h"
#include "stats_util.h"
+#include "UidMap.h"
#include <log/logprint.h>
#include <stdio.h>
@@ -32,7 +33,7 @@
class StatsLogProcessor : public LogListener {
public:
- StatsLogProcessor();
+ StatsLogProcessor(const sp<UidMap> &uidMap);
virtual ~StatsLogProcessor();
virtual void OnLogEvent(const log_msg& msg);
@@ -44,6 +45,8 @@
DropboxWriter m_dropbox_writer;
std::unordered_map<int, std::unique_ptr<MetricsManager>> mMetricsManagers;
+
+ sp<UidMap> m_UidMap; // Reference to the UidMap to lookup app name and version for each uid.
};
} // namespace statsd
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 9baeebb..b496404 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -40,7 +40,8 @@
namespace statsd {
StatsService::StatsService(const sp<Looper>& handlerLooper)
- : mAnomalyMonitor(new AnomalyMonitor(2)), mStatsPullerManager() // TODO: Change this based on the config
+ : mAnomalyMonitor(new AnomalyMonitor(2)),m_UidMap(new UidMap()), mStatsPullerManager()
+ // TODO: Change AnomalyMonitor initialization based on the config
{
ALOGD("stats service constructed");
}
@@ -131,6 +132,9 @@
if (!args[0].compare(String8("config"))) {
return doLoadConfig(in);
}
+ if (!args[0].compare(String8("print-uid-map"))) {
+ return doPrintUidMap(out);
+ }
}
printCmdHelp(out);
@@ -153,6 +157,43 @@
}
}
+Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
+ const vector<String16>& app) {
+ if (DEBUG) ALOGD("StatsService::informAllUidData was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call informAllUidData");
+ }
+
+ m_UidMap->updateMap(uid, version, app);
+ if (DEBUG) ALOGD("StatsService::informAllUidData succeeded");
+
+ return Status::ok();
+}
+
+Status StatsService::informOnePackage(const String16& app, int32_t uid, int32_t version) {
+ if (DEBUG) ALOGD("StatsService::informOnePackage was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call informOnePackage");
+ }
+ m_UidMap->updateApp(app, uid, version);
+ return Status::ok();
+}
+
+Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
+ if (DEBUG) ALOGD("StatsService::informOnePackageRemoved was called");
+
+ if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+ return Status::fromExceptionCode(Status::EX_SECURITY,
+ "Only system uid can call informOnePackageRemoved");
+ }
+ m_UidMap->removeApp(app, uid);
+ return Status::ok();
+}
+
Status StatsService::informAnomalyAlarmFired() {
if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired was called");
@@ -261,9 +302,15 @@
return DropboxReader::readStatsLogs(out, args[1].string(), msec);
}
+status_t StatsService::doPrintUidMap(FILE* out) {
+ m_UidMap->printUidMap(out);
+ return NO_ERROR;
+}
+
void StatsService::printCmdHelp(FILE* out) {
fprintf(out, "Usage:\n");
fprintf(out, "\t print-stats-log [tag_required] [timestamp_nsec_optional]\n");
+ fprintf(out, "\t print-uid-map Prints the UID, app name, version mapping.\n");
fprintf(out,
"\t config\t Loads a new config from command-line (must be proto in wire-encoded "
"format).\n");
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 9642279..541f7e8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -20,6 +20,8 @@
#include "AnomalyMonitor.h"
#include "StatsLogProcessor.h"
#include "StatsPullerManager.h"
+#include "StatsPuller.h"
+#include "UidMap.h"
#include <android/os/BnStatsManager.h>
#include <android/os/IStatsCompanionService.h>
@@ -60,6 +62,11 @@
virtual Status informPollAlarmFired();
+ virtual Status informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
+ const vector<String16>& app);
+ virtual Status informOnePackage(const String16& app, int32_t uid, int32_t version);
+ virtual Status informOnePackageRemoved(const String16& app, int32_t uid);
+
virtual status_t setProcessor(const sp<StatsLogProcessor>& main_processor);
// TODO: public for testing since statsd doesn't run when system starts. Change to private
@@ -71,10 +78,16 @@
// TODO: Should be private. Temporarily public for testing purposes only.
const sp<AnomalyMonitor> mAnomalyMonitor;
+ sp<UidMap> getUidMap() {
+ return m_UidMap;
+ }
+
/** Fetches and returns the StatsCompanionService. */
static sp<IStatsCompanionService> getStatsCompanionService();
private:
+ sp<UidMap> m_UidMap; // Reference to the UID map needed for translating UID to app name/version.
+
sp<StatsLogProcessor> m_processor; // Reference to the processor for updating configs.
status_t doPrintStatsLog(FILE* out, const Vector<String8>& args);
@@ -84,6 +97,8 @@
status_t doLoadConfig(FILE* in);
StatsPullerManager mStatsPullerManager;
+
+ status_t doPrintUidMap(FILE* out);
};
// --- StatsdDeathRecipient ---
diff --git a/cmds/statsd/src/UidMap.cpp b/cmds/statsd/src/UidMap.cpp
new file mode 100644
index 0000000..76a7f3f
--- /dev/null
+++ b/cmds/statsd/src/UidMap.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, versionCode 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 "UidMap.h"
+#include <cutils/log.h>
+#include <utils/Errors.h>
+
+using namespace android;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool UidMap::hasApp(int uid, const string& packageName) const {
+ lock_guard<mutex> lock(mMutex);
+
+ auto range = mMap.equal_range(uid);
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second.packageName == packageName) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int UidMap::getAppVersion(int uid, const string& packageName) const {
+ lock_guard<mutex> lock(mMutex);
+
+ auto range = mMap.equal_range(uid);
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second.packageName == packageName) {
+ return it->second.versionCode;
+ }
+ }
+ return 0;
+}
+
+void UidMap::updateMap(const vector <int32_t> &uid, const vector <int32_t> &versionCode,
+ const vector <String16> &packageName) {
+ lock_guard<mutex> lock(mMutex); // Exclusively lock for updates.
+
+ mMap.clear();
+ for (unsigned long j=0; j<uid.size(); j++) {
+ mMap.insert(make_pair(uid[j], AppData(string(String8(packageName[j]).string()),
+ versionCode[j])));
+ }
+
+ if (mOutput.initial_size() == 0) { // Provide the initial states in the mOutput proto
+ for (unsigned long j=0; j<uid.size(); j++) {
+ auto t = mOutput.add_initial();
+ t->set_app(string(String8(packageName[j]).string()));
+ t->set_version(int(versionCode[j]));
+ t->set_uid(uid[j]);
+ }
+ }
+}
+
+void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode){
+ lock_guard<mutex> lock(mMutex);
+
+ string app = string(String8(app_16).string());
+
+ // Notify any interested producers that this app has updated
+ for (auto it : mSubscribers) {
+ it->notifyAppUpgrade(app, uid, versionCode);
+ }
+
+ auto log = mOutput.add_changes();
+ log->set_deletion(false);
+ //log.timestamp = TODO: choose how timestamps are computed
+ log->set_app(app);
+ log->set_uid(uid);
+ log->set_version(versionCode);
+
+ auto range = mMap.equal_range(int(uid));
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second.packageName == app) {
+ it->second.versionCode = int(versionCode);
+ return;
+ }
+ ALOGD("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
+ return;
+ }
+
+ // Otherwise, we need to add an app at this uid.
+ mMap.insert(make_pair(uid, AppData(app, int(versionCode))));
+}
+
+
+void UidMap::removeApp(const String16& app_16, const int32_t& uid){
+ lock_guard<mutex> lock(mMutex);
+
+ string app = string(String8(app_16).string());
+
+ auto log = mOutput.add_changes();
+ log->set_deletion(true);
+ //log.timestamp = TODO: choose how timestamps are computed
+ log->set_app(app);
+ log->set_uid(uid);
+
+ auto range = mMap.equal_range(int(uid));
+ for (auto it = range.first; it != range.second; ++it) {
+ if (it->second.packageName == app) {
+ mMap.erase(it);
+ return;
+ }
+ }
+ ALOGD("removeApp failed to find the app %s with uid %i to remove", app.c_str(), uid);
+ return;
+}
+
+void UidMap::addListener(sp<PackageInfoListener> producer) {
+ lock_guard<mutex> lock(mMutex); // Lock for updates
+ mSubscribers.insert(producer);
+}
+
+void UidMap::removeListener(sp<PackageInfoListener> producer) {
+ lock_guard<mutex> lock(mMutex); // Lock for updates
+ mSubscribers.erase(producer);
+}
+
+UidMapping UidMap::getAndClearOutput() {
+ lock_guard<mutex> lock(mMutex); // Lock for updates
+
+ auto ret = UidMapping(mOutput); // Copy that will be returned.
+ mOutput.Clear();
+
+ // Re-initialize the initial state for the outputs. This results in extra data being uploaded
+ // but helps ensure we can't re-construct the UID->app name, versionCode mapping in server.
+ for (auto it : mMap) {
+ auto t = mOutput.add_initial();
+ t->set_app(it.second.packageName);
+ t->set_version(it.second.versionCode);
+ t->set_uid(it.first);
+ }
+
+ return ret;
+}
+
+void UidMap::printUidMap(FILE* out) {
+ lock_guard<mutex> lock(mMutex);
+
+ for (auto it : mMap) {
+ fprintf(out, "%s, v%d (%i)\n", it.second.packageName.c_str(), it.second.versionCode, it.first);
+ }
+}
+
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/UidMap.h b/cmds/statsd/src/UidMap.h
new file mode 100644
index 0000000..1481010
--- /dev/null
+++ b/cmds/statsd/src/UidMap.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#ifndef STATSD_UIDMAP_H
+#define STATSD_UIDMAP_H
+
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
+#include "PackageInfoListener.h"
+
+#include <binder/IResultReceiver.h>
+#include <binder/IShellCallback.h>
+#include <log/logprint.h>
+#include <mutex>
+#include <string>
+#include <stdio.h>
+#include <set>
+#include <unordered_map>
+#include <utils/RefBase.h>
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+struct AppData {
+ const string packageName;
+ int versionCode;
+
+ AppData(const string& a, const int v) : packageName(a), versionCode(v) {};
+};
+
+// UidMap keeps track of what the corresponding app name (APK name) and version code for every uid
+// at any given moment. This map must be updated by StatsCompanionService.
+class UidMap : public virtual android::RefBase {
+public:
+ /*
+ * All three inputs must be the same size, and the jth element in each array refers to the same
+ * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j].
+ */
+ void updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
+ const vector<String16>& packageName);
+
+ // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
+ bool hasApp(int uid, const string& packageName) const;
+
+ int getAppVersion(int uid, const string& packageName) const;
+
+ void updateApp(const String16& packageName, const int32_t& uid, const int32_t& versionCode);
+ void removeApp(const String16& packageName, const int32_t& uid);
+
+ // Helper for debugging contents of this uid map. Can be triggered with:
+ // adb shell cmd stats print-uid-map
+ void printUidMap(FILE* out);
+
+ // Commands for indicating to the map that a producer should be notified if an app is updated.
+ // This allows the metric producer to distinguish when the same uid or app represents a
+ // different version of an app.
+ void addListener(sp<PackageInfoListener> producer);
+ // Remove the listener from the set of metric producers that subscribe to updates.
+ void removeListener(sp<PackageInfoListener> producer);
+
+ // Grabs the current output contents and then clears it.
+ UidMapping getAndClearOutput();
+
+private:
+ // TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
+ mutable mutex mMutex;
+
+ std::unordered_multimap<int, AppData> mMap;
+
+ // We prepare the output proto as apps are updated, so that we can grab the current output.
+ UidMapping mOutput;
+
+ // Metric producers that should be notified if there's an upgrade in any app.
+ set<sp<PackageInfoListener>> mSubscribers;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+
+#endif //STATSD_UIDMAP_H
+
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index b303321..37477dc 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -20,6 +20,7 @@
#include "LogReader.h"
#include "StatsLogProcessor.h"
#include "StatsService.h"
+#include "UidMap.h"
#include <binder/IInterface.h>
#include <binder/IPCThreadState.h>
@@ -56,7 +57,7 @@
// Put the printer one first, so it will print before the real ones.
reader->AddListener(new LogEntryPrinter(STDOUT_FILENO));
- sp<StatsLogProcessor> main_processor = new StatsLogProcessor();
+ sp<StatsLogProcessor> main_processor = new StatsLogProcessor(data->service->getUidMap());
data->service->setProcessor(main_processor);
reader->AddListener(main_processor);
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 0729e2c..370cd468 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -26,6 +26,8 @@
#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+using namespace std;
+
namespace android {
namespace os {
namespace statsd {
@@ -44,6 +46,9 @@
void onDumpReport() override;
+ // TODO: Implement this later.
+ virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override {};
+
private:
const CountMetric mMetric;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 7d3d661..b7e9656 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -20,14 +20,17 @@
#include <log/logprint.h>
#include <utils/RefBase.h>
#include "../matchers/matcher_util.h"
+#include "PackageInfoListener.h"
namespace android {
namespace os {
namespace statsd {
// A MetricProducer is responsible for compute one single metrics, creating stats log report, and
-// writing the report to dropbox.
-class MetricProducer : public virtual RefBase {
+// writing the report to dropbox. MetricProducers should respond to package changes as required in
+// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
+// be a no-op.
+class MetricProducer : public virtual RefBase, public virtual PackageInfoListener {
public:
virtual ~MetricProducer(){};