• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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