Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d33dbf0e authored by Zhi An Ng's avatar Zhi An Ng Committed by Android (Google) Code Review
Browse files

Merge "Log app start to statsd"

parents 71a8fcfd bbefdec6
Loading
Loading
Loading
Loading
+42 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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.
@@ -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;
            }
        }
    };
@@ -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
@@ -492,6 +495,7 @@ class ActivityMetricsLogger {
                    info.bindApplicationDelayMs,
                    info.windowsDrawnDelayMs,
                    launchToken);
            mHandler.obtainMessage(MSG_LOG_APP_START_MEMORY_STATE_CAPTURE, info).sendToTarget();
        }
    }

@@ -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;
    }
}
+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;
    }
}
+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);
  }
}