Statsd broadcast subscriber
Allows a uid that uploads a statsd config to additionally
register a BroadcastSubscriber with statsd. If statsd
detects an anomaly (according to the config's Alert),
statsd can inform a BroadcastSubscriber provided in the config.
The config uses a subscriberId (just an int) to identify the
BroadcastSubscriber. It then uses StatsManager.setBroadcastSubscriber
to associate that subscriberId with a given PendingIntent.
Then, when the anomaly is detected, statsd sends a broadcast
using that PendingIntent, alerting whoever was specified by
the config/setBroadcastSubscriber.
Bug: 70356901
Test: cts-tradefed run cts-dev -m CtsStatsdHostTestCases -t android.cts.statsd.alert.BroadcastSubscriberTests
Change-Id: I4d9ea9a6c8a85e61fadfd99c1513c55abbadd5e9
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index a7daa3f..ebc62ba 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -62,6 +62,7 @@
src/storage/StorageManager.cpp \
src/StatsLogProcessor.cpp \
src/StatsService.cpp \
+ src/subscriber/SubscriberReporter.cpp \
src/HashableDimensionKey.cpp \
src/guardrail/MemoryLeakTrackUtil.cpp \
src/guardrail/StatsdStats.cpp
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index ba628b8..ba34e59 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -24,6 +24,7 @@
#include "guardrail/MemoryLeakTrackUtil.h"
#include "guardrail/StatsdStats.h"
#include "storage/StorageManager.h"
+#include "subscriber/SubscriberReporter.h"
#include <android-base/file.h>
#include <binder/IPCThreadState.h>
@@ -67,6 +68,7 @@
void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) {
ALOGW("statscompanion service died");
mAnomalyMonitor->setStatsCompanionService(nullptr);
+ SubscriberReporter::getInstance().setStatsCompanionService(nullptr);
}
// ======================================================================
@@ -681,6 +683,7 @@
VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
mAnomalyMonitor->setStatsCompanionService(statsCompanion);
+ SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion);
return Status::ok();
}
@@ -743,7 +746,9 @@
Status StatsService::removeConfiguration(int64_t key, bool* success) {
IPCThreadState* ipc = IPCThreadState::self();
if (checkCallingPermission(String16(kPermissionDump))) {
- mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key));
+ ConfigKey configKey(ipc->getCallingUid(), key);
+ mConfigManager->RemoveConfig(configKey);
+ SubscriberReporter::getInstance().removeConfig(configKey);
*success = true;
return Status::ok();
} else {
@@ -752,6 +757,42 @@
}
}
+Status StatsService::setBroadcastSubscriber(int64_t configId,
+ int64_t subscriberId,
+ const sp<android::IBinder>& intentSender,
+ bool* success) {
+ VLOG("StatsService::setBroadcastSubscriber called.");
+ IPCThreadState* ipc = IPCThreadState::self();
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ ConfigKey configKey(ipc->getCallingUid(), configId);
+ SubscriberReporter::getInstance()
+ .setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ *success = true;
+ return Status::ok();
+ } else {
+ *success = false;
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
+Status StatsService::unsetBroadcastSubscriber(int64_t configId,
+ int64_t subscriberId,
+ bool* success) {
+ VLOG("StatsService::unsetBroadcastSubscriber called.");
+ IPCThreadState* ipc = IPCThreadState::self();
+ if (checkCallingPermission(String16(kPermissionDump))) {
+ ConfigKey configKey(ipc->getCallingUid(), configId);
+ SubscriberReporter::getInstance()
+ .unsetBroadcastSubscriber(configKey, subscriberId);
+ *success = true;
+ return Status::ok();
+ } else {
+ *success = false;
+ return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+ }
+}
+
+
void StatsService::binderDied(const wp <IBinder>& who) {
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 8d29970..ba6bd24 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -99,6 +99,21 @@
*/
virtual Status removeConfiguration(int64_t key, bool* success) override;
+ /**
+ * Binder call to associate the given config's subscriberId with the given intentSender.
+ * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+ */
+ virtual Status setBroadcastSubscriber(int64_t configId,
+ int64_t subscriberId,
+ const sp<android::IBinder>& intentSender,
+ bool* success) override;
+
+ /**
+ * Binder call to unassociate the given config's subscriberId with any intentSender.
+ */
+ virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId,
+ bool* success) override;
+
// TODO: public for testing since statsd doesn't run when system starts. Change to private
// later.
/** Inform statsCompanion that statsd is ready. */
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index e34aed3..ded6c4c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -21,6 +21,7 @@
#include "external/Perfetto.h"
#include "guardrail/StatsdStats.h"
#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+#include "subscriber/SubscriberReporter.h"
#include <android/os/IIncidentManager.h>
#include <android/os/IncidentReportArgs.h>
@@ -233,6 +234,7 @@
}
std::set<int> incidentdSections;
+
for (const Subscription& subscription : mSubscriptions) {
switch (subscription.subscriber_information_case()) {
case Subscription::SubscriberInformationCase::kIncidentdDetails:
@@ -243,6 +245,10 @@
case Subscription::SubscriberInformationCase::kPerfettoDetails:
CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
break;
+ case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
+ SubscriberReporter::getInstance()
+ .alertBroadcastSubscriber(mConfigKey, subscription, key);
+ break;
default:
break;
}
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index de7093d..33e55ab 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -51,10 +51,8 @@
// Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
// and removes it from firedAlarms.
- // TODO: This will actually be called from a different thread, so make it thread-safe!
- // This means that almost every function in DurationAnomalyTracker needs to be locked.
- // But this should be done at the level of StatsLogProcessor, which needs to lock
- // mMetricsMangers anyway.
+ // Note that this will generally be called from a different thread from the other functions;
+ // the caller is responsible for thread safety.
void informAlarmsFired(const uint64_t& timestampNs,
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
new file mode 100644
index 0000000..f912e4b
--- /dev/null
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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 "Log.h"
+
+#include "SubscriberReporter.h"
+
+using android::IBinder;
+using std::lock_guard;
+using std::unordered_map;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId,
+ const sp<IBinder>& intentSender) {
+ VLOG("SubscriberReporter::setBroadcastSubscriber called.");
+ lock_guard<std::mutex> lock(mLock);
+ mIntentMap[configKey][subscriberId] = intentSender;
+}
+
+void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId) {
+ VLOG("SubscriberReporter::unsetBroadcastSubscriber called.");
+ lock_guard<std::mutex> lock(mLock);
+ auto subscriberMapIt = mIntentMap.find(configKey);
+ if (subscriberMapIt != mIntentMap.end()) {
+ subscriberMapIt->second.erase(subscriberId);
+ if (subscriberMapIt->second.empty()) {
+ mIntentMap.erase(configKey);
+ }
+ }
+}
+
+void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
+ VLOG("SubscriberReporter::removeConfig called.");
+ lock_guard<std::mutex> lock(mLock);
+ mIntentMap.erase(configKey);
+}
+
+void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const {
+ // Reminder about ids:
+ // subscription id - name of the Subscription (that ties the Alert to the broadcast)
+ // subscription rule_id - the name of the Alert (that triggers the broadcast)
+ // subscriber_id - name of the PendingIntent to use to send the broadcast
+ // config uid - the uid that uploaded the config (and therefore gave the PendingIntent,
+ // although the intent may be to broadcast to a different uid)
+ // config id - the name of this config (for this particular uid)
+
+ VLOG("SubscriberReporter::alertBroadcastSubscriber called.");
+ lock_guard<std::mutex> lock(mLock);
+
+ if (!subscription.has_broadcast_subscriber_details()
+ || !subscription.broadcast_subscriber_details().has_subscriber_id()) {
+ ALOGE("Broadcast subscriber does not have an id.");
+ return;
+ }
+ int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
+
+ auto it1 = mIntentMap.find(configKey);
+ if (it1 == mIntentMap.end()) {
+ ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str());
+ return;
+ }
+ auto it2 = it1->second.find(subscriberId);
+ if (it2 == it1->second.end()) {
+ ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ",
+ configKey.ToString().c_str(), (long long)subscriberId);
+ return;
+ }
+ sendBroadcastLocked(it2->second, configKey, subscription, dimKey);
+}
+
+void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
+ const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const {
+ VLOG("SubscriberReporter::sendBroadcastLocked called.");
+ if (mStatsCompanionService == nullptr) {
+ ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
+ return;
+ }
+ mStatsCompanionService->sendSubscriberBroadcast(intentSender,
+ configKey.GetUid(),
+ configKey.GetId(),
+ subscription.id(),
+ subscription.rule_id(),
+ protoToStatsDimensionsValue(dimKey));
+}
+
+StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
+ const HashableDimensionKey& dimKey) {
+ return protoToStatsDimensionsValue(dimKey.getDimensionsValue());
+}
+
+StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
+ const DimensionsValue& protoDimsVal) {
+ int32_t field = protoDimsVal.field();
+
+ switch (protoDimsVal.value_case()) {
+ case DimensionsValue::ValueCase::kValueStr:
+ return StatsDimensionsValue(field, String16(protoDimsVal.value_str().c_str()));
+ case DimensionsValue::ValueCase::kValueInt:
+ return StatsDimensionsValue(field, static_cast<int32_t>(protoDimsVal.value_int()));
+ case DimensionsValue::ValueCase::kValueLong:
+ return StatsDimensionsValue(field, static_cast<int64_t>(protoDimsVal.value_long()));
+ case DimensionsValue::ValueCase::kValueBool:
+ return StatsDimensionsValue(field, static_cast<bool>(protoDimsVal.value_bool()));
+ case DimensionsValue::ValueCase::kValueFloat:
+ return StatsDimensionsValue(field, static_cast<float>(protoDimsVal.value_float()));
+ case DimensionsValue::ValueCase::kValueTuple:
+ {
+ int sz = protoDimsVal.value_tuple().dimensions_value_size();
+ std::vector<StatsDimensionsValue> sdvVec(sz);
+ for (int i = 0; i < sz; i++) {
+ sdvVec[i] = protoToStatsDimensionsValue(
+ protoDimsVal.value_tuple().dimensions_value(i));
+ }
+ return StatsDimensionsValue(field, sdvVec);
+ }
+ default:
+ ALOGW("protoToStatsDimensionsValue failed: illegal type.");
+ return StatsDimensionsValue();
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
new file mode 100644
index 0000000..5bb458a
--- /dev/null
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#pragma once
+
+#include <android/os/IStatsCompanionService.h>
+#include <utils/RefBase.h>
+
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // subscription
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h" // DimensionsValue
+#include "android/os/StatsDimensionsValue.h"
+#include "HashableDimensionKey.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Reports information to subscribers.
+// Single instance shared across the process. All methods are thread safe.
+class SubscriberReporter {
+public:
+ /** Get (singleton) instance of SubscriberReporter. */
+ static SubscriberReporter& getInstance() {
+ static SubscriberReporter subscriberReporter;
+ return subscriberReporter;
+ }
+
+ ~SubscriberReporter(){};
+ SubscriberReporter(SubscriberReporter const&) = delete;
+ void operator=(SubscriberReporter const&) = delete;
+
+ /**
+ * Tells SubscriberReporter what IStatsCompanionService to use.
+ * May be nullptr, but SubscriberReporter will not send broadcasts for any calls
+ * to alertBroadcastSubscriber that occur while nullptr.
+ */
+ void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+ std::lock_guard<std::mutex> lock(mLock);
+ sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+ mStatsCompanionService = statsCompanionService;
+ }
+
+ /**
+ * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair.
+ * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+ */
+ void setBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId,
+ const sp<android::IBinder>& intentSender);
+
+ /**
+ * Erases any intentSender information from the given (configKey, subscriberId) pair.
+ */
+ void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
+
+ /** Remove all information stored by SubscriberReporter about the given config. */
+ void removeConfig(const ConfigKey& configKey);
+
+ /**
+ * Sends a broadcast via the intentSender previously stored for the
+ * given (configKey, subscriberId) pair by setBroadcastSubscriber.
+ * Information about the subscriber, as well as information extracted from the dimKey, is sent.
+ */
+ void alertBroadcastSubscriber(const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const;
+
+private:
+ SubscriberReporter() {};
+
+ mutable std::mutex mLock;
+
+ /** Binder interface for communicating with StatsCompanionService. */
+ sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+ /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */
+ std::unordered_map<ConfigKey,
+ std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap;
+
+ /**
+ * Sends a broadcast via the given intentSender (using mStatsCompanionService), along
+ * with the information in the other parameters.
+ */
+ void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
+ const ConfigKey& configKey,
+ const Subscription& subscription,
+ const HashableDimensionKey& dimKey) const;
+
+ /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */
+ static StatsDimensionsValue protoToStatsDimensionsValue(
+ const DimensionsValue& protoDimsVal);
+
+ /** Converts a HashableDimensionKey to a StatsDimensionsValue. */
+ static StatsDimensionsValue protoToStatsDimensionsValue(
+ const HashableDimensionKey& dimKey);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 4f9032f..d39aa1d 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -16,12 +16,12 @@
package com.android.statsd.dogfood;
import android.app.Activity;
+import android.app.StatsManager;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import android.util.StatsLog;
-import android.util.StatsManager;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index 26c1c72..652f6b2 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.StatsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -34,7 +35,6 @@
import android.text.TextWatcher;
import android.util.Log;
import android.util.StatsLog;
-import android.util.StatsManager;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.view.MotionEvent;