Loading apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ public class KernelCpuThreadReaderPerfTest { public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); private final KernelCpuThreadReader mKernelCpuThreadReader = KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000, 0); KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000); @Test public void timeReadCurrentProcessCpuUsage() { Loading core/java/com/android/internal/os/KernelCpuThreadReader.java +7 −69 Original line number Diff line number Diff line Loading @@ -92,24 +92,12 @@ public class KernelCpuThreadReader { /** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */ private static final int ID_ERROR = -1; /** Thread ID used when reporting CPU used by other threads */ private static final int OTHER_THREADS_ID = -1; /** Thread name used when reporting CPU used by other threads */ private static final String OTHER_THREADS_NAME = "__OTHER_THREADS"; /** * When checking whether to report data for a thread, we check the UID of the thread's owner * against this predicate */ private Predicate<Integer> mUidPredicate; /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ private int mMinimumTotalCpuUsageMillis; /** Where the proc filesystem is mounted */ private final Path mProcPath; Loading Loading @@ -138,13 +126,11 @@ public class KernelCpuThreadReader { public KernelCpuThreadReader( int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis, Path procPath, Path initialTimeInStatePath, Injector injector) throws IOException { mUidPredicate = uidPredicate; mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; mProcPath = procPath; mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath); mInjector = injector; Loading @@ -157,13 +143,11 @@ public class KernelCpuThreadReader { * @return the reader, null if an exception was thrown during creation */ @Nullable public static KernelCpuThreadReader create( int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis) { public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) { try { return new KernelCpuThreadReader( numBuckets, uidPredicate, minimumTotalCpuUsageMillis, DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH, new Injector()); Loading Loading @@ -258,18 +242,6 @@ public class KernelCpuThreadReader { mUidPredicate = uidPredicate; } /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) { if (minimumTotalCpuUsageMillis < 0) { Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis); return; } mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; } /** * Read all of the CPU usage statistics for each child thread of a process * Loading @@ -292,7 +264,6 @@ public class KernelCpuThreadReader { + uid); } int[] filteredThreadsCpuUsage = null; final Path allThreadsPath = processPath.resolve("task"); final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>(); try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) { Loading @@ -301,14 +272,6 @@ public class KernelCpuThreadReader { if (threadCpuUsage == null) { continue; } if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) { if (filteredThreadsCpuUsage == null) { filteredThreadsCpuUsage = new int[mFrequenciesKhz.length]; } filteredThreadsCpuUsage = sumCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis); continue; } threadCpuUsages.add(threadCpuUsage); } } catch (IOException e) { Loading @@ -320,14 +283,6 @@ public class KernelCpuThreadReader { if (threadCpuUsages.isEmpty()) { return null; } // Add the filtered out thread CPU usage under an "other threads" ThreadCpuUsage if (filteredThreadsCpuUsage != null) { threadCpuUsages.add( new ThreadCpuUsage( OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage)); } if (DEBUG) { Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads"); } Loading Loading @@ -404,25 +359,6 @@ public class KernelCpuThreadReader { } } /** Get the sum of all CPU usage across all frequencies */ @SuppressWarnings("ForLoopReplaceableByForEach") private static int totalCpuUsage(int[] cpuUsage) { int total = 0; for (int i = 0; i < cpuUsage.length; i++) { total += cpuUsage[i]; } return total; } /** Add two CPU frequency usages together */ private static int[] sumCpuUsage(int[] a, int[] b) { int[] summed = new int[a.length]; for (int i = 0; i < a.length; i++) { summed[i] = a[i] + b[i]; } return summed; } /** Puts frequencies and usage times into buckets */ @VisibleForTesting public static class FrequencyBucketCreator { Loading Loading @@ -553,9 +489,10 @@ public class KernelCpuThreadReader { public final int processId; public final String processName; public final int uid; public final ArrayList<ThreadCpuUsage> threadCpuUsages; public ArrayList<ThreadCpuUsage> threadCpuUsages; ProcessCpuUsage( @VisibleForTesting public ProcessCpuUsage( int processId, String processName, int uid, Loading @@ -571,9 +508,10 @@ public class KernelCpuThreadReader { public static class ThreadCpuUsage { public final int threadId; public final String threadName; public final int[] usageTimesMillis; public int[] usageTimesMillis; ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) { @VisibleForTesting public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) { this.threadId = threadId; this.threadName = threadName; this.usageTimesMillis = usageTimesMillis; Loading core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java 0 → 100644 +305 −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.internal.os; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; /** * Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the * difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}. * * <p>Some notes on the diff calculation: * * <ul> * <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N * of this method will return CPU used by threads between call N-1 and N. * <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first * call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object). * <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that * the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value * returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1. * <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no * information for CPU usage between call N-1 and N (as we don't know the start value) and * between N and N+1 (as we don't know the end value). Assuming all other calls are * successful, the next call to return data will be N+2, for the period between N+1 and N+2. * <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the * data will only be dropped for call N, as we can still use the CPU data for the surrounding * calls. * </ul> * * <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A * thread will not be reported unless its total CPU usage is at least equal to the value set in * {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under * one "other threads" thread. This reduces the cardinality of the {@link * #getProcessCpuUsageDiffed()} result. * * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of * WestWorld, because the thresholding should be done after diffing, not before. This is because of * two issues with thresholding before diffing: * * <ul> * <li>We would threshold less and less threads as thread uptime increases. * <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads" * result could have negative diffs, and the newly unfiltered threads would have incorrect * diffs that include CPU usage from when they were filtered. * </ul> * * @hide Only for use within the system server */ @SuppressWarnings("ForLoopReplaceableByForEach") public class KernelCpuThreadReaderDiff { private static final String TAG = "KernelCpuThreadReaderDiff"; /** Thread ID used when reporting CPU used by other threads */ private static final int OTHER_THREADS_ID = -1; /** Thread name used when reporting CPU used by other threads */ private static final String OTHER_THREADS_NAME = "__OTHER_THREADS"; private final KernelCpuThreadReader mReader; /** * CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no * previous call, or if the previous call failed * * <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The * identifier contains the minimal amount of information to identify a thread (see {@link * ThreadKey} for more information), thus reducing memory consumption. */ @Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage; /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ private int mMinimumTotalCpuUsageMillis; @VisibleForTesting public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) { mReader = reader; mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; mPreviousCpuUsage = null; } /** * Returns the difference in CPU usage since the last time this method was called. * * @see KernelCpuThreadReader#getProcessCpuUsage() */ @Nullable public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() { Map<ThreadKey, int[]> newCpuUsage = null; try { // Get the thread CPU usage and index them by ThreadKey final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages = mReader.getProcessCpuUsage(); newCpuUsage = createCpuUsageMap(processCpuUsages); // If there is no previous CPU usage, return nothing if (mPreviousCpuUsage == null) { return null; } // Do diffing and thresholding for each process for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); changeToDiffs(mPreviousCpuUsage, processCpuUsage); applyThresholding(processCpuUsage); } return processCpuUsages; } finally { // Always update the previous CPU usage. If we haven't got an update, it will be set to // null, so the next call knows there no previous values mPreviousCpuUsage = newCpuUsage; } } /** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */ @Nullable public int[] getCpuFrequenciesKhz() { return mReader.getCpuFrequenciesKhz(); } /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) { if (minimumTotalCpuUsageMillis < 0) { Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis); return; } mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; } /** * Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when * calculating diffs */ private static Map<ThreadKey, int[]> createCpuUsageMap( List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) { final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>(); for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = processCpuUsage.threadCpuUsages.get(j); cpuUsageMap.put( new ThreadKey( processCpuUsage.processId, threadCpuUsage.threadId, processCpuUsage.processName, threadCpuUsage.threadName), threadCpuUsage.usageTimesMillis); } } return cpuUsageMap; } /** * Calculate the difference in per-frequency CPU usage for all threads in a process * * @param previousCpuUsage CPU usage from the last call, the base of the diff * @param processCpuUsage CPU usage from the current call, this value is modified to contain the * diffed values */ private static void changeToDiffs( Map<ThreadKey, int[]> previousCpuUsage, KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) { for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = processCpuUsage.threadCpuUsages.get(i); final ThreadKey key = new ThreadKey( processCpuUsage.processId, threadCpuUsage.threadId, processCpuUsage.processName, threadCpuUsage.threadName); int[] previous = previousCpuUsage.get(key); if (previous == null) { // If there's no previous CPU usage, assume that it's zero previous = new int[threadCpuUsage.usageTimesMillis.length]; } threadCpuUsage.usageTimesMillis = cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous); } } /** * Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage * * <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with * {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}. * * @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change * the threads it contains */ private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) { int[] filteredThreadsCpuUsage = null; final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>(); for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = processCpuUsage.threadCpuUsages.get(i); if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) { if (filteredThreadsCpuUsage == null) { filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length]; } addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis); continue; } thresholded.add(threadCpuUsage); } if (filteredThreadsCpuUsage != null) { thresholded.add( new KernelCpuThreadReader.ThreadCpuUsage( OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage)); } processCpuUsage.threadCpuUsages = thresholded; } /** Get the sum of all CPU usage across all frequencies */ private static int totalCpuUsage(int[] cpuUsage) { int total = 0; for (int i = 0; i < cpuUsage.length; i++) { total += cpuUsage[i]; } return total; } /** Add two CPU frequency usages together */ private static void addToCpuUsage(int[] a, int[] b) { for (int i = 0; i < a.length; i++) { a[i] += b[i]; } } /** Subtract two CPU frequency usages from each other */ private static int[] cpuTimeDiff(int[] a, int[] b) { int[] difference = new int[a.length]; for (int i = 0; i < a.length; i++) { difference[i] = a[i] - b[i]; } return difference; } /** * Identifies a thread * * <p>Only stores the minimum amount of information to identify a thread. This includes the * PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash * of the name of the process/thread. */ private static class ThreadKey { private final int mProcessId; private final int mThreadId; private final int mProcessNameHash; private final int mThreadNameHash; ThreadKey(int processId, int threadId, String processName, String threadName) { this.mProcessId = processId; this.mThreadId = threadId; // Only store the hash to reduce memory consumption this.mProcessNameHash = Objects.hash(processName); this.mThreadNameHash = Objects.hash(threadName); } @Override public int hashCode() { return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash); } @Override public boolean equals(Object obj) { if (!(obj instanceof ThreadKey)) { return false; } ThreadKey other = (ThreadKey) obj; return mProcessId == other.mProcessId && mThreadId == other.mThreadId && mProcessNameHash == other.mProcessNameHash && mThreadNameHash == other.mThreadNameHash; } } } core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java +9 −6 Original line number Diff line number Diff line Loading @@ -67,12 +67,14 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader; @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff; /** * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change * in settings, returns null if creation failed */ @Nullable public static KernelCpuThreadReader getSettingsModifiedReader(Context context) { public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) { // Create the observer KernelCpuThreadReaderSettingsObserver settingsObserver = new KernelCpuThreadReaderSettingsObserver(context); Loading @@ -82,7 +84,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { .registerContentObserver( settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM); // Return the observer's reader return settingsObserver.mKernelCpuThreadReader; return settingsObserver.mKernelCpuThreadReaderDiff; } private KernelCpuThreadReaderSettingsObserver(Context context) { Loading @@ -90,9 +92,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { mContext = context; mKernelCpuThreadReader = KernelCpuThreadReader.create( NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT), MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT); NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT)); mKernelCpuThreadReaderDiff = new KernelCpuThreadReaderDiff( mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT); } @Override Loading Loading @@ -130,7 +133,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { mKernelCpuThreadReader.setNumBuckets( parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT)); mKernelCpuThreadReader.setUidPredicate(uidPredicate); mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis( mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis( parser.getInt( MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT)); Loading core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java 0 → 100644 +242 −0 File added.Preview size limit exceeded, changes collapsed. Show changes Loading
apct-tests/perftests/core/src/android/os/KernelCpuThreadReaderPerfTest.java +1 −1 Original line number Diff line number Diff line Loading @@ -40,7 +40,7 @@ public class KernelCpuThreadReaderPerfTest { public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter(); private final KernelCpuThreadReader mKernelCpuThreadReader = KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000, 0); KernelCpuThreadReader.create(8, uid -> 1000 <= uid && uid < 2000); @Test public void timeReadCurrentProcessCpuUsage() { Loading
core/java/com/android/internal/os/KernelCpuThreadReader.java +7 −69 Original line number Diff line number Diff line Loading @@ -92,24 +92,12 @@ public class KernelCpuThreadReader { /** Value returned when there was an error getting an integer ID value (e.g. PID, UID) */ private static final int ID_ERROR = -1; /** Thread ID used when reporting CPU used by other threads */ private static final int OTHER_THREADS_ID = -1; /** Thread name used when reporting CPU used by other threads */ private static final String OTHER_THREADS_NAME = "__OTHER_THREADS"; /** * When checking whether to report data for a thread, we check the UID of the thread's owner * against this predicate */ private Predicate<Integer> mUidPredicate; /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ private int mMinimumTotalCpuUsageMillis; /** Where the proc filesystem is mounted */ private final Path mProcPath; Loading Loading @@ -138,13 +126,11 @@ public class KernelCpuThreadReader { public KernelCpuThreadReader( int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis, Path procPath, Path initialTimeInStatePath, Injector injector) throws IOException { mUidPredicate = uidPredicate; mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; mProcPath = procPath; mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath); mInjector = injector; Loading @@ -157,13 +143,11 @@ public class KernelCpuThreadReader { * @return the reader, null if an exception was thrown during creation */ @Nullable public static KernelCpuThreadReader create( int numBuckets, Predicate<Integer> uidPredicate, int minimumTotalCpuUsageMillis) { public static KernelCpuThreadReader create(int numBuckets, Predicate<Integer> uidPredicate) { try { return new KernelCpuThreadReader( numBuckets, uidPredicate, minimumTotalCpuUsageMillis, DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH, new Injector()); Loading Loading @@ -258,18 +242,6 @@ public class KernelCpuThreadReader { mUidPredicate = uidPredicate; } /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) { if (minimumTotalCpuUsageMillis < 0) { Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis); return; } mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; } /** * Read all of the CPU usage statistics for each child thread of a process * Loading @@ -292,7 +264,6 @@ public class KernelCpuThreadReader { + uid); } int[] filteredThreadsCpuUsage = null; final Path allThreadsPath = processPath.resolve("task"); final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>(); try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(allThreadsPath)) { Loading @@ -301,14 +272,6 @@ public class KernelCpuThreadReader { if (threadCpuUsage == null) { continue; } if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) { if (filteredThreadsCpuUsage == null) { filteredThreadsCpuUsage = new int[mFrequenciesKhz.length]; } filteredThreadsCpuUsage = sumCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis); continue; } threadCpuUsages.add(threadCpuUsage); } } catch (IOException e) { Loading @@ -320,14 +283,6 @@ public class KernelCpuThreadReader { if (threadCpuUsages.isEmpty()) { return null; } // Add the filtered out thread CPU usage under an "other threads" ThreadCpuUsage if (filteredThreadsCpuUsage != null) { threadCpuUsages.add( new ThreadCpuUsage( OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage)); } if (DEBUG) { Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads"); } Loading Loading @@ -404,25 +359,6 @@ public class KernelCpuThreadReader { } } /** Get the sum of all CPU usage across all frequencies */ @SuppressWarnings("ForLoopReplaceableByForEach") private static int totalCpuUsage(int[] cpuUsage) { int total = 0; for (int i = 0; i < cpuUsage.length; i++) { total += cpuUsage[i]; } return total; } /** Add two CPU frequency usages together */ private static int[] sumCpuUsage(int[] a, int[] b) { int[] summed = new int[a.length]; for (int i = 0; i < a.length; i++) { summed[i] = a[i] + b[i]; } return summed; } /** Puts frequencies and usage times into buckets */ @VisibleForTesting public static class FrequencyBucketCreator { Loading Loading @@ -553,9 +489,10 @@ public class KernelCpuThreadReader { public final int processId; public final String processName; public final int uid; public final ArrayList<ThreadCpuUsage> threadCpuUsages; public ArrayList<ThreadCpuUsage> threadCpuUsages; ProcessCpuUsage( @VisibleForTesting public ProcessCpuUsage( int processId, String processName, int uid, Loading @@ -571,9 +508,10 @@ public class KernelCpuThreadReader { public static class ThreadCpuUsage { public final int threadId; public final String threadName; public final int[] usageTimesMillis; public int[] usageTimesMillis; ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) { @VisibleForTesting public ThreadCpuUsage(int threadId, String threadName, int[] usageTimesMillis) { this.threadId = threadId; this.threadName = threadName; this.usageTimesMillis = usageTimesMillis; Loading
core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java 0 → 100644 +305 −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.internal.os; import android.annotation.Nullable; import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; /** * Delegates per-thread CPU collection to {@link KernelCpuThreadReader}, and calculates the * difference between CPU usage at each call of {@link #getProcessCpuUsageDiffed()}. * * <p>Some notes on the diff calculation: * * <ul> * <li>The diffing is done between each call of {@link #getProcessCpuUsageDiffed()}, i.e. call N * of this method will return CPU used by threads between call N-1 and N. * <li>The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first * call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object). * <li>If a thread does not exist at call N, but does exist at call N+1, the diff will assume that * the CPU usage at call N was zero. Thus, the diff reported will be equivalent to the value * returned by {@link KernelCpuThreadReader#getProcessCpuUsage()} at call N+1. * <li>If an error occurs in {@link KernelCpuThreadReader} at call N, we will return no * information for CPU usage between call N-1 and N (as we don't know the start value) and * between N and N+1 (as we don't know the end value). Assuming all other calls are * successful, the next call to return data will be N+2, for the period between N+1 and N+2. * <li>If an error occurs in this class (but not in {@link KernelCpuThreadReader}) at call N, the * data will only be dropped for call N, as we can still use the CPU data for the surrounding * calls. * </ul> * * <p>Additionally to diffing, this class also contains logic for thresholding reported threads. A * thread will not be reported unless its total CPU usage is at least equal to the value set in * {@link #setMinimumTotalCpuUsageMillis}. Filtered thread CPU usage is summed and reported under * one "other threads" thread. This reduces the cardinality of the {@link * #getProcessCpuUsageDiffed()} result. * * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of * WestWorld, because the thresholding should be done after diffing, not before. This is because of * two issues with thresholding before diffing: * * <ul> * <li>We would threshold less and less threads as thread uptime increases. * <li>We would encounter errors as the filtered threads become unfiltered, as the "other threads" * result could have negative diffs, and the newly unfiltered threads would have incorrect * diffs that include CPU usage from when they were filtered. * </ul> * * @hide Only for use within the system server */ @SuppressWarnings("ForLoopReplaceableByForEach") public class KernelCpuThreadReaderDiff { private static final String TAG = "KernelCpuThreadReaderDiff"; /** Thread ID used when reporting CPU used by other threads */ private static final int OTHER_THREADS_ID = -1; /** Thread name used when reporting CPU used by other threads */ private static final String OTHER_THREADS_NAME = "__OTHER_THREADS"; private final KernelCpuThreadReader mReader; /** * CPU usage from the previous call of {@link #getProcessCpuUsageDiffed()}. Null if there was no * previous call, or if the previous call failed * * <p>Maps the thread's identifier to the per-frequency CPU usage for that thread. The * identifier contains the minimal amount of information to identify a thread (see {@link * ThreadKey} for more information), thus reducing memory consumption. */ @Nullable private Map<ThreadKey, int[]> mPreviousCpuUsage; /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ private int mMinimumTotalCpuUsageMillis; @VisibleForTesting public KernelCpuThreadReaderDiff(KernelCpuThreadReader reader, int minimumTotalCpuUsageMillis) { mReader = reader; mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; mPreviousCpuUsage = null; } /** * Returns the difference in CPU usage since the last time this method was called. * * @see KernelCpuThreadReader#getProcessCpuUsage() */ @Nullable public ArrayList<KernelCpuThreadReader.ProcessCpuUsage> getProcessCpuUsageDiffed() { Map<ThreadKey, int[]> newCpuUsage = null; try { // Get the thread CPU usage and index them by ThreadKey final ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages = mReader.getProcessCpuUsage(); newCpuUsage = createCpuUsageMap(processCpuUsages); // If there is no previous CPU usage, return nothing if (mPreviousCpuUsage == null) { return null; } // Do diffing and thresholding for each process for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); changeToDiffs(mPreviousCpuUsage, processCpuUsage); applyThresholding(processCpuUsage); } return processCpuUsages; } finally { // Always update the previous CPU usage. If we haven't got an update, it will be set to // null, so the next call knows there no previous values mPreviousCpuUsage = newCpuUsage; } } /** @see KernelCpuThreadReader#getCpuFrequenciesKhz() */ @Nullable public int[] getCpuFrequenciesKhz() { return mReader.getCpuFrequenciesKhz(); } /** * If a thread has strictly less than {@code minimumTotalCpuUsageMillis} total CPU usage, it * will not be reported */ void setMinimumTotalCpuUsageMillis(int minimumTotalCpuUsageMillis) { if (minimumTotalCpuUsageMillis < 0) { Slog.w(TAG, "Negative minimumTotalCpuUsageMillis: " + minimumTotalCpuUsageMillis); return; } mMinimumTotalCpuUsageMillis = minimumTotalCpuUsageMillis; } /** * Create a map of a thread's identifier to a thread's CPU usage. Used for fast indexing when * calculating diffs */ private static Map<ThreadKey, int[]> createCpuUsageMap( List<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages) { final Map<ThreadKey, int[]> cpuUsageMap = new ArrayMap<>(); for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); for (int j = 0; j < processCpuUsage.threadCpuUsages.size(); j++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = processCpuUsage.threadCpuUsages.get(j); cpuUsageMap.put( new ThreadKey( processCpuUsage.processId, threadCpuUsage.threadId, processCpuUsage.processName, threadCpuUsage.threadName), threadCpuUsage.usageTimesMillis); } } return cpuUsageMap; } /** * Calculate the difference in per-frequency CPU usage for all threads in a process * * @param previousCpuUsage CPU usage from the last call, the base of the diff * @param processCpuUsage CPU usage from the current call, this value is modified to contain the * diffed values */ private static void changeToDiffs( Map<ThreadKey, int[]> previousCpuUsage, KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) { for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = processCpuUsage.threadCpuUsages.get(i); final ThreadKey key = new ThreadKey( processCpuUsage.processId, threadCpuUsage.threadId, processCpuUsage.processName, threadCpuUsage.threadName); int[] previous = previousCpuUsage.get(key); if (previous == null) { // If there's no previous CPU usage, assume that it's zero previous = new int[threadCpuUsage.usageTimesMillis.length]; } threadCpuUsage.usageTimesMillis = cpuTimeDiff(threadCpuUsage.usageTimesMillis, previous); } } /** * Filter out any threads with less than {@link #mMinimumTotalCpuUsageMillis} total CPU usage * * <p>The sum of the CPU usage of filtered threads is added under a single thread, labeled with * {@link #OTHER_THREADS_ID} and {@link #OTHER_THREADS_NAME}. * * @param processCpuUsage CPU usage to apply thresholding to, this value is modified to change * the threads it contains */ private void applyThresholding(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage) { int[] filteredThreadsCpuUsage = null; final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> thresholded = new ArrayList<>(); for (int i = 0; i < processCpuUsage.threadCpuUsages.size(); i++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = processCpuUsage.threadCpuUsages.get(i); if (mMinimumTotalCpuUsageMillis > totalCpuUsage(threadCpuUsage.usageTimesMillis)) { if (filteredThreadsCpuUsage == null) { filteredThreadsCpuUsage = new int[threadCpuUsage.usageTimesMillis.length]; } addToCpuUsage(filteredThreadsCpuUsage, threadCpuUsage.usageTimesMillis); continue; } thresholded.add(threadCpuUsage); } if (filteredThreadsCpuUsage != null) { thresholded.add( new KernelCpuThreadReader.ThreadCpuUsage( OTHER_THREADS_ID, OTHER_THREADS_NAME, filteredThreadsCpuUsage)); } processCpuUsage.threadCpuUsages = thresholded; } /** Get the sum of all CPU usage across all frequencies */ private static int totalCpuUsage(int[] cpuUsage) { int total = 0; for (int i = 0; i < cpuUsage.length; i++) { total += cpuUsage[i]; } return total; } /** Add two CPU frequency usages together */ private static void addToCpuUsage(int[] a, int[] b) { for (int i = 0; i < a.length; i++) { a[i] += b[i]; } } /** Subtract two CPU frequency usages from each other */ private static int[] cpuTimeDiff(int[] a, int[] b) { int[] difference = new int[a.length]; for (int i = 0; i < a.length; i++) { difference[i] = a[i] - b[i]; } return difference; } /** * Identifies a thread * * <p>Only stores the minimum amount of information to identify a thread. This includes the * PID/TID, but as both are recycled as processes/threads end and begin, we also store the hash * of the name of the process/thread. */ private static class ThreadKey { private final int mProcessId; private final int mThreadId; private final int mProcessNameHash; private final int mThreadNameHash; ThreadKey(int processId, int threadId, String processName, String threadName) { this.mProcessId = processId; this.mThreadId = threadId; // Only store the hash to reduce memory consumption this.mProcessNameHash = Objects.hash(processName); this.mThreadNameHash = Objects.hash(threadName); } @Override public int hashCode() { return Objects.hash(mProcessId, mThreadId, mProcessNameHash, mThreadNameHash); } @Override public boolean equals(Object obj) { if (!(obj instanceof ThreadKey)) { return false; } ThreadKey other = (ThreadKey) obj; return mProcessId == other.mProcessId && mThreadId == other.mThreadId && mProcessNameHash == other.mProcessNameHash && mThreadNameHash == other.mThreadNameHash; } } }
core/java/com/android/internal/os/KernelCpuThreadReaderSettingsObserver.java +9 −6 Original line number Diff line number Diff line Loading @@ -67,12 +67,14 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { @Nullable private final KernelCpuThreadReader mKernelCpuThreadReader; @Nullable private final KernelCpuThreadReaderDiff mKernelCpuThreadReaderDiff; /** * @return returns a created {@link KernelCpuThreadReader} that will be modified by any change * in settings, returns null if creation failed */ @Nullable public static KernelCpuThreadReader getSettingsModifiedReader(Context context) { public static KernelCpuThreadReaderDiff getSettingsModifiedReader(Context context) { // Create the observer KernelCpuThreadReaderSettingsObserver settingsObserver = new KernelCpuThreadReaderSettingsObserver(context); Loading @@ -82,7 +84,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { .registerContentObserver( settingsUri, false, settingsObserver, UserHandle.USER_SYSTEM); // Return the observer's reader return settingsObserver.mKernelCpuThreadReader; return settingsObserver.mKernelCpuThreadReaderDiff; } private KernelCpuThreadReaderSettingsObserver(Context context) { Loading @@ -90,9 +92,10 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { mContext = context; mKernelCpuThreadReader = KernelCpuThreadReader.create( NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT), MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT); NUM_BUCKETS_DEFAULT, UidPredicate.fromString(COLLECTED_UIDS_DEFAULT)); mKernelCpuThreadReaderDiff = new KernelCpuThreadReaderDiff( mKernelCpuThreadReader, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT); } @Override Loading Loading @@ -130,7 +133,7 @@ public class KernelCpuThreadReaderSettingsObserver extends ContentObserver { mKernelCpuThreadReader.setNumBuckets( parser.getInt(NUM_BUCKETS_SETTINGS_KEY, NUM_BUCKETS_DEFAULT)); mKernelCpuThreadReader.setUidPredicate(uidPredicate); mKernelCpuThreadReader.setMinimumTotalCpuUsageMillis( mKernelCpuThreadReaderDiff.setMinimumTotalCpuUsageMillis( parser.getInt( MINIMUM_TOTAL_CPU_USAGE_MILLIS_SETTINGS_KEY, MINIMUM_TOTAL_CPU_USAGE_MILLIS_DEFAULT)); Loading
core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderDiffTest.java 0 → 100644 +242 −0 File added.Preview size limit exceeded, changes collapsed. Show changes