From 947264128775be7f47d3e170a0fbeb6a14a5241e Mon Sep 17 00:00:00 2001 From: Bookatz Date: Thu, 31 Aug 2017 09:26:15 -0700 Subject: [PATCH 1/3] Set up StatsCompanionService.java Introduces StatsCompanionService.java and sets it up as a system service. This service is a helper for statsd. It will be responsible for setting and cancelling alarms related to polling stats and anomaly detection. It currently does not have selinux permission, so must be started manually (or with selinux temporarily disabled). Test: disable enforcing selinux, then set and cancel alarms and make sure they work. Change-Id: I3bd73acdd998ee424696cce40965134c14220d8f --- Android.mk | 1 + core/java/android/content/Context.java | 7 + .../android/os/IStatsCompanionService.aidl | 44 +++++ core/res/AndroidManifest.xml | 16 ++ .../server/stats/StatsCompanionService.java | 165 ++++++++++++++++++ .../java/com/android/server/SystemServer.java | 6 + 6 files changed, 239 insertions(+) create mode 100644 core/java/android/os/IStatsCompanionService.aidl create mode 100644 services/core/java/com/android/server/stats/StatsCompanionService.java diff --git a/Android.mk b/Android.mk index 76ca9b928aa21..8e09267f125d7 100644 --- a/Android.mk +++ b/Android.mk @@ -270,6 +270,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IRecoverySystemProgressListener.aidl \ core/java/android/os/IRemoteCallback.aidl \ core/java/android/os/ISchedulingPolicyService.aidl \ + core/java/android/os/IStatsCompanionService.aidl \ core/java/android/os/IStatsManager.aidl \ core/java/android/os/IThermalEventListener.aidl \ core/java/android/os/IThermalService.aidl \ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2d8249acb5bfa..03e4dfe829de5 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2991,6 +2991,7 @@ public abstract class Context { //@hide: CONTEXTHUB_SERVICE, SYSTEM_HEALTH_SERVICE, //@hide: INCIDENT_SERVICE, + //@hide: STATS_COMPANION_SERVICE, COMPANION_DEVICE_SERVICE }) @Retention(RetentionPolicy.SOURCE) @@ -4019,6 +4020,12 @@ public abstract class Context { */ public static final String INCIDENT_SERVICE = "incident"; + /** + * Service to assist statsd in obtaining general stats. + * @hide + */ + public static final String STATS_COMPANION_SERVICE = "statscompanion"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.content.om.OverlayManager} for managing overlay packages. diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl new file mode 100644 index 0000000000000..a29e375bcfbd8 --- /dev/null +++ b/core/java/android/os/IStatsCompanionService.aidl @@ -0,0 +1,44 @@ +/* + * 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. + */ + +package android.os; + +/** + * Binder interface to communicate with the Java-based statistics service helper. + * {@hide} + */ +interface IStatsCompanionService { + /** + * Register an alarm for anomaly detection to fire at the given timestamp (ms since epoch). + * If anomaly alarm had already been registered, it will be replaced with the new timestamp. + * Uses AlarmManager.set API, so if the timestamp is in the past, alarm fires immediately, and + * alarm is inexact. + */ + void setAnomalyAlarm(long timestampMs); + /** Cancel any anomaly detection alarm. */ + void cancelAnomalyAlarm(); + + /** + * Register a repeating alarm for polling to fire at the given timestamp and every + * intervalMs thereafter (in ms since epoch). + * If polling alarm had already been registered, it will be replaced by new one. + * Uses AlarmManager.setRepeating API, so if the timestamp is in past, alarm fires immediately, + * and alarm is inexact. + */ + void setPollingAlarms(long timestampMs, long intervalMs); + /** Cancel any repeating polling alarm. */ + void cancelPollingAlarms(); +} diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d6f67f6eb1a69..6ac352ed76126 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -3074,6 +3074,12 @@ + + + @@ -3854,6 +3860,16 @@ + + + + + + diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java new file mode 100644 index 0000000000000..5b77b1ed52699 --- /dev/null +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -0,0 +1,165 @@ +/* + * 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. + */ +package com.android.server.stats; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Binder; +import android.os.IStatsCompanionService; +import android.os.IStatsManager; +import android.os.Process; +import android.os.ServiceManager; +import android.util.Slog; + +import com.android.server.SystemService; + +/** + * Helper service for statsd (the native stats management service in cmds/statsd/). + * Used for registering and receiving alarms on behalf of statsd. + */ +public class StatsCompanionService extends IStatsCompanionService.Stub { + static final String TAG = "StatsCompanionService"; + static final boolean DEBUG = true; + + private final Context mContext; + private final AlarmManager mAlarmManager; + private final IStatsManager mStatsd; + + private final PendingIntent mAnomalyAlarmIntent; + private final PendingIntent mPollingAlarmIntent; + + public final static class AnomalyAlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred."); + // TODO: mStatsd.informAlarm(); // should be twoway so device won't sleep before acting? + // AlarmManager releases its own wakelock here. + } + }; + + public final static class PollingAlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (DEBUG) Slog.d(TAG, "Time to poll something."); + // TODO: mStatsd.poll(); // should be twoway so device won't sleep before acting? + // AlarmManager releases its own wakelock here. + } + }; + + public StatsCompanionService(Context context) { + super(); + mContext = context; + mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + + mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent(mContext, AnomalyAlarmReceiver.class), 0); + mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent(mContext, PollingAlarmReceiver.class), 0); + + mStatsd = getStatsdService(); + } + + /** Returns the statsd IBinder service */ + public static IStatsManager getStatsdService() { + return IStatsManager.Stub.asInterface(ServiceManager.getService("statsd")); + } + + public static final class Lifecycle extends SystemService { + private StatsCompanionService mStatsCompanionService; + + public Lifecycle(Context context) { + super(context); + } + + @Override + public void onStart() { + mStatsCompanionService = new StatsCompanionService(getContext()); + try { + publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService); + if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE); + } catch (Exception e) { + Slog.e(TAG, "Failed to publishBinderService", e); + } + } + } + + @Override // Binder call + public void setAnomalyAlarm(long timestampMs) { + enforceCallingPermission(); + if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs); + final long callingToken = Binder.clearCallingIdentity(); + try { + // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens. + // This alarm is inexact, leaving its exactness completely up to the OS optimizations. + // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm. + mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent); + } finally { + Binder.restoreCallingIdentity(callingToken); + } + } + + @Override // Binder call + public void cancelAnomalyAlarm() { + enforceCallingPermission(); + if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm"); + final long callingToken = Binder.clearCallingIdentity(); + try { + mAlarmManager.cancel(mAnomalyAlarmIntent); + } finally { + Binder.restoreCallingIdentity(callingToken); + } + } + + @Override // Binder call + public void setPollingAlarms(long timestampMs, long intervalMs) { + enforceCallingPermission(); + if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs + + " every " + intervalMs + "ms"); + final long callingToken = Binder.clearCallingIdentity(); + try { + // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens. + // This alarm is inexact, leaving its exactness completely up to the OS optimizations. + // TODO: totally inexact means that stats per bucket could be quite off. Is this okay? + mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs, + mPollingAlarmIntent); + } finally { + Binder.restoreCallingIdentity(callingToken); + } + } + + @Override // Binder call + public void cancelPollingAlarms() { + enforceCallingPermission(); + if (DEBUG) Slog.d(TAG, "Cancelling polling alarm"); + final long callingToken = Binder.clearCallingIdentity(); + try { + mAlarmManager.cancel(mPollingAlarmIntent); + } finally { + Binder.restoreCallingIdentity(callingToken); + } + } + + private void enforceCallingPermission() { + if (Binder.getCallingPid() == Process.myPid()) { + return; + } + mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null); + } + +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 57271fa109501..d411c74e4ecd0 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -103,6 +103,7 @@ import com.android.server.restrictions.RestrictionsManagerService; import com.android.server.security.KeyAttestationApplicationIdProviderService; import com.android.server.security.KeyChainSystemService; import com.android.server.soundtrigger.SoundTriggerService; +import com.android.server.stats.StatsCompanionService; import com.android.server.statusbar.StatusBarManagerService; import com.android.server.storage.DeviceStorageMonitorService; import com.android.server.telecom.TelecomLoaderService; @@ -1536,6 +1537,11 @@ public final class SystemServer { traceEnd(); } + // Statsd helper + traceBeginAndSlog("StartStatsCompanionService"); + mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class); + traceEnd(); + // Before things start rolling, be sure we have decided whether // we are in safe mode. final boolean safeMode = wm.detectSafeMode(); From 486d1cf358a5589263ac509ca513a30251bf20d6 Mon Sep 17 00:00:00 2001 From: Bookatz Date: Fri, 1 Sep 2017 13:10:41 -0700 Subject: [PATCH 2/3] StatsCompanionService connection to Statsd Created AnomalyMonitor in statsd, which can monitor statsd's internal anomaly alarms and register the soonest one with the StatsCompanionService. This cl introduces the AnomalyMonitor and allows it to register alarms. It does not yet allow for the removal of internal alarms. Test: manually added alarms and ensured they were correctly registered in StatsCompanionService Change-Id: Iff324c73751c4f43b1507dc64649b50ac388adef --- cmds/statsd/Android.mk | 2 + cmds/statsd/src/AnomalyMonitor.cpp | 112 ++++++++++++++++++++++++ cmds/statsd/src/AnomalyMonitor.h | 132 +++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 cmds/statsd/src/AnomalyMonitor.cpp create mode 100644 cmds/statsd/src/AnomalyMonitor.h diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index db8c89dafaba3..6c6e037ea38ac 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -24,7 +24,9 @@ LOCAL_MODULE := statsd LOCAL_SRC_FILES := \ ../../core/java/android/os/IStatsManager.aidl \ + ../../core/java/android/os/IStatsCompanionService.aidl \ src/StatsService.cpp \ + src/AnomalyMonitor.cpp \ src/LogEntryPrinter.cpp \ src/LogReader.cpp \ src/main.cpp diff --git a/cmds/statsd/src/AnomalyMonitor.cpp b/cmds/statsd/src/AnomalyMonitor.cpp new file mode 100644 index 0000000000000..8338d5026b460 --- /dev/null +++ b/cmds/statsd/src/AnomalyMonitor.cpp @@ -0,0 +1,112 @@ +/* + * 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 LOG_TAG "AnomalyMonitor" +#define DEBUG true + +#include + +#include +#include + +namespace statsd { + +AnomalyMonitor::AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec) + : mRegisteredAlarmTimeSec(0), + mMinUpdateTimeSec(minDiffToUpdateRegisteredAlarmTimeSec) { +} + +AnomalyMonitor::~AnomalyMonitor() { +} + +void AnomalyMonitor::add(sp alarm) { + if (alarm == nullptr) { + ALOGW("Asked to add a null alarm."); + return; + } + if (alarm->timestampSec < 1) { + // forbidden since a timestamp 0 is used to indicate no alarm registered + ALOGW("Asked to add a 0-time alarm."); + return; + } + std::lock_guard lock(mLock); + // TODO: Ensure that refractory period is respected. + if (DEBUG) ALOGD("Adding alarm with time %u", alarm->timestampSec); + mPq.push(alarm); + if (mRegisteredAlarmTimeSec < 1 || + alarm->timestampSec + mMinUpdateTimeSec < mRegisteredAlarmTimeSec) { + updateRegisteredAlarmTime(alarm->timestampSec); + } +} + +void AnomalyMonitor::remove(sp alarm) { + if (alarm == nullptr) { + ALOGW("Asked to remove a null alarm."); + return; + } + std::lock_guard lock(mLock); + if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec); + // TODO: make priority queue able to have items removed from it !!! + // mPq.remove(alarm); + if (mPq.empty()) { + if (DEBUG) ALOGD("Queue is empty. Cancel any alarm."); + mRegisteredAlarmTimeSec = 0; + // TODO: Make this resistant to doing work when companion is not ready yet + sp statsCompanionService = getStatsCompanion_l(); + if (statsCompanionService != nullptr) { + statsCompanionService->cancelAnomalyAlarm(); + } + return; + } + uint32_t soonestAlarmTimeSec = mPq.top()->timestampSec; + if (DEBUG) ALOGD("Soonest alarm is %u", soonestAlarmTimeSec); + if (soonestAlarmTimeSec > mRegisteredAlarmTimeSec + mMinUpdateTimeSec) { + updateRegisteredAlarmTime(soonestAlarmTimeSec); + } +} + +void AnomalyMonitor::updateRegisteredAlarmTime(uint32_t timestampSec) { + if (DEBUG) ALOGD("Updating reg alarm time to %u", timestampSec); + mRegisteredAlarmTimeSec = timestampSec; + sp statsCompanionService = getStatsCompanion_l(); + if (statsCompanionService != nullptr) { + statsCompanionService->setAnomalyAlarm(secToMs(mRegisteredAlarmTimeSec)); + } +} + +sp AnomalyMonitor::getStatsCompanion_l() { + if (mStatsCompanion != nullptr) { + return mStatsCompanion; + } + // Get statscompanion service from service manager + const sp sm(defaultServiceManager()); + if (sm != nullptr) { + const String16 name("statscompanion"); + mStatsCompanion = + interface_cast(sm->checkService(name)); + if (mStatsCompanion == nullptr) { + ALOGW("statscompanion service unavailable!"); + return nullptr; + } + } + return mStatsCompanion; +} + +int64_t AnomalyMonitor::secToMs(uint32_t timeSec) { + return ((int64_t) timeSec) * 1000; +} + +} // namespace statsd \ No newline at end of file diff --git a/cmds/statsd/src/AnomalyMonitor.h b/cmds/statsd/src/AnomalyMonitor.h new file mode 100644 index 0000000000000..63b806416c7e7 --- /dev/null +++ b/cmds/statsd/src/AnomalyMonitor.h @@ -0,0 +1,132 @@ +/* + * 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. + */ + +#ifndef ANOMALY_MONITOR_H +#define ANOMALY_MONITOR_H + +#include +#include + +#include +#include + +using namespace android::os; +using namespace android; + +namespace statsd { + +/** + * Represents an alarm, associated with some aggregate metric, holding a + * projected time at which the metric is expected to exceed its anomaly + * threshold. + * Timestamps are in seconds since epoch in a uint32, so will fail in year 2106. + */ +struct AnomalyAlarm : public RefBase { + AnomalyAlarm(uint32_t timestampSec) : timestampSec(timestampSec) { + } + + const uint32_t timestampSec; + + /** AnomalyAlarm a is higher priority than b if its timestamp is sooner. */ + struct Comparator { + bool operator()(sp a, sp b) const + { + return (a->timestampSec > b->timestampSec); + } + }; +}; + +/** + * Manages alarms for Anomaly Detection. + */ +class AnomalyMonitor { + public: + /** + * @param minDiffToUpdateRegisteredAlarmTimeSec If the soonest alarm differs + * from the registered alarm by more than this amount, update the registered + * alarm. + */ + AnomalyMonitor(uint32_t minDiffToUpdateRegisteredAlarmTimeSec); + ~AnomalyMonitor(); + + /** + * Adds the given alarm (reference) to the queue. + */ + void add(sp alarm); + + /** + * Removes the given alarm (reference) from the queue. + * Note that alarm comparison is reference-based; if another alarm exists + * with the same timestampSec, that alarm will still remain in the queue. + */ + void remove(sp alarm); + + /** + * Returns the projected alarm timestamp that is registered with + * StatsCompanionService. This may not be equal to the soonest alarm, + * but should be within minDiffToUpdateRegisteredAlarmTimeSec of it. + */ + uint32_t getRegisteredAlarmTimeSec() const { + return mRegisteredAlarmTimeSec; + } + + private: + /** Lock for accessing/writing to mPq. */ + std::mutex mLock; + + /** + * Timestamp (seconds since epoch) of the alarm registered with + * StatsCompanionService. This, in general, may not be equal to the soonest + * alarm stored in mPq, but should be within minUpdateTimeSec of it. + * A value of 0 indicates that no alarm is currently registered. + */ + uint32_t mRegisteredAlarmTimeSec; + + /** + * Priority queue of alarms, prioritized by soonest alarm.timestampSec. + */ + // TODO: use a priority queue from which elements can be removed + // Can I use the one in toolchain/gcc/gcc-4.9/include/fibheap.h? + std::priority_queue, std::vector>, + AnomalyAlarm::Comparator> mPq; + + /** + * Binder interface for communicating with StatsCompanionService. + */ + sp mStatsCompanion; + + /** + * Amount by which the soonest projected alarm must differ from + * mRegisteredAlarmTimeSec before updateRegisteredAlarmTime is called. + */ + uint32_t mMinUpdateTimeSec; + + /** + * Updates the alarm registered with StatsCompanionService to the given time. + * Also correspondingly updates mRegisteredAlarmTimeSec. + */ + void updateRegisteredAlarmTime(uint32_t timestampSec); + + /** Returns the StatsCompanionService. */ + sp getStatsCompanion_l(); + + /** Converts uint32 timestamp in seconds to a Java long in msec. */ + int64_t secToMs(uint32_t timeSec); +}; + +} // namespace statsd + +#endif // ANOMALY_MONITOR_H \ No newline at end of file From 0e95909ef0b2aac44f305551ea2aa03209b1eec0 Mon Sep 17 00:00:00 2001 From: Bookatz Date: Thu, 7 Sep 2017 17:39:37 -0700 Subject: [PATCH 3/3] indexed priority queue for AnomalyMonitor Created a new class, indexed_priority_queue, which is a priority queue that allows the removal of elements (other than just the top element). This is required for AnomalyMonitor, which will henceforth use it. Some tests for this new class are included. I have only implemented the methods in indexed_priority_queue that are currently needed. For example, pop_top() has not been written as AnomalyMonitor does not need it. Test: adb shell data/nativetest64/statsd_test/statsd_test Change-Id: I82fe220ee5a879189b0cfa03b551c829cfdd05f0 --- cmds/statsd/Android.mk | 1 + cmds/statsd/src/AnomalyMonitor.cpp | 3 +- cmds/statsd/src/AnomalyMonitor.h | 15 +- cmds/statsd/src/indexed_priority_queue.h | 195 ++++++++++++++++++ .../tests/indexed_priority_queue_test.cpp | 188 +++++++++++++++++ 5 files changed, 391 insertions(+), 11 deletions(-) create mode 100644 cmds/statsd/src/indexed_priority_queue.h create mode 100644 cmds/statsd/tests/indexed_priority_queue_test.cpp diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 6c6e037ea38ac..b09d961cd61bd 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -89,6 +89,7 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH)/src LOCAL_SRC_FILES := \ ../../core/java/android/os/IStatsManager.aidl \ src/StatsService.cpp \ + tests/indexed_priority_queue_test.cpp \ src/LogEntryPrinter.cpp \ src/LogReader.cpp \ tests/LogReader_test.cpp \ diff --git a/cmds/statsd/src/AnomalyMonitor.cpp b/cmds/statsd/src/AnomalyMonitor.cpp index 8338d5026b460..d73de957881aa 100644 --- a/cmds/statsd/src/AnomalyMonitor.cpp +++ b/cmds/statsd/src/AnomalyMonitor.cpp @@ -59,8 +59,7 @@ void AnomalyMonitor::remove(sp alarm) { } std::lock_guard lock(mLock); if (DEBUG) ALOGD("Removing alarm with time %u", alarm->timestampSec); - // TODO: make priority queue able to have items removed from it !!! - // mPq.remove(alarm); + mPq.remove(alarm); if (mPq.empty()) { if (DEBUG) ALOGD("Queue is empty. Cancel any alarm."); mRegisteredAlarmTimeSec = 0; diff --git a/cmds/statsd/src/AnomalyMonitor.h b/cmds/statsd/src/AnomalyMonitor.h index 63b806416c7e7..5418cf0dc5525 100644 --- a/cmds/statsd/src/AnomalyMonitor.h +++ b/cmds/statsd/src/AnomalyMonitor.h @@ -17,6 +17,7 @@ #ifndef ANOMALY_MONITOR_H #define ANOMALY_MONITOR_H +#include #include #include @@ -40,11 +41,10 @@ struct AnomalyAlarm : public RefBase { const uint32_t timestampSec; - /** AnomalyAlarm a is higher priority than b if its timestamp is sooner. */ - struct Comparator { - bool operator()(sp a, sp b) const - { - return (a->timestampSec > b->timestampSec); + /** AnomalyAlarm a is smaller (higher priority) than b if its timestamp is sooner. */ + struct SmallerTimestamp { + bool operator()(sp a, sp b) const { + return (a->timestampSec < b->timestampSec); } }; }; @@ -98,10 +98,7 @@ class AnomalyMonitor { /** * Priority queue of alarms, prioritized by soonest alarm.timestampSec. */ - // TODO: use a priority queue from which elements can be removed - // Can I use the one in toolchain/gcc/gcc-4.9/include/fibheap.h? - std::priority_queue, std::vector>, - AnomalyAlarm::Comparator> mPq; + indexed_priority_queue mPq; /** * Binder interface for communicating with StatsCompanionService. diff --git a/cmds/statsd/src/indexed_priority_queue.h b/cmds/statsd/src/indexed_priority_queue.h new file mode 100644 index 0000000000000..d302f855b1bf3 --- /dev/null +++ b/cmds/statsd/src/indexed_priority_queue.h @@ -0,0 +1,195 @@ +/* + * 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. + */ + +#ifndef STATSD_INDEXED_PRIORITY_QUEUE_H +#define STATSD_INDEXED_PRIORITY_QUEUE_H + +// ALOGE can be called from this file. If header loaded by another class, use their LOG_TAG instead. +#ifndef LOG_TAG +#define LOG_TAG "statsd(indexed_priority_queue)" +#endif //LOG_TAG + +#include +#include +#include +#include + +using namespace android; + +namespace statsd { + +/** Defines a hash function for sp, returning the hash of the underlying pointer. */ +template +struct SpHash { + size_t operator()(const sp& k) const { + return std::hash()(k.get()); + } +}; + +/** + * Min priority queue for generic type AA. + * Unlike a regular priority queue, this class is also capable of removing interior elements. + * @tparam Comparator must implement [bool operator()(sp a, sp b)], returning + * whether a should be closer to the top of the queue than b. + */ +template +class indexed_priority_queue { + public: + indexed_priority_queue(); + /** Adds a into the priority queue. If already present or a==nullptr, does nothing. */ + void push(sp a); + /** Removes a from the priority queue. If not present or a==nullptr, does nothing. */ + void remove(sp a); + /** Removes all elements. */ + void clear(); + /** Returns whether priority queue contains a (not just a copy of a, but a itself). */ + bool contains(sp a) const; + /** Returns min element. Returns nullptr iff empty(). */ + sp top() const; + /** Returns number of elements in priority queue. */ + size_t size() const { return pq.size() - 1; } // pq is 1-indexed + /** Returns true iff priority queue is empty. */ + bool empty() const { return size() < 1; } + + private: + /** Vector representing a min-heap (1-indexed, with nullptr at 0). */ + std::vector> pq; + /** Mapping of each element in pq to its index in pq (i.e. the inverse of a=pq[i]). */ + std::unordered_map, size_t, SpHash> indices; + + void init(); + void sift_up(size_t idx); + void sift_down(size_t idx); + /** Returns whether pq[idx1] is considered higher than pq[idx2], according to Comparator. */ + bool higher(size_t idx1, size_t idx2) const; + void swap_indices(size_t i, size_t j); +}; + +// Implementation must be done in this file due to use of template. + +template +indexed_priority_queue::indexed_priority_queue() { + init(); +} + +template +void indexed_priority_queue::push(sp a) { + if (a == nullptr) return; + if (contains(a)) return; + pq.push_back(a); + size_t idx = size(); // index of last element since 1-indexed + indices.insert({a, idx}); + sift_up(idx); // get the pq back in order +} + +template +void indexed_priority_queue::remove(sp a) { + if (a == nullptr) return; + if (!contains(a)) return; + size_t idx = indices[a]; + if (idx >= pq.size()) { + ALOGE("indexed_priority_queue: Invalid index in map of indices."); + return; + } + if (idx == size()) { // if a is the last element, i.e. at index idx == size() == (pq.size()-1) + pq.pop_back(); + indices.erase(a); + return; + } + // move last element (guaranteed not to be at idx) to idx, then delete a + sp last_a = pq.back(); + pq[idx] = last_a; + pq.pop_back(); + indices[last_a] = idx; + indices.erase(a); + + // get the heap back in order (since the element at idx is not in order) + sift_up(idx); + sift_down(idx); +} + +template +void indexed_priority_queue::clear() { + pq.clear(); + indices.clear(); + init(); +} + +template +sp indexed_priority_queue::top() const { + if (empty()) return nullptr; + return pq[1]; +} + +template +void indexed_priority_queue::init() { + pq.push_back(nullptr); // so that pq is 1-indexed. + indices.insert({nullptr, 0}); // just to be consistent with pq. +} + +template +void indexed_priority_queue::sift_up(size_t idx) { + while (idx > 1) { + size_t parent = idx/2; + if (higher(idx, parent)) swap_indices(idx, parent); + else break; + idx = parent; + } +} + +template +void indexed_priority_queue::sift_down(size_t idx) { + while (2*idx <= size()) { + size_t child = 2 * idx; + if (child < size() && higher(child+1, child)) child++; + if (higher(child, idx)) swap_indices(child, idx); + else break; + idx = child; + } +} + +template +bool indexed_priority_queue::higher(size_t idx1, size_t idx2) const { + if (!(0u < idx1 && idx1 < pq.size() && 0u < idx2 && idx2 < pq.size())) { + ALOGE("indexed_priority_queue: Attempting to access invalid index"); + return false; // got to do something. + } + return Comparator()(pq[idx1], pq[idx2]); +} + +template +bool indexed_priority_queue::contains(sp a) const { + if (a == nullptr) return false; // publicly, we pretend that nullptr is not actually in pq. + return indices.count(a) > 0; +} + +template +void indexed_priority_queue::swap_indices(size_t i, size_t j) { + if (!(0u < i && i < pq.size() && 0u < j && j < pq.size())) { + ALOGE("indexed_priority_queue: Attempting to swap invalid index"); + return; + } + sp val_i = pq[i]; + sp val_j = pq[j]; + pq[i] = val_j; + pq[j] = val_i; + indices[val_i] = j; + indices[val_j] = i; +} + +} // namespace statsd + +#endif //STATSD_INDEXED_PRIORITY_QUEUE_H diff --git a/cmds/statsd/tests/indexed_priority_queue_test.cpp b/cmds/statsd/tests/indexed_priority_queue_test.cpp new file mode 100644 index 0000000000000..a679128dd37e8 --- /dev/null +++ b/cmds/statsd/tests/indexed_priority_queue_test.cpp @@ -0,0 +1,188 @@ +/* + * 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/indexed_priority_queue.h" + +#include + +using namespace statsd; + +/** struct for template in indexed_priority_queue */ +struct AATest : public RefBase { + AATest(uint32_t val) : val(val) { + } + + const int val; + + struct Smaller { + bool operator()(const sp a, const sp b) const { + return (a->val < b->val); + } + }; +}; + +#ifdef __ANDROID__ +TEST(indexed_priority_queue, empty_and_size) { + indexed_priority_queue ipq; + sp aa4 = new AATest{4}; + sp aa8 = new AATest{8}; + + EXPECT_EQ(0u, ipq.size()); + EXPECT_TRUE(ipq.empty()); + + ipq.push(aa4); + EXPECT_EQ(1u, ipq.size()); + EXPECT_FALSE(ipq.empty()); + + ipq.push(aa8); + EXPECT_EQ(2u, ipq.size()); + EXPECT_FALSE(ipq.empty()); + + ipq.remove(aa4); + EXPECT_EQ(1u, ipq.size()); + EXPECT_FALSE(ipq.empty()); + + ipq.remove(aa8); + EXPECT_EQ(0u, ipq.size()); + EXPECT_TRUE(ipq.empty()); +} + +TEST(indexed_priority_queue, top) { + indexed_priority_queue ipq; + sp aa2 = new AATest{2}; + sp aa4 = new AATest{4}; + sp aa8 = new AATest{8}; + sp aa12 = new AATest{12}; + sp aa16 = new AATest{16}; + sp aa20 = new AATest{20}; + + EXPECT_EQ(ipq.top(), nullptr); + + // add 8, 4, 12 + ipq.push(aa8); + EXPECT_EQ(ipq.top(), aa8); + + ipq.push(aa12); + EXPECT_EQ(ipq.top(), aa8); + + ipq.push(aa4); + EXPECT_EQ(ipq.top(), aa4); + + // remove 12, 4 + ipq.remove(aa12); + EXPECT_EQ(ipq.top(), aa4); + + ipq.remove(aa4); + EXPECT_EQ(ipq.top(), aa8); + + // add 16, 2, 20 + ipq.push(aa16); + EXPECT_EQ(ipq.top(), aa8); + + ipq.push(aa2); + EXPECT_EQ(ipq.top(), aa2); + + ipq.push(aa20); + EXPECT_EQ(ipq.top(), aa2); + + // remove 2, 20, 16, 8 + ipq.remove(aa2); + EXPECT_EQ(ipq.top(), aa8); + + ipq.remove(aa20); + EXPECT_EQ(ipq.top(), aa8); + + ipq.remove(aa16); + EXPECT_EQ(ipq.top(), aa8); + + ipq.remove(aa8); + EXPECT_EQ(ipq.top(), nullptr); +} + +TEST(indexed_priority_queue, push_same_aa) { + indexed_priority_queue ipq; + sp aa4_a = new AATest{4}; + sp aa4_b = new AATest{4}; + + ipq.push(aa4_a); + EXPECT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); + + ipq.push(aa4_a); + EXPECT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); + + ipq.push(aa4_b); + EXPECT_EQ(2u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_TRUE(ipq.contains(aa4_b)); +} + + +TEST(indexed_priority_queue, remove_nonexistant) { + indexed_priority_queue ipq; + sp aa4 = new AATest{4}; + sp aa5 = new AATest{5}; + + ipq.push(aa4); + ipq.remove(aa5); + EXPECT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4)); + EXPECT_FALSE(ipq.contains(aa5)); +} + +TEST(indexed_priority_queue, remove_same_aa) { + indexed_priority_queue ipq; + sp aa4_a = new AATest{4}; + sp aa4_b = new AATest{4}; + + ipq.push(aa4_a); + ipq.push(aa4_b); + EXPECT_EQ(2u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_TRUE(ipq.contains(aa4_b)); + + ipq.remove(aa4_b); + EXPECT_EQ(1u, ipq.size()); + EXPECT_TRUE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); + + ipq.remove(aa4_a); + EXPECT_EQ(0u, ipq.size()); + EXPECT_FALSE(ipq.contains(aa4_a)); + EXPECT_FALSE(ipq.contains(aa4_b)); +} + +TEST(indexed_priority_queue, nulls) { + indexed_priority_queue ipq; + + EXPECT_TRUE(ipq.empty()); + EXPECT_FALSE(ipq.contains(nullptr)); + + ipq.push(nullptr); + EXPECT_TRUE(ipq.empty()); + EXPECT_FALSE(ipq.contains(nullptr)); + + ipq.remove(nullptr); + EXPECT_TRUE(ipq.empty()); + EXPECT_FALSE(ipq.contains(nullptr)); +} + +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif