Merge "track and report packages with undecorated remoteviews" into qt-qpr1-dev

This commit is contained in:
TreeHugger Robot
2019-12-10 18:36:43 +00:00
committed by Android (Google) Code Review
10 changed files with 451 additions and 12 deletions

View File

@@ -325,7 +325,7 @@ message Atom {
}
// Pulled events will start at field 10000.
// Next: 10062
// Next: 10067
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -390,6 +390,7 @@ message Atom {
AppOps app_ops = 10060;
ProcessSystemIonHeapSize process_system_ion_heap_size = 10061;
VmsClientStats vms_client_stats = 10065;
NotificationRemoteViews notification_remote_views = 10066;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -4751,6 +4752,24 @@ message ProcStatsPkgProc {
optional ProcessStatsSectionProto proc_stats_section = 1;
}
// Next Tag: 2
message PackageRemoteViewInfoProto {
optional string package_name = 1;
// add per-package additional info here (like channels)
}
// Next Tag: 2
message NotificationRemoteViewsProto {
repeated PackageRemoteViewInfoProto package_remote_view_info = 1;
}
/**
* Pulled from NotificationManagerService.java
*/
message NotificationRemoteViews {
optional NotificationRemoteViewsProto notification_remote_views = 1;
}
message PowerProfileProto {
optional double cpu_suspend = 1;

View File

@@ -271,6 +271,9 @@ std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
{android::util::VMS_CLIENT_STATS,
{.additiveFields = {5, 6, 7, 8, 9, 10},
.puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
// NotiifcationRemoteViews.
{android::util::NOTIFICATION_REMOTE_VIEWS,
{.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
};
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {

View File

@@ -202,4 +202,6 @@ interface INotificationManager
void setPrivateNotificationsAllowed(boolean allow);
boolean getPrivateNotificationsAllowed();
long pullStats(long startNs, int report, boolean doAgg, out List<ParcelFileDescriptor> stats);
}

View File

@@ -264,3 +264,14 @@ message ZenPolicyProto {
optional Sender priority_calls = 16;
optional Sender priority_messages = 17;
}
// Next Tag: 2
message PackageRemoteViewInfoProto {
optional string package_name = 1;
// add per-package additional info here (like channels)
}
// Next Tag: 2
message NotificationRemoteViewsProto {
repeated PackageRemoteViewInfoProto package_remote_view_info = 1;
}

View File

@@ -163,6 +163,7 @@ import android.os.IDeviceIdleController;
import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -281,6 +282,9 @@ public class NotificationManagerService extends SystemService {
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
// pullStats report request: undecorated remote view stats
public static final int REPORT_REMOTE_VIEWS = 0x01;
static final boolean DEBUG_INTERRUPTIVENESS = SystemProperties.getBoolean(
"debug.notification.interruptiveness", false);
@@ -3734,6 +3738,8 @@ public class NotificationManagerService extends SystemService {
try {
if (filter.stats) {
dumpJson(pw, filter);
} else if (filter.rvStats) {
dumpRemoteViewStats(pw, filter);
} else if (filter.proto) {
dumpProto(fd, filter);
} else if (filter.criticalPriority) {
@@ -4210,6 +4216,49 @@ public class NotificationManagerService extends SystemService {
new NotificationShellCmd(NotificationManagerService.this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
/**
* Get stats committed after startNs
*
* @param startNs Report stats committed after this time in nanoseconds.
* @param report Indicatess which section to include in the stats.
* @param doAgg Whether to aggregate the stats or keep them separated.
* @param out List of protos of individual commits or one representing the
* aggregate.
* @return the report time in nanoseconds, or 0 on error.
*/
@Override
public long pullStats(long startNs, int report, boolean doAgg,
List<ParcelFileDescriptor> out) {
checkCallerIsSystemOrShell();
long startMs = TimeUnit.MILLISECONDS.convert(startNs, TimeUnit.NANOSECONDS);
final long identity = Binder.clearCallingIdentity();
try {
switch (report) {
case REPORT_REMOTE_VIEWS:
Slog.e(TAG, "pullStats REPORT_REMOTE_VIEWS from: "
+ startMs + " wtih " + doAgg);
PulledStats stats = mUsageStats.remoteViewStats(startMs, doAgg);
if (stats != null) {
out.add(stats.toParcelFileDescriptor(report));
Slog.e(TAG, "exiting pullStats with: " + out.size());
long endNs = TimeUnit.NANOSECONDS
.convert(stats.endTimeMs(), TimeUnit.MILLISECONDS);
return endNs;
}
Slog.e(TAG, "null stats for: " + report);
}
} catch (IOException e) {
Slog.e(TAG, "exiting pullStats: on error", e);
return 0;
} finally {
Binder.restoreCallingIdentity(identity);
}
Slog.e(TAG, "exiting pullStats: bad request");
return 0;
}
};
@VisibleForTesting
@@ -4425,6 +4474,15 @@ public class NotificationManagerService extends SystemService {
pw.println(dump);
}
private void dumpRemoteViewStats(PrintWriter pw, @NonNull DumpFilter filter) {
PulledStats stats = mUsageStats.remoteViewStats(filter.since, true);
if (stats == null) {
pw.println("no remote view stats reported.");
return;
}
stats.dump(REPORT_REMOTE_VIEWS, pw, filter);
}
private void dumpProto(FileDescriptor fd, @NonNull DumpFilter filter) {
final ProtoOutputStream proto = new ProtoOutputStream(fd);
synchronized (mNotificationLock) {
@@ -8559,6 +8617,7 @@ public class NotificationManagerService extends SystemService {
public boolean zen;
public long since;
public boolean stats;
public boolean rvStats;
public boolean redact = true;
public boolean proto = false;
public boolean criticalPriority = false;
@@ -8594,6 +8653,14 @@ public class NotificationManagerService extends SystemService {
} else {
filter.since = 0;
}
} else if ("--remote-view-stats".equals(a)) {
filter.rvStats = true;
if (ai < args.length-1) {
ai++;
filter.since = Long.parseLong(args[ai]);
} else {
filter.since = 0;
}
} else if (PRIORITY_ARG.equals(a)) {
// Bugreport will call the service twice with priority arguments, first to dump
// critical sections and then non critical ones. Set approriate filters

View File

@@ -149,6 +149,7 @@ public class NotificationUsageStats {
stats.numPostedByApp++;
stats.updateInterarrivalEstimate(now);
stats.countApiUse(notification);
stats.numUndecoratedRemoteViews += (isUndecoratedRemoteView(notification) ? 1 : 0);
}
releaseAggregatedStatsLocked(aggregatedStatsArray);
if (ENABLE_SQLITE_LOG) {
@@ -156,6 +157,13 @@ public class NotificationUsageStats {
}
}
/**
* Does this notification use RemoveViews without a platform decoration?
*/
protected static boolean isUndecoratedRemoteView(NotificationRecord notification) {
return (notification.getNotification().getNotificationStyle() == null);
}
/**
* Called when a notification has been updated.
*/
@@ -326,6 +334,15 @@ public class NotificationUsageStats {
return dump;
}
public PulledStats remoteViewStats(long startMs, boolean aggregate) {
if (ENABLE_SQLITE_LOG) {
if (aggregate) {
return mSQLiteLog.remoteViewAggStats(startMs);
}
}
return null;
}
public synchronized void dump(PrintWriter pw, String indent, DumpFilter filter) {
if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
for (AggregatedStats as : mStats.values()) {
@@ -403,6 +420,7 @@ public class NotificationUsageStats {
public int numRateViolations;
public int numAlertViolations;
public int numQuotaViolations;
public int numUndecoratedRemoteViews;
public long mLastAccessTime;
public AggregatedStats(Context context, String key) {
@@ -669,6 +687,8 @@ public class NotificationUsageStats {
output.append(indentPlusTwo).append(noisyImportance.toString()).append("\n");
output.append(indentPlusTwo).append(quietImportance.toString()).append("\n");
output.append(indentPlusTwo).append(finalImportance.toString()).append("\n");
output.append(indentPlusTwo);
output.append("numUndecorateRVs=").append(numUndecoratedRemoteViews).append("\n");
output.append(indent).append("}");
return output.toString();
}
@@ -1027,7 +1047,7 @@ public class NotificationUsageStats {
private static final int MSG_DISMISS = 4;
private static final String DB_NAME = "notification_log.db";
private static final int DB_VERSION = 5;
private static final int DB_VERSION = 7;
/** Age in ms after which events are pruned from the DB. */
private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L; // 1 week
@@ -1060,6 +1080,7 @@ public class NotificationUsageStats {
private static final String COL_FIRST_EXPANSIONTIME_MS = "first_expansion_time_ms";
private static final String COL_AIRTIME_EXPANDED_MS = "expansion_airtime_ms";
private static final String COL_EXPAND_COUNT = "expansion_count";
private static final String COL_UNDECORATED = "undecorated";
private static final int EVENT_TYPE_POST = 1;
@@ -1085,12 +1106,20 @@ public class NotificationUsageStats {
"COUNT(*) AS cnt, " +
"SUM(" + COL_MUTED + ") as muted, " +
"SUM(" + COL_NOISY + ") as noisy, " +
"SUM(" + COL_DEMOTED + ") as demoted " +
"SUM(" + COL_DEMOTED + ") as demoted, " +
"SUM(" + COL_UNDECORATED + ") as undecorated " +
"FROM " + TAB_LOG + " " +
"WHERE " +
COL_EVENT_TYPE + "=" + EVENT_TYPE_POST +
" AND " + COL_EVENT_TIME + " > %d " +
" GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
private static final String UNDECORATED_QUERY = "SELECT " +
COL_PKG + ", " +
"MAX(" + COL_EVENT_TIME + ") as max_time " +
"FROM " + TAB_LOG + " " +
"WHERE " + COL_UNDECORATED + "> 0 " +
" AND " + COL_EVENT_TIME + " > %d " +
"GROUP BY " + COL_PKG;
public SQLiteLog(Context context) {
HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log",
@@ -1146,7 +1175,8 @@ public class NotificationUsageStats {
COL_AIRTIME_MS + " INT," +
COL_FIRST_EXPANSIONTIME_MS + " INT," +
COL_AIRTIME_EXPANDED_MS + " INT," +
COL_EXPAND_COUNT + " INT" +
COL_EXPAND_COUNT + " INT," +
COL_UNDECORATED + " INT" +
")");
}
@@ -1256,6 +1286,7 @@ public class NotificationUsageStats {
} else {
putPosttimeVisibility(r, cv);
}
cv.put(COL_UNDECORATED, (isUndecoratedRemoteView(r) ? 1 : 0));
SQLiteDatabase db = mHelper.getWritableDatabase();
if (db.insert(TAB_LOG, null, cv) < 0) {
Log.wtf(TAG, "Error while trying to insert values: " + cv);
@@ -1332,5 +1363,22 @@ public class NotificationUsageStats {
}
return dump;
}
public PulledStats remoteViewAggStats(long startMs) {
PulledStats stats = new PulledStats(startMs);
SQLiteDatabase db = mHelper.getReadableDatabase();
String q = String.format(UNDECORATED_QUERY, startMs);
Cursor cursor = db.rawQuery(q, null);
try {
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
String pkg = cursor.getString(0);
long maxTimeMs = cursor.getLong(1);
stats.addUndecoratedPackage(pkg, maxTimeMs);
}
} finally {
cursor.close();
}
return stats;
}
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2019 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.notification;
import static com.android.server.notification.NotificationManagerService.REPORT_REMOTE_VIEWS;
import android.os.ParcelFileDescriptor;
import android.service.notification.NotificationRemoteViewsProto;
import android.service.notification.PackageRemoteViewInfoProto;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
public class PulledStats {
static final String TAG = "PulledStats";
private final long mTimePeriodStartMs;
private long mTimePeriodEndMs;
private List<String> mUndecoratedPackageNames;
public PulledStats(long startMs) {
mTimePeriodEndMs = mTimePeriodStartMs = startMs;
mUndecoratedPackageNames = new ArrayList<>();
}
ParcelFileDescriptor toParcelFileDescriptor(int report)
throws IOException {
final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
switch(report) {
case REPORT_REMOTE_VIEWS:
Thread thr = new Thread("NotificationManager pulled metric output") {
public void run() {
try {
FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(
fds[1]);
final ProtoOutputStream proto = new ProtoOutputStream(fout);
writeToProto(report, proto);
proto.flush();
fout.close();
} catch (IOException e) {
Slog.w(TAG, "Failure writing pipe", e);
}
}
};
thr.start();
break;
default:
Slog.w(TAG, "Unknown pulled stats request: " + report);
break;
}
return fds[0];
}
/*
* @return the most recent timestamp in the report, as nanoseconds.
*/
public long endTimeMs() {
return mTimePeriodEndMs;
}
public void dump(int report, PrintWriter pw, NotificationManagerService.DumpFilter filter) {
switch(report) {
case REPORT_REMOTE_VIEWS:
pw.print(" Packages with undecordated notifications (");
pw.print(mTimePeriodStartMs);
pw.print(" - ");
pw.print(mTimePeriodEndMs);
pw.println("):");
if (mUndecoratedPackageNames.size() == 0) {
pw.println(" none");
} else {
for (String pkg : mUndecoratedPackageNames) {
if (!filter.filtered || pkg.equals(filter.pkgFilter)) {
pw.println(" " + pkg);
}
}
}
break;
default:
pw.println("Unknown pulled stats request: " + report);
break;
}
}
@VisibleForTesting
void writeToProto(int report, ProtoOutputStream proto) {
switch(report) {
case REPORT_REMOTE_VIEWS:
for (String pkg: mUndecoratedPackageNames) {
long token = proto.start(NotificationRemoteViewsProto.PACKAGE_REMOTE_VIEW_INFO);
proto.write(PackageRemoteViewInfoProto.PACKAGE_NAME, pkg);
proto.end(token);
}
break;
default:
Slog.w(TAG, "Unknown pulled stats request: " + report);
break;
}
}
public void addUndecoratedPackage(String packageName, long timestampMs) {
mUndecoratedPackageNames.add(packageName);
mTimePeriodEndMs = Math.max(mTimePeriodEndMs, timestampMs);
}
}

View File

@@ -41,6 +41,7 @@ import android.app.AppOpsManager.HistoricalOps;
import android.app.AppOpsManager.HistoricalOpsRequest;
import android.app.AppOpsManager.HistoricalPackageOps;
import android.app.AppOpsManager.HistoricalUidOps;
import android.app.INotificationManager;
import android.app.ProcessMemoryState;
import android.app.StatsManager;
import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -140,6 +141,7 @@ import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.am.MemoryStatUtil.IonAllocations;
import com.android.server.am.MemoryStatUtil.MemoryStat;
import com.android.server.notification.NotificationManagerService;
import com.android.server.role.RoleManagerInternal;
import com.android.server.storage.DiskStatsFileLogger;
import com.android.server.storage.DiskStatsLoggingService;
@@ -1625,14 +1627,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
if (statsFiles.size() != 1) {
return;
}
InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(
statsFiles.get(0));
int[] len = new int[1];
byte[] stats = readFully(stream, len);
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
wallClockNanos);
e.writeStorage(Arrays.copyOf(stats, len[0]));
pulledData.add(e);
unpackStreamedData(tagId, elapsedNanos, wallClockNanos, pulledData, statsFiles);
new File(mBaseDir.getAbsolutePath() + "/" + section + "_"
+ lastHighWaterMark).delete();
new File(
@@ -1648,6 +1643,52 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
private INotificationManager mNotificationManager =
INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
private void pullNotificationStats(int reportId, int tagId, long elapsedNanos,
long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
final long callingToken = Binder.clearCallingIdentity();
try {
// determine last pull tine. Copy file trick from pullProcessStats?
long lastNotificationStatsNs = wallClockNanos -
TimeUnit.NANOSECONDS.convert(1, TimeUnit.DAYS);
List<ParcelFileDescriptor> statsFiles = new ArrayList<>();
long notificationStatsNs = mNotificationManager.pullStats(
lastNotificationStatsNs, reportId, true, statsFiles);
if (statsFiles.size() != 1) {
return;
}
unpackStreamedData(tagId, elapsedNanos, wallClockNanos, pulledData, statsFiles);
} catch (IOException e) {
Log.e(TAG, "Getting notistats failed: ", e);
} catch (RemoteException e) {
Log.e(TAG, "Getting notistats failed: ", e);
} catch (SecurityException e) {
Log.e(TAG, "Getting notistats failed: ", e);
} finally {
Binder.restoreCallingIdentity(callingToken);
}
}
static void unpackStreamedData(int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData, List<ParcelFileDescriptor> statsFiles)
throws IOException {
InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(
statsFiles.get(0));
int[] len = new int[1];
byte[] stats = readFully(stream, len);
StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
wallClockNanos);
e.writeStorage(Arrays.copyOf(stats, len[0]));
pulledData.add(e);
}
static byte[] readFully(InputStream stream, int[] outLen) throws IOException {
int pos = 0;
final int initialAvail = stream.available();
@@ -2477,6 +2518,11 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
pullAppOps(elapsedNanos, wallClockNanos, ret);
break;
}
case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
tagId, elapsedNanos, wallClockNanos, ret);
break;
}
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;

View File

@@ -20,6 +20,7 @@ android_test {
"androidx.test.rules", "hamcrest-library",
"mockito-target-inline-minus-junit4",
"platform-test-annotations",
"platformprotosnano",
"hamcrest-library",
"testables",
"truth-prebuilt",

View File

@@ -0,0 +1,113 @@
/*
* Copyright (C) 2019 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.notification;
import static com.android.server.notification.NotificationManagerService.REPORT_REMOTE_VIEWS;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.service.notification.nano.NotificationRemoteViewsProto;
import android.test.MoreAsserts;
import android.util.proto.ProtoOutputStream;
import androidx.test.filters.SmallTest;
import com.android.server.UiServiceTestCase;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
@SmallTest
public class PulledStatsTest extends UiServiceTestCase {
@Test
public void testPulledStats_Empty() {
PulledStats stats = new PulledStats(0L);
assertEquals(0L, stats.endTimeMs());
}
@Test
public void testPulledStats_UnknownReport() {
PulledStats stats = new PulledStats(0L);
stats.addUndecoratedPackage("foo", 456);
stats.addUndecoratedPackage("bar", 123);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
final ProtoOutputStream proto = new ProtoOutputStream(bytes);
stats.writeToProto(1023123, proto); // a very large number
proto.flush();
// expect empty output in response to an unrecognized request
assertEquals(0L, bytes.size());
}
@Test
public void testPulledStats_RemoteViewReportPackages() {
List<String> expectedPkgs = new ArrayList<>(2);
expectedPkgs.add("foo");
expectedPkgs.add("bar");
PulledStats stats = new PulledStats(0L);
for(String pkg: expectedPkgs) {
stats.addUndecoratedPackage(pkg, 111);
}
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
final ProtoOutputStream protoStream = new ProtoOutputStream(bytes);
stats.writeToProto(REPORT_REMOTE_VIEWS, protoStream);
protoStream.flush();
try {
NotificationRemoteViewsProto proto =
NotificationRemoteViewsProto.parseFrom(bytes.toByteArray());
List<String> actualPkgs = new ArrayList<>(2);
for(int i = 0 ; i < proto.packageRemoteViewInfo.length; i++) {
actualPkgs.add(proto.packageRemoteViewInfo[i].packageName);
}
assertEquals(2, actualPkgs.size());
assertTrue("missing packages", actualPkgs.containsAll(expectedPkgs));
assertTrue("unexpected packages", expectedPkgs.containsAll(actualPkgs));
} catch (InvalidProtocolBufferNanoException e) {
e.printStackTrace();
fail("writeToProto generated unparsable output");
}
}
@Test
public void testPulledStats_RemoteViewReportEndTime() {
List<String> expectedPkgs = new ArrayList<>(2);
expectedPkgs.add("foo");
expectedPkgs.add("bar");
PulledStats stats = new PulledStats(0L);
long t = 111;
for(String pkg: expectedPkgs) {
t += 1000;
stats.addUndecoratedPackage(pkg, t);
}
assertEquals(t, stats.endTimeMs());
}
}