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