1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.os; 18 19 import android.annotation.Nullable; 20 import android.util.Slog; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import java.io.IOException; 25 import java.util.Arrays; 26 27 /** 28 * Iterates over all threads owned by a given process, and return the CPU usage for 29 * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU 30 * usage is collected using {@link ProcTimeInStateReader}. 31 */ 32 public class KernelSingleProcessCpuThreadReader { 33 34 private static final String TAG = "KernelSingleProcCpuThreadRdr"; 35 36 private static final boolean DEBUG = false; 37 38 private final int mPid; 39 40 private final CpuTimeInStateReader mCpuTimeInStateReader; 41 42 private int[] mSelectedThreadNativeTids = new int[0]; // Sorted 43 44 /** 45 * Count of frequencies read from the {@code time_in_state} file. 46 */ 47 private int mFrequencyCount; 48 49 private boolean mIsTracking; 50 51 /** 52 * A CPU time-in-state provider for testing. Imitates the behavior of the corresponding 53 * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c 54 */ 55 @VisibleForTesting 56 public interface CpuTimeInStateReader { 57 /** 58 * Returns the overall number of cluster-frequency combinations. 59 */ getCpuFrequencyCount()60 int getCpuFrequencyCount(); 61 62 /** 63 * Returns true to indicate success. 64 * 65 * Called from native. 66 */ startTrackingProcessCpuTimes(int tgid)67 boolean startTrackingProcessCpuTimes(int tgid); 68 69 /** 70 * Returns true to indicate success. 71 * 72 * Called from native. 73 */ startAggregatingTaskCpuTimes(int pid, int aggregationKey)74 boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey); 75 76 /** 77 * Must return an array of strings formatted like this: 78 * "aggKey:t0_0 t0_1...:t1_0 t1_1..." 79 * Times should be provided in nanoseconds. 80 * 81 * Called from native. 82 */ getAggregatedTaskCpuFreqTimes(int pid)83 String[] getAggregatedTaskCpuFreqTimes(int pid); 84 } 85 86 /** 87 * Create with a path where `proc` is mounted. Used primarily for testing 88 * 89 * @param pid PID of the process whose threads are to be read. 90 */ 91 @VisibleForTesting KernelSingleProcessCpuThreadReader(int pid, @Nullable CpuTimeInStateReader cpuTimeInStateReader)92 public KernelSingleProcessCpuThreadReader(int pid, 93 @Nullable CpuTimeInStateReader cpuTimeInStateReader) throws IOException { 94 mPid = pid; 95 mCpuTimeInStateReader = cpuTimeInStateReader; 96 } 97 98 /** 99 * Create the reader and handle exceptions during creation 100 * 101 * @return the reader, null if an exception was thrown during creation 102 */ 103 @Nullable create(int pid)104 public static KernelSingleProcessCpuThreadReader create(int pid) { 105 try { 106 return new KernelSingleProcessCpuThreadReader(pid, null); 107 } catch (IOException e) { 108 Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e); 109 return null; 110 } 111 } 112 113 /** 114 * Starts tracking aggregated CPU time-in-state of all threads of the process with the PID 115 * supplied in the constructor. 116 */ startTrackingThreadCpuTimes()117 public void startTrackingThreadCpuTimes() { 118 if (!mIsTracking) { 119 if (!startTrackingProcessCpuTimes(mPid, mCpuTimeInStateReader)) { 120 Slog.e(TAG, "Failed to start tracking process CPU times for " + mPid); 121 } 122 if (mSelectedThreadNativeTids.length > 0) { 123 if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, 124 mCpuTimeInStateReader)) { 125 Slog.e(TAG, "Failed to start tracking aggregated thread CPU times for " 126 + Arrays.toString(mSelectedThreadNativeTids)); 127 } 128 } 129 mIsTracking = true; 130 } 131 } 132 133 /** 134 * @param nativeTids an array of native Thread IDs whose CPU times should 135 * be aggregated as a group. This is expected to be a subset 136 * of all thread IDs owned by the process. 137 */ setSelectedThreadIds(int[] nativeTids)138 public void setSelectedThreadIds(int[] nativeTids) { 139 mSelectedThreadNativeTids = nativeTids.clone(); 140 if (mIsTracking) { 141 startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader); 142 } 143 } 144 145 /** 146 * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}. 147 */ getCpuFrequencyCount()148 public int getCpuFrequencyCount() { 149 if (mFrequencyCount == 0) { 150 mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader); 151 } 152 return mFrequencyCount; 153 } 154 155 /** 156 * Get the total CPU usage of the process with the PID specified in the 157 * constructor. The CPU usage time is aggregated across all threads and may 158 * exceed the time the entire process has been running. 159 */ 160 @Nullable getProcessCpuUsage()161 public ProcessCpuUsage getProcessCpuUsage() { 162 if (DEBUG) { 163 Slog.d(TAG, "Reading CPU thread usages for PID " + mPid); 164 } 165 166 ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount()); 167 168 boolean result = readProcessCpuUsage(mPid, 169 processCpuUsage.threadCpuTimesMillis, 170 processCpuUsage.selectedThreadCpuTimesMillis, 171 mCpuTimeInStateReader); 172 if (!result) { 173 return null; 174 } 175 176 if (DEBUG) { 177 Slog.d(TAG, "threadCpuTimesMillis = " 178 + Arrays.toString(processCpuUsage.threadCpuTimesMillis)); 179 Slog.d(TAG, "selectedThreadCpuTimesMillis = " 180 + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis)); 181 } 182 183 return processCpuUsage; 184 } 185 186 /** CPU usage of a process, all of its threads and a selected subset of its threads */ 187 public static class ProcessCpuUsage { 188 public long[] threadCpuTimesMillis; 189 public long[] selectedThreadCpuTimesMillis; 190 ProcessCpuUsage(int cpuFrequencyCount)191 public ProcessCpuUsage(int cpuFrequencyCount) { 192 threadCpuTimesMillis = new long[cpuFrequencyCount]; 193 selectedThreadCpuTimesMillis = new long[cpuFrequencyCount]; 194 } 195 } 196 getCpuFrequencyCount(CpuTimeInStateReader reader)197 private native int getCpuFrequencyCount(CpuTimeInStateReader reader); 198 startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader)199 private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader); 200 startAggregatingThreadCpuTimes(int[] selectedThreadIds, CpuTimeInStateReader reader)201 private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds, 202 CpuTimeInStateReader reader); 203 readProcessCpuUsage(int pid, long[] threadCpuTimesMillis, long[] selectedThreadCpuTimesMillis, CpuTimeInStateReader reader)204 private native boolean readProcessCpuUsage(int pid, 205 long[] threadCpuTimesMillis, 206 long[] selectedThreadCpuTimesMillis, 207 CpuTimeInStateReader reader); 208 } 209