Merge "Merge "Client logging changes for ProcessState and ProcessAssociation atoms" into tm-qpr-dev" into tm-qpr-dev am: b18ea46e27

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21733647

Change-Id: I9d3c334b83ca673191d8a760cc9e75cf0bdccb1a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Yu-Ting Tseng
2023-03-08 16:42:22 +00:00
committed by Automerger Merge Worker
5 changed files with 528 additions and 36 deletions

View File

@@ -73,6 +73,7 @@ import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
import java.io.PrintWriter;
import java.util.Comparator;
import java.util.concurrent.TimeUnit;
public final class ProcessState {
private static final String TAG = "ProcessStats";
@@ -1542,6 +1543,75 @@ public final class ProcessState {
proto.write(fieldId, procName);
}
/** Dumps the duration of each state to statsEventOutput. */
public void dumpStateDurationToStatsd(
int atomTag, ProcessStats processStats, StatsEventOutput statsEventOutput) {
long topMs = 0;
long fgsMs = 0;
long boundTopMs = 0;
long boundFgsMs = 0;
long importantForegroundMs = 0;
long cachedMs = 0;
long frozenMs = 0;
long otherMs = 0;
for (int i = 0, size = mDurations.getKeyCount(); i < size; i++) {
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
int procStateIndex = type % STATE_COUNT;
long duration = mDurations.getValue(key);
switch (procStateIndex) {
case STATE_TOP:
topMs += duration;
break;
case STATE_BOUND_TOP_OR_FGS:
boundTopMs += duration;
break;
case STATE_FGS:
fgsMs += duration;
break;
case STATE_IMPORTANT_FOREGROUND:
case STATE_IMPORTANT_BACKGROUND:
importantForegroundMs += duration;
break;
case STATE_BACKUP:
case STATE_SERVICE:
case STATE_SERVICE_RESTARTING:
case STATE_RECEIVER:
case STATE_HEAVY_WEIGHT:
case STATE_HOME:
case STATE_LAST_ACTIVITY:
case STATE_PERSISTENT:
otherMs += duration;
break;
case STATE_CACHED_ACTIVITY:
case STATE_CACHED_ACTIVITY_CLIENT:
case STATE_CACHED_EMPTY:
cachedMs += duration;
break;
// TODO (b/261910877) Add support for tracking boundFgsMs and
// frozenMs.
}
}
statsEventOutput.write(
atomTag,
getUid(),
getName(),
(int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodStartUptime),
(int) TimeUnit.MILLISECONDS.toSeconds(processStats.mTimePeriodEndUptime),
(int)
TimeUnit.MILLISECONDS.toSeconds(
processStats.mTimePeriodEndUptime
- processStats.mTimePeriodStartUptime),
(int) TimeUnit.MILLISECONDS.toSeconds(topMs),
(int) TimeUnit.MILLISECONDS.toSeconds(fgsMs),
(int) TimeUnit.MILLISECONDS.toSeconds(boundTopMs),
(int) TimeUnit.MILLISECONDS.toSeconds(boundFgsMs),
(int) TimeUnit.MILLISECONDS.toSeconds(importantForegroundMs),
(int) TimeUnit.MILLISECONDS.toSeconds(cachedMs),
(int) TimeUnit.MILLISECONDS.toSeconds(frozenMs),
(int) TimeUnit.MILLISECONDS.toSeconds(otherMs));
}
/** Similar to {@code #dumpDebug}, but with a reduced/aggregated subset of states. */
public void dumpAggregatedProtoForStatsd(ProtoOutputStream proto, long fieldId,
String procName, int uid, long now,

View File

@@ -43,6 +43,7 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.procstats.AssociationState.SourceKey;
import com.android.internal.app.procstats.AssociationState.SourceState;
import com.android.internal.util.function.QuintConsumer;
import dalvik.system.VMRuntime;
@@ -56,6 +57,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -2389,6 +2392,79 @@ public final class ProcessStats implements Parcelable {
}
}
void forEachProcess(Consumer<ProcessState> consumer) {
final ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
for (int ip = 0, size = procMap.size(); ip < size; ip++) {
final SparseArray<ProcessState> uids = procMap.valueAt(ip);
for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
final ProcessState processState = uids.valueAt(iu);
consumer.accept(processState);
}
}
}
void forEachAssociation(
QuintConsumer<AssociationState, Integer, String, SourceKey, SourceState> consumer) {
final ArrayMap<String, SparseArray<LongSparseArray<PackageState>>> pkgMap =
mPackages.getMap();
for (int ip = 0, size = pkgMap.size(); ip < size; ip++) {
final SparseArray<LongSparseArray<PackageState>> uids = pkgMap.valueAt(ip);
for (int iu = 0, uidsSize = uids.size(); iu < uidsSize; iu++) {
final int uid = uids.keyAt(iu);
final LongSparseArray<PackageState> versions = uids.valueAt(iu);
for (int iv = 0, versionsSize = versions.size(); iv < versionsSize; iv++) {
final PackageState state = versions.valueAt(iv);
for (int iasc = 0, ascSize = state.mAssociations.size();
iasc < ascSize;
iasc++) {
final String serviceName = state.mAssociations.keyAt(iasc);
final AssociationState asc = state.mAssociations.valueAt(iasc);
for (int is = 0, sourcesSize = asc.mSources.size();
is < sourcesSize;
is++) {
final SourceState src = asc.mSources.valueAt(is);
final SourceKey key = asc.mSources.keyAt(is);
consumer.accept(asc, uid, serviceName, key, src);
}
}
}
}
}
}
/** Dumps the stats of all processes to statsEventOutput. */
public void dumpProcessState(int atomTag, StatsEventOutput statsEventOutput) {
forEachProcess(
(processState) -> {
if (processState.isMultiPackage()
&& processState.getCommonProcess() != processState) {
return;
}
processState.dumpStateDurationToStatsd(atomTag, this, statsEventOutput);
});
}
/** Dumps all process association data to statsEventOutput. */
public void dumpProcessAssociation(int atomTag, StatsEventOutput statsEventOutput) {
forEachAssociation(
(asc, serviceUid, serviceName, key, src) -> {
statsEventOutput.write(
atomTag,
key.mUid,
key.mProcess,
serviceUid,
serviceName,
(int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodStartUptime),
(int) TimeUnit.MILLISECONDS.toSeconds(mTimePeriodEndUptime),
(int)
TimeUnit.MILLISECONDS.toSeconds(
mTimePeriodEndUptime - mTimePeriodStartUptime),
(int) TimeUnit.MILLISECONDS.toSeconds(src.mDuration),
src.mActiveCount,
asc.getProcessName());
});
}
private void dumpProtoPreamble(ProtoOutputStream proto) {
proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
proto.write(ProcessStatsSectionProto.END_REALTIME_MS,

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2023 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.internal.app.procstats;
import android.util.StatsEvent;
import com.android.internal.util.FrameworkStatsLog;
import java.util.List;
/**
* A simple wrapper of FrameworkStatsLog.buildStatsEvent. This allows unit tests to mock out the
* dependency.
*/
public class StatsEventOutput {
List<StatsEvent> mOutput;
public StatsEventOutput(List<StatsEvent> output) {
mOutput = output;
}
/** Writes the data to the output. */
public void write(
int atomTag,
int uid,
String processName,
int measurementStartUptimeSecs,
int measurementEndUptimeSecs,
int measurementDurationUptimeSecs,
int topSeconds,
int fgsSeconds,
int boundTopSeconds,
int boundFgsSeconds,
int importantForegroundSeconds,
int cachedSeconds,
int frozenSeconds,
int otherSeconds) {
mOutput.add(
FrameworkStatsLog.buildStatsEvent(
atomTag,
uid,
processName,
measurementStartUptimeSecs,
measurementEndUptimeSecs,
measurementDurationUptimeSecs,
topSeconds,
fgsSeconds,
boundTopSeconds,
boundFgsSeconds,
importantForegroundSeconds,
cachedSeconds,
frozenSeconds,
otherSeconds));
}
/** Writes the data to the output. */
public void write(
int atomTag,
int clientUid,
String processName,
int serviceUid,
String serviceName,
int measurementStartUptimeSecs,
int measurementEndUptimeSecs,
int measurementDurationUptimeSecs,
int activeDurationUptimeSecs,
int activeCount,
String serviceProcessName) {
mOutput.add(
FrameworkStatsLog.buildStatsEvent(
atomTag,
clientUid,
processName,
serviceUid,
serviceName,
measurementStartUptimeSecs,
measurementEndUptimeSecs,
measurementDurationUptimeSecs,
activeDurationUptimeSecs,
activeCount,
serviceProcessName));
}
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2023 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.internal.app.procstats;
import static com.android.internal.app.procstats.ProcessStats.STATE_TOP;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.MockitoAnnotations.initMocks;
import androidx.test.filters.SmallTest;
import com.android.internal.util.FrameworkStatsLog;
import junit.framework.TestCase;
import org.junit.Before;
import org.mockito.Mock;
import java.util.concurrent.TimeUnit;
/** Provides test cases for ProcessStats. */
public class ProcessStatsTest extends TestCase {
private static final String APP_1_PACKAGE_NAME = "com.android.testapp";
private static final int APP_1_UID = 5001;
private static final long APP_1_VERSION = 10;
private static final String APP_1_PROCESS_NAME = "com.android.testapp.p";
private static final String APP_1_SERVICE_NAME = "com.android.testapp.service";
private static final String APP_2_PACKAGE_NAME = "com.android.testapp2";
private static final int APP_2_UID = 5002;
private static final long APP_2_VERSION = 30;
private static final String APP_2_PROCESS_NAME = "com.android.testapp2.p";
private static final long NOW_MS = 123000;
private static final int DURATION_SECS = 6;
@Mock StatsEventOutput mStatsEventOutput;
@Before
public void setUp() {
initMocks(this);
}
@SmallTest
public void testDumpProcessState() throws Exception {
ProcessStats processStats = new ProcessStats();
processStats.getProcessStateLocked(
APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
processStats.getProcessStateLocked(
APP_2_PACKAGE_NAME, APP_2_UID, APP_2_VERSION, APP_2_PROCESS_NAME);
processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
verify(mStatsEventOutput)
.write(
eq(FrameworkStatsLog.PROCESS_STATE),
eq(APP_1_UID),
eq(APP_1_PROCESS_NAME),
anyInt(),
anyInt(),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0));
verify(mStatsEventOutput)
.write(
eq(FrameworkStatsLog.PROCESS_STATE),
eq(APP_2_UID),
eq(APP_2_PROCESS_NAME),
anyInt(),
anyInt(),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0));
}
@SmallTest
public void testNonZeroProcessStateDuration() throws Exception {
ProcessStats processStats = new ProcessStats();
ProcessState processState =
processStats.getProcessStateLocked(
APP_1_PACKAGE_NAME, APP_1_UID, APP_1_VERSION, APP_1_PROCESS_NAME);
processState.setCombinedState(STATE_TOP, NOW_MS);
processState.commitStateTime(NOW_MS + TimeUnit.SECONDS.toMillis(DURATION_SECS));
processStats.dumpProcessState(FrameworkStatsLog.PROCESS_STATE, mStatsEventOutput);
verify(mStatsEventOutput)
.write(
eq(FrameworkStatsLog.PROCESS_STATE),
eq(APP_1_UID),
eq(APP_1_PROCESS_NAME),
anyInt(),
anyInt(),
eq(0),
eq(DURATION_SECS),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0),
eq(0));
}
@SmallTest
public void testDumpProcessAssociation() throws Exception {
ProcessStats processStats = new ProcessStats();
AssociationState associationState =
processStats.getAssociationStateLocked(
APP_1_PACKAGE_NAME,
APP_1_UID,
APP_1_VERSION,
APP_1_PROCESS_NAME,
APP_1_SERVICE_NAME);
AssociationState.SourceState sourceState =
associationState.startSource(APP_2_UID, APP_2_PROCESS_NAME, APP_2_PACKAGE_NAME);
sourceState.stop();
processStats.dumpProcessAssociation(
FrameworkStatsLog.PROCESS_ASSOCIATION, mStatsEventOutput);
verify(mStatsEventOutput)
.write(
eq(FrameworkStatsLog.PROCESS_ASSOCIATION),
eq(APP_2_UID),
eq(APP_2_PROCESS_NAME),
eq(APP_1_UID),
eq(APP_1_SERVICE_NAME),
anyInt(),
anyInt(),
eq(0),
eq(0),
eq(0),
eq(APP_1_PROCESS_NAME));
}
}

View File

@@ -169,6 +169,7 @@ import android.view.Display;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.app.procstats.StatsEventOutput;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelAllocationStats;
@@ -612,12 +613,19 @@ public class StatsPullAtomService extends SystemService {
}
case FrameworkStatsLog.PROC_STATS:
synchronized (mProcStatsLock) {
return pullProcStatsLocked(ProcessStats.REPORT_ALL, atomTag, data);
return pullProcStatsLocked(atomTag, data);
}
case FrameworkStatsLog.PROC_STATS_PKG_PROC:
synchronized (mProcStatsLock) {
return pullProcStatsLocked(ProcessStats.REPORT_PKG_PROC_STATS, atomTag,
data);
return pullProcStatsLocked(atomTag, data);
}
case FrameworkStatsLog.PROCESS_STATE:
synchronized (mProcStatsLock) {
return pullProcessStateLocked(atomTag, data);
}
case FrameworkStatsLog.PROCESS_ASSOCIATION:
synchronized (mProcStatsLock) {
return pullProcessAssociationLocked(atomTag, data);
}
case FrameworkStatsLog.DISK_IO:
synchronized (mDiskIoLock) {
@@ -890,6 +898,8 @@ public class StatsPullAtomService extends SystemService {
registerNumFacesEnrolled();
registerProcStats();
registerProcStatsPkgProc();
registerProcessState();
registerProcessAssociation();
registerDiskIO();
registerPowerProfile();
registerProcessCpuTime();
@@ -2870,59 +2880,138 @@ public class StatsPullAtomService extends SystemService {
);
}
private int pullProcStatsLocked(int section, int atomTag, List<StatsEvent> pulledData) {
private void registerProcessState() {
int tagId = FrameworkStatsLog.PROCESS_STATE;
mStatsManager.setPullAtomCallback(
tagId,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR,
mStatsCallbackImpl);
}
private void registerProcessAssociation() {
int tagId = FrameworkStatsLog.PROCESS_ASSOCIATION;
mStatsManager.setPullAtomCallback(
tagId,
null, // use default PullAtomMetadata values
DIRECT_EXECUTOR,
mStatsCallbackImpl);
}
@GuardedBy("mProcStatsLock")
private ProcessStats getStatsFromProcessStatsService(int atomTag) {
IProcessStats processStatsService = getIProcessStatsService();
if (processStatsService == null) {
return StatsManager.PULL_SKIP;
return null;
}
final long token = Binder.clearCallingIdentity();
try {
// force procstats to flush & combine old files into one store
long lastHighWaterMark = readProcStatsHighWaterMark(section);
ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
for (int i = 0; i < protoStreams.length; i++) {
protoStreams[i] = new ProtoOutputStream();
}
long lastHighWaterMark = readProcStatsHighWaterMark(atomTag);
ProcessStats procStats = new ProcessStats(false);
// Force processStatsService to aggregate all in-storage and in-memory data.
long highWaterMark = processStatsService.getCommittedStatsMerged(
lastHighWaterMark, section, true, null, procStats);
procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
for (int i = 0; i < protoStreams.length; i++) {
byte[] bytes = protoStreams[i].getBytes(); // cache the value
if (bytes.length > 0) {
pulledData.add(FrameworkStatsLog.buildStatsEvent(atomTag, bytes,
// This is a shard ID, and is specified in the metric definition to be
// a dimension. This will result in statsd using RANDOM_ONE_SAMPLE to
// keep all the shards, as it thinks each shard is a different dimension
// of data.
i));
}
}
new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + lastHighWaterMark)
long highWaterMark =
processStatsService.getCommittedStatsMerged(
lastHighWaterMark,
ProcessStats.REPORT_ALL, // ignored since committedStats below is null.
true,
null, // committedStats
procStats);
new File(
mBaseDir.getAbsolutePath()
+ "/"
+ highWaterMarkFilePrefix(atomTag)
+ "_"
+ lastHighWaterMark)
.delete();
new File(mBaseDir.getAbsolutePath() + "/" + section + "_" + highWaterMark)
new File(
mBaseDir.getAbsolutePath()
+ "/"
+ highWaterMarkFilePrefix(atomTag)
+ "_"
+ highWaterMark)
.createNewFile();
return procStats;
} catch (RemoteException | IOException e) {
Slog.e(TAG, "Getting procstats failed: ", e);
return StatsManager.PULL_SKIP;
return null;
} finally {
Binder.restoreCallingIdentity(token);
}
}
@GuardedBy("mProcStatsLock")
private int pullProcStatsLocked(int atomTag, List<StatsEvent> pulledData) {
ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
if (procStats == null) {
return StatsManager.PULL_SKIP;
}
ProtoOutputStream[] protoStreams = new ProtoOutputStream[MAX_PROCSTATS_SHARDS];
for (int i = 0; i < protoStreams.length; i++) {
protoStreams[i] = new ProtoOutputStream();
}
procStats.dumpAggregatedProtoForStatsd(protoStreams, MAX_PROCSTATS_RAW_SHARD_SIZE);
for (int i = 0; i < protoStreams.length; i++) {
byte[] bytes = protoStreams[i].getBytes(); // cache the value
if (bytes.length > 0) {
pulledData.add(
FrameworkStatsLog.buildStatsEvent(
atomTag,
bytes,
// This is a shard ID, and is specified in the metric definition to
// be
// a dimension. This will result in statsd using RANDOM_ONE_SAMPLE
// to
// keep all the shards, as it thinks each shard is a different
// dimension
// of data.
i));
}
}
return StatsManager.PULL_SUCCESS;
}
@GuardedBy("mProcStatsLock")
private int pullProcessStateLocked(int atomTag, List<StatsEvent> pulledData) {
ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
if (procStats == null) {
return StatsManager.PULL_SKIP;
}
procStats.dumpProcessState(atomTag, new StatsEventOutput(pulledData));
return StatsManager.PULL_SUCCESS;
}
@GuardedBy("mProcStatsLock")
private int pullProcessAssociationLocked(int atomTag, List<StatsEvent> pulledData) {
ProcessStats procStats = getStatsFromProcessStatsService(atomTag);
if (procStats == null) {
return StatsManager.PULL_SKIP;
}
procStats.dumpProcessAssociation(atomTag, new StatsEventOutput(pulledData));
return StatsManager.PULL_SUCCESS;
}
private String highWaterMarkFilePrefix(int atomTag) {
// For backward compatibility, use the legacy ProcessStats enum value as the prefix for
// PROC_STATS and PROC_STATS_PKG_PROC.
if (atomTag == FrameworkStatsLog.PROC_STATS) {
return String.valueOf(ProcessStats.REPORT_ALL);
}
if (atomTag == FrameworkStatsLog.PROC_STATS_PKG_PROC) {
return String.valueOf(ProcessStats.REPORT_PKG_PROC_STATS);
}
return "atom-" + atomTag;
}
// read high watermark for section
private long readProcStatsHighWaterMark(int section) {
private long readProcStatsHighWaterMark(int atomTag) {
try {
File[] files = mBaseDir.listFiles((d, name) -> {
return name.toLowerCase().startsWith(String.valueOf(section) + '_');
});
File[] files =
mBaseDir.listFiles(
(d, name) -> {
return name.toLowerCase()
.startsWith(highWaterMarkFilePrefix(atomTag) + '_');
});
if (files == null || files.length == 0) {
return 0;
}