am aeb1205a: am 3d2b6212: Merge "high-frequency notification stats." into mnc-dev

* commit 'aeb1205a388c8190829a93808f51980ece14558d':
  high-frequency notification stats.
This commit is contained in:
Chris Wren
2015-06-17 16:04:23 +00:00
committed by Android Git Automerger
9 changed files with 164 additions and 113 deletions

View File

@@ -33,7 +33,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt
the top of the ranking order, before it falls back to its natural position. */
private static final long HANG_TIME_MS = 10000;
public void initialize(Context ctx) {
public void initialize(Context ctx, NotificationUsageStats usageStats) {
if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
}

View File

@@ -856,8 +856,10 @@ public class NotificationManagerService extends SystemService {
} catch (Resources.NotFoundException e) {
extractorNames = new String[0];
}
mUsageStats = new NotificationUsageStats(getContext());
mRankingHelper = new RankingHelper(getContext(),
new RankingWorkerHandler(mRankingThread.getLooper()),
mUsageStats,
extractorNames);
mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
@@ -882,7 +884,6 @@ public class NotificationManagerService extends SystemService {
});
final File systemDir = new File(Environment.getDataDirectory(), "system");
mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
mUsageStats = new NotificationUsageStats(getContext());
importOldBlockDb();
@@ -2074,6 +2075,7 @@ public class NotificationManagerService extends SystemService {
r.score = JUNK_SCORE;
Slog.e(TAG, "Suppressing notification from package " + pkg
+ " by user request.");
mUsageStats.registerBlocked(r);
}
}
@@ -2739,12 +2741,6 @@ public class NotificationManagerService extends SystemService {
case REASON_NOMAN_CANCEL_ALL:
mUsageStats.registerRemovedByApp(r);
break;
case REASON_DELEGATE_CLICK:
mUsageStats.registerCancelDueToClick(r);
break;
default:
mUsageStats.registerCancelUnknown(r);
break;
}
mNotificationsByKey.remove(r.sbn.getKey());

View File

@@ -26,7 +26,7 @@ import android.content.Context;
public interface NotificationSignalExtractor {
/** One-time initialization. */
public void initialize(Context context);
public void initialize(Context context, NotificationUsageStats usageStats);
/**
* Called once per notification that is posted or updated.

View File

@@ -25,13 +25,13 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import com.android.internal.logging.MetricsLogger;
import com.android.server.notification.NotificationManagerService.DumpFilter;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
@@ -47,21 +47,45 @@ import java.util.Map;
* {@hide}
*/
public class NotificationUsageStats {
// WARNING: Aggregated stats can grow unboundedly with pkg+id+tag.
// Don't enable on production builds.
private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = false;
private static final boolean ENABLE_SQLITE_LOG = true;
private static final String TAG = "NotificationUsageStats";
private static final boolean ENABLE_AGGREGATED_IN_MEMORY_STATS = true;
private static final boolean ENABLE_SQLITE_LOG = true;
private static final AggregatedStats[] EMPTY_AGGREGATED_STATS = new AggregatedStats[0];
private static final String DEVICE_GLOBAL_STATS = "__global"; // packages start with letters
private static final int MSG_EMIT = 1;
private static final boolean DEBUG = false;
public static final int TEN_SECONDS = 1000 * 10;
public static final int ONE_HOUR = 1000 * 60 * 60;
private static final long EMIT_PERIOD = DEBUG ? TEN_SECONDS : ONE_HOUR;
// Guarded by synchronized(this).
private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>();
private final Map<String, AggregatedStats> mStats = new HashMap<>();
private final ArrayDeque<AggregatedStats[]> mStatsArrays = new ArrayDeque<>();
private final SQLiteLog mSQLiteLog;
private final Context mContext;
private final Handler mHandler;
private long mLastEmitTime;
public NotificationUsageStats(Context context) {
mContext = context;
mLastEmitTime = SystemClock.elapsedRealtime();
mSQLiteLog = ENABLE_SQLITE_LOG ? new SQLiteLog(context) : null;
mHandler = new Handler(mContext.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_EMIT:
emit();
break;
default:
Log.wtf(TAG, "Unknown message type: " + msg.what);
break;
}
}
};
mHandler.sendEmptyMessageDelayed(MSG_EMIT, EMIT_PERIOD);
}
/**
@@ -70,9 +94,12 @@ public class NotificationUsageStats {
public synchronized void registerPostedByApp(NotificationRecord notification) {
notification.stats = new SingleNotificationStats();
notification.stats.posttimeElapsedMs = SystemClock.elapsedRealtime();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
for (AggregatedStats stats : aggregatedStatsArray) {
stats.numPostedByApp++;
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
if (ENABLE_SQLITE_LOG) {
mSQLiteLog.logPosted(notification);
}
@@ -83,9 +110,11 @@ public class NotificationUsageStats {
*/
public void registerUpdatedByApp(NotificationRecord notification, NotificationRecord old) {
notification.stats = old.stats;
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
for (AggregatedStats stats : aggregatedStatsArray) {
stats.numUpdatedByApp++;
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
}
/**
@@ -93,10 +122,11 @@ public class NotificationUsageStats {
*/
public synchronized void registerRemovedByApp(NotificationRecord notification) {
notification.stats.onRemoved();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
for (AggregatedStats stats : aggregatedStatsArray) {
stats.numRemovedByApp++;
stats.collect(notification.stats);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
if (ENABLE_SQLITE_LOG) {
mSQLiteLog.logRemoved(notification);
}
@@ -109,10 +139,6 @@ public class NotificationUsageStats {
MetricsLogger.histogram(mContext, "note_dismiss_longevity",
(int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onDismiss();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numDismissedByUser++;
stats.collect(notification.stats);
}
if (ENABLE_SQLITE_LOG) {
mSQLiteLog.logDismissed(notification);
}
@@ -125,36 +151,36 @@ public class NotificationUsageStats {
MetricsLogger.histogram(mContext, "note_click_longevity",
(int) (System.currentTimeMillis() - notification.getRankingTimeMs()) / (60 * 1000));
notification.stats.onClick();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.numClickedByUser++;
}
if (ENABLE_SQLITE_LOG) {
mSQLiteLog.logClicked(notification);
}
}
/**
* Called when the notification is canceled because the user clicked it.
*
* <p>Called after {@link #registerClickedByUser(NotificationRecord)}.</p>
*/
public synchronized void registerCancelDueToClick(NotificationRecord notification) {
notification.stats.onCancel();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.collect(notification.stats);
public synchronized void registerPeopleAffinity(NotificationRecord notification, boolean valid,
boolean starred, boolean cached) {
AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
for (AggregatedStats stats : aggregatedStatsArray) {
if (valid) {
stats.numWithValidPeople++;
}
if (starred) {
stats.numWithStaredPeople++;
}
if (cached) {
stats.numPeopleCacheHit++;
} else {
stats.numPeopleCacheMiss++;
}
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
}
/**
* Called when the notification is canceled due to unknown reasons.
*
* <p>Called for notifications of apps being uninstalled, for example.</p>
*/
public synchronized void registerCancelUnknown(NotificationRecord notification) {
notification.stats.onCancel();
for (AggregatedStats stats : getAggregatedStatsLocked(notification)) {
stats.collect(notification.stats);
public synchronized void registerBlocked(NotificationRecord notification) {
AggregatedStats[] aggregatedStatsArray = getAggregatedStatsLocked(notification);
for (AggregatedStats stats : aggregatedStatsArray) {
stats.numBlocked++;
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
}
// Locked by this.
@@ -163,24 +189,28 @@ public class NotificationUsageStats {
return EMPTY_AGGREGATED_STATS;
}
StatusBarNotification n = record.sbn;
// TODO: expand to package-level counts in the future.
AggregatedStats[] array = mStatsArrays.poll();
if (array == null) {
array = new AggregatedStats[1];
}
array[0] = getOrCreateAggregatedStatsLocked(DEVICE_GLOBAL_STATS);
return array;
}
String user = String.valueOf(n.getUserId());
String userPackage = user + ":" + n.getPackageName();
// TODO: Use pool of arrays.
return new AggregatedStats[] {
getOrCreateAggregatedStatsLocked(user),
getOrCreateAggregatedStatsLocked(userPackage),
getOrCreateAggregatedStatsLocked(n.getKey()),
};
// Locked by this.
private void releaseAggregatedStatsLocked(AggregatedStats[] array) {
for(int i = 0; i < array.length; i++) {
array[i] = null;
}
mStatsArrays.offer(array);
}
// Locked by this.
private AggregatedStats getOrCreateAggregatedStatsLocked(String key) {
AggregatedStats result = mStats.get(key);
if (result == null) {
result = new AggregatedStats(key);
result = new AggregatedStats(mContext, key);
mStats.put(key, result);
}
return result;
@@ -193,64 +223,74 @@ public class NotificationUsageStats {
continue;
as.dump(pw, indent);
}
pw.println(indent + "mStatsArrays.size(): " + mStatsArrays.size());
}
if (ENABLE_SQLITE_LOG) {
mSQLiteLog.dump(pw, indent, filter);
}
}
public synchronized void emit() {
// TODO: expand to package-level counts in the future.
AggregatedStats stats = getOrCreateAggregatedStatsLocked(DEVICE_GLOBAL_STATS);
stats.emit();
mLastEmitTime = SystemClock.elapsedRealtime();
mHandler.removeMessages(MSG_EMIT);
mHandler.sendEmptyMessageDelayed(MSG_EMIT, EMIT_PERIOD);
}
/**
* Aggregated notification stats.
*/
private static class AggregatedStats {
private final Context mContext;
public final String key;
// ---- Updated as the respective events occur.
public int numPostedByApp;
public int numUpdatedByApp;
public int numRemovedByApp;
public int numClickedByUser;
public int numDismissedByUser;
public int numPeopleCacheHit;
public int numPeopleCacheMiss;;
public int numWithStaredPeople;
public int numWithValidPeople;
public int numBlocked;
// ---- Updated when a notification is canceled.
public final Aggregate posttimeMs = new Aggregate();
public final Aggregate posttimeToDismissMs = new Aggregate();
public final Aggregate posttimeToFirstClickMs = new Aggregate();
public final Aggregate airtimeCount = new Aggregate();
public final Aggregate airtimeMs = new Aggregate();
public final Aggregate posttimeToFirstAirtimeMs = new Aggregate();
public final Aggregate userExpansionCount = new Aggregate();
public final Aggregate airtimeExpandedMs = new Aggregate();
public final Aggregate posttimeToFirstVisibleExpansionMs = new Aggregate();
private AggregatedStats mPrevious;
public AggregatedStats(String key) {
public AggregatedStats(Context context, String key) {
this.key = key;
mContext = context;
}
public void collect(SingleNotificationStats singleNotificationStats) {
posttimeMs.addSample(
SystemClock.elapsedRealtime() - singleNotificationStats.posttimeElapsedMs);
if (singleNotificationStats.posttimeToDismissMs >= 0) {
posttimeToDismissMs.addSample(singleNotificationStats.posttimeToDismissMs);
public void emit() {
if (mPrevious == null) {
mPrevious = new AggregatedStats(null, key);
}
if (singleNotificationStats.posttimeToFirstClickMs >= 0) {
posttimeToFirstClickMs.addSample(singleNotificationStats.posttimeToFirstClickMs);
}
airtimeCount.addSample(singleNotificationStats.airtimeCount);
if (singleNotificationStats.airtimeMs >= 0) {
airtimeMs.addSample(singleNotificationStats.airtimeMs);
}
if (singleNotificationStats.posttimeToFirstAirtimeMs >= 0) {
posttimeToFirstAirtimeMs.addSample(
singleNotificationStats.posttimeToFirstAirtimeMs);
}
if (singleNotificationStats.posttimeToFirstVisibleExpansionMs >= 0) {
posttimeToFirstVisibleExpansionMs.addSample(
singleNotificationStats.posttimeToFirstVisibleExpansionMs);
}
userExpansionCount.addSample(singleNotificationStats.userExpansionCount);
if (singleNotificationStats.airtimeExpandedMs >= 0) {
airtimeExpandedMs.addSample(singleNotificationStats.airtimeExpandedMs);
maybeCount("note_post", (numPostedByApp - mPrevious.numPostedByApp));
maybeCount("note_update", (numUpdatedByApp - mPrevious.numUpdatedByApp));
maybeCount("note_remove", (numRemovedByApp - mPrevious.numRemovedByApp));
maybeCount("note_with_people", (numWithValidPeople - mPrevious.numWithValidPeople));
maybeCount("note_with_stars", (numWithStaredPeople - mPrevious.numWithStaredPeople));
maybeCount("people_cache_hit", (numPeopleCacheHit - mPrevious.numPeopleCacheHit));
maybeCount("people_cache_miss", (numPeopleCacheMiss - mPrevious.numPeopleCacheMiss));
maybeCount("note_blocked", (numBlocked - mPrevious.numBlocked));
mPrevious.numPostedByApp = numPostedByApp;
mPrevious.numUpdatedByApp = numUpdatedByApp;
mPrevious.numRemovedByApp = numRemovedByApp;
mPrevious.numPeopleCacheHit = numPeopleCacheHit;
mPrevious.numPeopleCacheMiss = numPeopleCacheMiss;
mPrevious.numWithStaredPeople = numWithStaredPeople;
mPrevious.numWithValidPeople = numWithValidPeople;
mPrevious.numBlocked = numBlocked;
}
void maybeCount(String name, int value) {
if (value > 0) {
MetricsLogger.count(mContext, name, value);
}
}
@@ -269,17 +309,11 @@ public class NotificationUsageStats {
indent + " numPostedByApp=" + numPostedByApp + ",\n" +
indent + " numUpdatedByApp=" + numUpdatedByApp + ",\n" +
indent + " numRemovedByApp=" + numRemovedByApp + ",\n" +
indent + " numClickedByUser=" + numClickedByUser + ",\n" +
indent + " numDismissedByUser=" + numDismissedByUser + ",\n" +
indent + " posttimeMs=" + posttimeMs + ",\n" +
indent + " posttimeToDismissMs=" + posttimeToDismissMs + ",\n" +
indent + " posttimeToFirstClickMs=" + posttimeToFirstClickMs + ",\n" +
indent + " airtimeCount=" + airtimeCount + ",\n" +
indent + " airtimeMs=" + airtimeMs + ",\n" +
indent + " posttimeToFirstAirtimeMs=" + posttimeToFirstAirtimeMs + ",\n" +
indent + " userExpansionCount=" + userExpansionCount + ",\n" +
indent + " airtimeExpandedMs=" + airtimeExpandedMs + ",\n" +
indent + " posttimeToFVEMs=" + posttimeToFirstVisibleExpansionMs + ",\n" +
indent + " numPeopleCacheHit=" + numPeopleCacheHit + ",\n" +
indent + " numWithStaredPeople=" + numWithStaredPeople + ",\n" +
indent + " numWithValidPeople=" + numWithValidPeople + ",\n" +
indent + " numPeopleCacheMiss=" + numPeopleCacheMiss + ",\n" +
indent + " numBlocked=" + numBlocked + ",\n" +
indent + "}";
}
}

View File

@@ -24,7 +24,7 @@ public class PackagePriorityExtractor implements NotificationSignalExtractor {
private RankingConfig mConfig;
public void initialize(Context ctx) {
public void initialize(Context ctx, NotificationUsageStats usageStats) {
if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
}

View File

@@ -24,7 +24,7 @@ public class PackageVisibilityExtractor implements NotificationSignalExtractor {
private RankingConfig mConfig;
public void initialize(Context ctx) {
public void initialize(Context ctx, NotificationUsageStats usageStats) {
if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
}

View File

@@ -68,7 +68,8 @@ public class RankingHelper implements RankingConfig {
private final Context mContext;
private final Handler mRankingHandler;
public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
public RankingHelper(Context context, Handler rankingHandler, NotificationUsageStats usageStats,
String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
@@ -79,7 +80,7 @@ public class RankingHelper implements RankingConfig {
Class<?> extractorClass = mContext.getClassLoader().loadClass(extractorNames[i]);
NotificationSignalExtractor extractor =
(NotificationSignalExtractor) extractorClass.newInstance();
extractor.initialize(mContext);
extractor.initialize(mContext, usageStats);
extractor.setConfig(this);
mSignalExtractors[i] = extractor;
} catch (ClassNotFoundException e) {

View File

@@ -34,7 +34,6 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
import com.android.internal.logging.MetricsLogger;
import java.util.ArrayList;
import java.util.LinkedList;
@@ -85,11 +84,13 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
private Handler mHandler;
private ContentObserver mObserver;
private int mEvictionCount;
private NotificationUsageStats mUsageStats;
public void initialize(Context context) {
public void initialize(Context context, NotificationUsageStats usageStats) {
if (DEBUG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + ".");
mUserToContextMap = new ArrayMap<>();
mBaseContext = context;
mUsageStats = usageStats;
mPeopleCache = new LruCache<String, LookupResult>(PEOPLE_CACHE_SIZE);
mEnabled = ENABLE_PEOPLE_VALIDATOR && 1 == Settings.Global.getInt(
mBaseContext.getContentResolver(), SETTING_ENABLE_PEOPLE_VALIDATOR, 1);
@@ -203,8 +204,15 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
final String key = record.getKey();
final Bundle extras = record.getNotification().extras;
final float[] affinityOut = new float[1];
final RankingReconsideration rr = validatePeople(context, key, extras, affinityOut);
record.setContactAffinity(affinityOut[0]);
final PeopleRankingReconsideration rr = validatePeople(context, key, extras, affinityOut);
final float affinity = affinityOut[0];
record.setContactAffinity(affinity);
if (rr == null) {
mUsageStats.registerPeopleAffinity(record, affinity > NONE, affinity == STARRED_CONTACT,
true /* cached */);
} else {
rr.setRecord(record);
}
return rr;
}
@@ -245,7 +253,6 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
if (pendingLookups.isEmpty()) {
if (VERBOSE) Slog.i(TAG, "final affinity: " + affinity);
if (affinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
return null;
}
@@ -413,6 +420,7 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
private final Context mContext;
private float mContactAffinity = NONE;
private NotificationRecord mRecord;
private PeopleRankingReconsideration(Context context, String key, LinkedList<String> pendingLookups) {
super(key);
@@ -456,7 +464,10 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
"ms");
}
if (mContactAffinity != NONE) MetricsLogger.count(mBaseContext, "note_with_people", 1);
if (mRecord != null) {
mUsageStats.registerPeopleAffinity(mRecord, mContactAffinity > NONE,
mContactAffinity == STARRED_CONTACT, false /* cached */);
}
}
@Override
@@ -469,6 +480,10 @@ public class ValidateNotificationPeople implements NotificationSignalExtractor {
public float getContactAffinity() {
return mContactAffinity;
}
public void setRecord(NotificationRecord record) {
mRecord = record;
}
}
}

View File

@@ -15,6 +15,9 @@
*/
package com.android.server.notification;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import android.app.Notification;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -24,6 +27,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import java.util.ArrayList;
public class RankingHelperTest extends AndroidTestCase {
@Mock NotificationUsageStats mUsageStats;
private Notification mNotiGroupGSortA;
private Notification mNotiGroupGSortB;
@@ -39,9 +43,10 @@ public class RankingHelperTest extends AndroidTestCase {
@Override
public void setUp() {
MockitoAnnotations.initMocks(this);
UserHandle user = UserHandle.ALL;
mHelper = new RankingHelper(getContext(), null, new String[0]);
mHelper = new RankingHelper(getContext(), null, mUsageStats, new String[0]);
mNotiGroupGSortA = new Notification.Builder(getContext())
.setContentTitle("A")