Loading services/core/java/com/android/server/am/ActivityMetricsLogger.java +42 −4 Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromMemcg; import android.content.Context; import android.metrics.LogMaker; Loading Loading @@ -67,6 +69,7 @@ class ActivityMetricsLogger { private static final long INVALID_START_TIME = -1; private static final int MSG_CHECK_VISIBILITY = 0; private static final int MSG_LOG_APP_START_MEMORY_STATE_CAPTURE = 1; // Preallocated strings we are sending to tron, so we don't have to allocate a new one every // time we log. Loading Loading @@ -102,6 +105,9 @@ class ActivityMetricsLogger { final SomeArgs args = (SomeArgs) msg.obj; checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2); break; case MSG_LOG_APP_START_MEMORY_STATE_CAPTURE: logAppStartMemoryStateCapture((StackTransitionInfo) msg.obj); break; } } }; Loading Loading @@ -187,10 +193,7 @@ class ActivityMetricsLogger { * @param launchedActivity the activity that is being launched */ void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity) { final ProcessRecord processRecord = launchedActivity != null ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName, launchedActivity.appInfo.uid) : null; final ProcessRecord processRecord = findProcessForActivity(launchedActivity); final boolean processRunning = processRecord != null; // We consider this a "process switch" if the process of the activity that gets launched Loading Loading @@ -492,6 +495,7 @@ class ActivityMetricsLogger { info.bindApplicationDelayMs, info.windowsDrawnDelayMs, launchToken); mHandler.obtainMessage(MSG_LOG_APP_START_MEMORY_STATE_CAPTURE, info).sendToTarget(); } } Loading Loading @@ -548,4 +552,38 @@ class ActivityMetricsLogger { } return -1; } private void logAppStartMemoryStateCapture(StackTransitionInfo info) { final ProcessRecord processRecord = findProcessForActivity(info.launchedActivity); if (processRecord == null) { if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture processRecord null"); return; } final int pid = processRecord.pid; final int uid = info.launchedActivity.appInfo.uid; final MemoryStat memoryStat = readMemoryStatFromMemcg(uid, pid); if (memoryStat == null) { if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture memoryStat null"); return; } StatsLog.write( StatsLog.APP_START_MEMORY_STATE_CAPTURED, uid, info.launchedActivity.processName, info.launchedActivity.info.name, memoryStat.pgfault, memoryStat.pgmajfault, memoryStat.rssInBytes, memoryStat.cacheInBytes, memoryStat.swapInBytes); } private ProcessRecord findProcessForActivity(ActivityRecord launchedActivity) { return launchedActivity != null ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName, launchedActivity.appInfo.uid) : null; } } services/core/java/com/android/server/am/MemoryStatUtil.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.Nullable; import android.os.FileUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Static utility methods related to {@link MemoryStat}. */ final class MemoryStatUtil { private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; /** Path to memory stat file for logging app start memory state */ private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); private static final Pattern RSS_IN_BYTES = Pattern.compile("total_rss (\\d+)"); private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); private MemoryStatUtil() {} /** * Reads memory.stat of a process from memcg. */ static @Nullable MemoryStat readMemoryStatFromMemcg(int uid, int pid) { final String memoryStatPath = String.format(MEMORY_STAT_FILE_FMT, uid, pid); final File memoryStatFile = new File(memoryStatPath); if (!memoryStatFile.exists()) { if (DEBUG_METRICS) Slog.i(TAG, memoryStatPath + " not found"); return null; } try { final String memoryStatContents = FileUtils.readTextFile( memoryStatFile, 0 /* max */, null /* ellipsis */); return parseMemoryStat(memoryStatContents); } catch (IOException e) { Slog.e(TAG, "Failed to read file:", e); return null; } } /** * Parses relevant statistics out from the contents of a memory.stat file in memcg. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) static @Nullable MemoryStat parseMemoryStat(String memoryStatContents) { MemoryStat memoryStat = new MemoryStat(); if (memoryStatContents == null) { return memoryStat; } Matcher m; m = PGFAULT.matcher(memoryStatContents); memoryStat.pgfault = m.find() ? Long.valueOf(m.group(1)) : 0; m = PGMAJFAULT.matcher(memoryStatContents); memoryStat.pgmajfault = m.find() ? Long.valueOf(m.group(1)) : 0; m = RSS_IN_BYTES.matcher(memoryStatContents); memoryStat.rssInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; m = CACHE_IN_BYTES.matcher(memoryStatContents); memoryStat.cacheInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; m = SWAP_IN_BYTES.matcher(memoryStatContents); memoryStat.swapInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; return memoryStat; } static final class MemoryStat { /** Number of page faults */ long pgfault; /** Number of major page faults */ long pgmajfault; /** Number of bytes of anonymous and swap cache memory */ long rssInBytes; /** Number of bytes of page cache memory */ long cacheInBytes; /** Number of bytes of swap usage */ long swapInBytes; } } services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static com.android.server.am.MemoryStatUtil.parseMemoryStat; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class MemoryStatUtilTest { private String MEMORY_STAT_CONTENTS = String.join( "\n", "cache 96", // keep different from total_cache to catch reading wrong value "rss 97", // keep different from total_rss to catch reading wrong value "rss_huge 0", "mapped_file 524288", "writeback 0", "swap 95", // keep different from total_rss to catch reading wrong value "pgpgin 16717", "pgpgout 5037", "pgfault 99", // keep different from total_pgfault to catch reading wrong value "pgmajfault 98", // keep different from total_pgmajfault to catch reading wrong value "inactive_anon 503808", "active_anon 46309376", "inactive_file 876544", "active_file 81920", "unevictable 0", "hierarchical_memory_limit 18446744073709551615", "hierarchical_memsw_limit 18446744073709551615", "total_cache 4", "total_rss 3", "total_rss_huge 0", "total_mapped_file 524288", "total_writeback 0", "total_swap 5", "total_pgpgin 16717", "total_pgpgout 5037", "total_pgfault 1", "total_pgmajfault 2", "total_inactive_anon 503808", "total_active_anon 46309376", "total_inactive_file 876544", "total_active_file 81920", "total_unevictable 0"); @Test public void testParseMemoryStat_parsesCorrectValues() throws Exception { MemoryStat stat = parseMemoryStat(MEMORY_STAT_CONTENTS); assertEquals(stat.pgfault, 1); assertEquals(stat.pgmajfault, 2); assertEquals(stat.rssInBytes, 3); assertEquals(stat.cacheInBytes, 4); assertEquals(stat.swapInBytes, 5); } @Test public void testParseMemoryStat_emptyMemoryStatContents() throws Exception { MemoryStat stat = parseMemoryStat(""); assertEquals(stat.pgfault, 0); assertEquals(stat.pgmajfault, 0); assertEquals(stat.rssInBytes, 0); assertEquals(stat.cacheInBytes, 0); assertEquals(stat.swapInBytes, 0); stat = parseMemoryStat(null); assertEquals(stat.pgfault, 0); assertEquals(stat.pgmajfault, 0); assertEquals(stat.rssInBytes, 0); assertEquals(stat.cacheInBytes, 0); assertEquals(stat.swapInBytes, 0); } } Loading
services/core/java/com/android/server/am/ActivityMetricsLogger.java +42 −4 Original line number Diff line number Diff line Loading @@ -32,6 +32,8 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromMemcg; import android.content.Context; import android.metrics.LogMaker; Loading Loading @@ -67,6 +69,7 @@ class ActivityMetricsLogger { private static final long INVALID_START_TIME = -1; private static final int MSG_CHECK_VISIBILITY = 0; private static final int MSG_LOG_APP_START_MEMORY_STATE_CAPTURE = 1; // Preallocated strings we are sending to tron, so we don't have to allocate a new one every // time we log. Loading Loading @@ -102,6 +105,9 @@ class ActivityMetricsLogger { final SomeArgs args = (SomeArgs) msg.obj; checkVisibility((TaskRecord) args.arg1, (ActivityRecord) args.arg2); break; case MSG_LOG_APP_START_MEMORY_STATE_CAPTURE: logAppStartMemoryStateCapture((StackTransitionInfo) msg.obj); break; } } }; Loading Loading @@ -187,10 +193,7 @@ class ActivityMetricsLogger { * @param launchedActivity the activity that is being launched */ void notifyActivityLaunched(int resultCode, ActivityRecord launchedActivity) { final ProcessRecord processRecord = launchedActivity != null ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName, launchedActivity.appInfo.uid) : null; final ProcessRecord processRecord = findProcessForActivity(launchedActivity); final boolean processRunning = processRecord != null; // We consider this a "process switch" if the process of the activity that gets launched Loading Loading @@ -492,6 +495,7 @@ class ActivityMetricsLogger { info.bindApplicationDelayMs, info.windowsDrawnDelayMs, launchToken); mHandler.obtainMessage(MSG_LOG_APP_START_MEMORY_STATE_CAPTURE, info).sendToTarget(); } } Loading Loading @@ -548,4 +552,38 @@ class ActivityMetricsLogger { } return -1; } private void logAppStartMemoryStateCapture(StackTransitionInfo info) { final ProcessRecord processRecord = findProcessForActivity(info.launchedActivity); if (processRecord == null) { if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture processRecord null"); return; } final int pid = processRecord.pid; final int uid = info.launchedActivity.appInfo.uid; final MemoryStat memoryStat = readMemoryStatFromMemcg(uid, pid); if (memoryStat == null) { if (DEBUG_METRICS) Slog.i(TAG, "logAppStartMemoryStateCapture memoryStat null"); return; } StatsLog.write( StatsLog.APP_START_MEMORY_STATE_CAPTURED, uid, info.launchedActivity.processName, info.launchedActivity.info.name, memoryStat.pgfault, memoryStat.pgmajfault, memoryStat.rssInBytes, memoryStat.cacheInBytes, memoryStat.swapInBytes); } private ProcessRecord findProcessForActivity(ActivityRecord launchedActivity) { return launchedActivity != null ? mSupervisor.mService.mProcessNames.get(launchedActivity.processName, launchedActivity.appInfo.uid) : null; } }
services/core/java/com/android/server/am/MemoryStatUtil.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_METRICS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.Nullable; import android.os.FileUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Static utility methods related to {@link MemoryStat}. */ final class MemoryStatUtil { private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; /** Path to memory stat file for logging app start memory state */ private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); private static final Pattern RSS_IN_BYTES = Pattern.compile("total_rss (\\d+)"); private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); private MemoryStatUtil() {} /** * Reads memory.stat of a process from memcg. */ static @Nullable MemoryStat readMemoryStatFromMemcg(int uid, int pid) { final String memoryStatPath = String.format(MEMORY_STAT_FILE_FMT, uid, pid); final File memoryStatFile = new File(memoryStatPath); if (!memoryStatFile.exists()) { if (DEBUG_METRICS) Slog.i(TAG, memoryStatPath + " not found"); return null; } try { final String memoryStatContents = FileUtils.readTextFile( memoryStatFile, 0 /* max */, null /* ellipsis */); return parseMemoryStat(memoryStatContents); } catch (IOException e) { Slog.e(TAG, "Failed to read file:", e); return null; } } /** * Parses relevant statistics out from the contents of a memory.stat file in memcg. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) static @Nullable MemoryStat parseMemoryStat(String memoryStatContents) { MemoryStat memoryStat = new MemoryStat(); if (memoryStatContents == null) { return memoryStat; } Matcher m; m = PGFAULT.matcher(memoryStatContents); memoryStat.pgfault = m.find() ? Long.valueOf(m.group(1)) : 0; m = PGMAJFAULT.matcher(memoryStatContents); memoryStat.pgmajfault = m.find() ? Long.valueOf(m.group(1)) : 0; m = RSS_IN_BYTES.matcher(memoryStatContents); memoryStat.rssInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; m = CACHE_IN_BYTES.matcher(memoryStatContents); memoryStat.cacheInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; m = SWAP_IN_BYTES.matcher(memoryStatContents); memoryStat.swapInBytes = m.find() ? Long.valueOf(m.group(1)) : 0; return memoryStat; } static final class MemoryStat { /** Number of page faults */ long pgfault; /** Number of major page faults */ long pgmajfault; /** Number of bytes of anonymous and swap cache memory */ long rssInBytes; /** Number of bytes of page cache memory */ long cacheInBytes; /** Number of bytes of swap usage */ long swapInBytes; } }
services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java 0 → 100644 +95 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.am; import static com.android.server.am.MemoryStatUtil.parseMemoryStat; import static com.android.server.am.MemoryStatUtil.MemoryStat; import static org.junit.Assert.assertEquals; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class MemoryStatUtilTest { private String MEMORY_STAT_CONTENTS = String.join( "\n", "cache 96", // keep different from total_cache to catch reading wrong value "rss 97", // keep different from total_rss to catch reading wrong value "rss_huge 0", "mapped_file 524288", "writeback 0", "swap 95", // keep different from total_rss to catch reading wrong value "pgpgin 16717", "pgpgout 5037", "pgfault 99", // keep different from total_pgfault to catch reading wrong value "pgmajfault 98", // keep different from total_pgmajfault to catch reading wrong value "inactive_anon 503808", "active_anon 46309376", "inactive_file 876544", "active_file 81920", "unevictable 0", "hierarchical_memory_limit 18446744073709551615", "hierarchical_memsw_limit 18446744073709551615", "total_cache 4", "total_rss 3", "total_rss_huge 0", "total_mapped_file 524288", "total_writeback 0", "total_swap 5", "total_pgpgin 16717", "total_pgpgout 5037", "total_pgfault 1", "total_pgmajfault 2", "total_inactive_anon 503808", "total_active_anon 46309376", "total_inactive_file 876544", "total_active_file 81920", "total_unevictable 0"); @Test public void testParseMemoryStat_parsesCorrectValues() throws Exception { MemoryStat stat = parseMemoryStat(MEMORY_STAT_CONTENTS); assertEquals(stat.pgfault, 1); assertEquals(stat.pgmajfault, 2); assertEquals(stat.rssInBytes, 3); assertEquals(stat.cacheInBytes, 4); assertEquals(stat.swapInBytes, 5); } @Test public void testParseMemoryStat_emptyMemoryStatContents() throws Exception { MemoryStat stat = parseMemoryStat(""); assertEquals(stat.pgfault, 0); assertEquals(stat.pgmajfault, 0); assertEquals(stat.rssInBytes, 0); assertEquals(stat.cacheInBytes, 0); assertEquals(stat.swapInBytes, 0); stat = parseMemoryStat(null); assertEquals(stat.pgfault, 0); assertEquals(stat.pgmajfault, 0); assertEquals(stat.rssInBytes, 0); assertEquals(stat.cacheInBytes, 0); assertEquals(stat.swapInBytes, 0); } }