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