All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.internal.os.KernelCpuThreadReaderDiff Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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()}.
 *
 * 

Some notes on the diff calculation: * *

    *
  • 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. *
  • The first call of {@link #getProcessCpuUsageDiffed()} will return no processes ("first * call" is the first call in the lifetime of a {@link KernelCpuThreadReaderDiff} object). *
  • 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. *
  • 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. *
  • 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. *
* *

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. * *

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: * *

    *
  • We would threshold less and less threads as thread uptime increases. *
  • 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. *
* * @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 * *

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 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 getProcessCpuUsageDiffed() { Map newCpuUsage = null; try { // Get the thread CPU usage and index them by ThreadKey final ArrayList 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 createCpuUsageMap( List processCpuUsages) { final Map 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 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 * *

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 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 * *

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; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy