1 /*
2  * Copyright (C) 2018 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.os.Process;
21 
22 import com.android.internal.util.ArrayUtils;
23 
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Path;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 
32 /**
33  * Reads and parses {@code time_in_state} files in the {@code proc} filesystem.
34  *
35  * Every line in a {@code time_in_state} file contains two numbers, separated by a single space
36  * character. The first number is the frequency of the CPU used in kilohertz. The second number is
37  * the time spent in this frequency. In the {@code time_in_state} file, this is given in 10s of
38  * milliseconds, but this class returns in milliseconds. This can be per user, process, or thread
39  * depending on which {@code time_in_state} file is used.
40  *
41  * For example, a {@code time_in_state} file would look like this:
42  * <pre>
43  *   300000 3
44  *   364800 0
45  *   ...
46  *   1824000 0
47  *   1900800 1
48  * </pre>
49  *
50  * This file would indicate that the CPU has spent 30 milliseconds at frequency 300,000KHz (300Mhz)
51  * and 10 milliseconds at frequency 1,900,800KHz (1.9GHz).
52  *
53  * <p>This class will also read {@code time_in_state} files with headers, such as:
54  * <pre>
55  *   cpu0
56  *   300000 3
57  *   364800 0
58  *   ...
59  *   cpu4
60  *   300000 1
61  *   364800 4
62  * </pre>
63  */
64 public class ProcTimeInStateReader {
65     private static final String TAG = "ProcTimeInStateReader";
66 
67     /**
68      * The format of a single line of the {@code time_in_state} file that exports the frequency
69      * values
70      */
71     private static final List<Integer> TIME_IN_STATE_LINE_FREQUENCY_FORMAT = Arrays.asList(
72             Process.PROC_OUT_LONG | Process.PROC_SPACE_TERM,
73             Process.PROC_NEWLINE_TERM
74     );
75 
76     /**
77      * The format of a single line of the {@code time_in_state} file that exports the time values
78      */
79     private static final List<Integer> TIME_IN_STATE_LINE_TIME_FORMAT = Arrays.asList(
80             Process.PROC_SPACE_TERM,
81             Process.PROC_OUT_LONG | Process.PROC_NEWLINE_TERM
82     );
83 
84     /**
85      * The format of a header line of the {@code time_in_state} file
86      */
87     private static final List<Integer> TIME_IN_STATE_HEADER_LINE_FORMAT =
88             Collections.singletonList(Process.PROC_NEWLINE_TERM);
89 
90     /**
91      * The format of the {@code time_in_state} file to extract times, defined using {@link
92      * Process}'s {@code PROC_OUT_LONG} and related variables
93      */
94     private int[] mTimeInStateTimeFormat;
95 
96     /**
97      * The frequencies reported in each {@code time_in_state} file
98      *
99      * Defined on first successful read of {@code time_in_state} file.
100      */
101     private long[] mFrequenciesKhz;
102 
103     /**
104      * @param initialTimeInStateFile the file to base the format of the frequency files on, and to
105      * read frequencies from. Expected to be in the same format as all other {@code time_in_state}
106      * files, and contain the same frequencies.
107      * @throws IOException if reading the initial {@code time_in_state} file failed
108      */
ProcTimeInStateReader(Path initialTimeInStateFile)109     public ProcTimeInStateReader(Path initialTimeInStateFile) throws IOException {
110         initializeTimeInStateFormat(initialTimeInStateFile);
111     }
112 
113     /**
114      * Read the CPU usages from a file
115      *
116      * @param timeInStatePath path where the CPU usages are read from
117      * @return list of CPU usage times from the file. These correspond to the CPU frequencies given
118      * by {@link ProcTimeInStateReader#getFrequenciesKhz}
119      */
120     @Nullable
getUsageTimesMillis(final Path timeInStatePath)121     public long[] getUsageTimesMillis(final Path timeInStatePath) {
122         // Read in the time_in_state file
123         final long[] readLongs = new long[mFrequenciesKhz.length];
124         final boolean readSuccess = Process.readProcFile(
125                 timeInStatePath.toString(),
126                 mTimeInStateTimeFormat,
127                 null, readLongs, null);
128         if (!readSuccess) {
129             return null;
130         }
131         // Usage time is given in 10ms, so convert to ms
132         for (int i = 0; i < readLongs.length; i++) {
133             readLongs[i] *= 10;
134         }
135         return readLongs;
136     }
137 
138     /**
139      * Get the frequencies found in each {@code time_in_state} file
140      *
141      * @return list of CPU frequencies. These correspond to the CPU times given by {@link
142      * ProcTimeInStateReader#getUsageTimesMillis(Path)}()}.
143      */
144     @Nullable
getFrequenciesKhz()145     public long[] getFrequenciesKhz() {
146         return mFrequenciesKhz;
147     }
148 
149     /**
150      * Set the {@link #mTimeInStateTimeFormat} and {@link #mFrequenciesKhz} variables based on the
151      * an input file. If the file is empty, these variables aren't set
152      *
153      * This needs to be run once on the first invocation of {@link #getUsageTimesMillis(Path)}. This
154      * is because we need to know how many frequencies are available in order to parse time
155      * {@code time_in_state} file using {@link Process#readProcFile}, which only accepts
156      * fixed-length formats. Also, as the frequencies do not change between {@code time_in_state}
157      * files, we read and store them here.
158      *
159      * @param timeInStatePath the input file to base the format off of
160      */
initializeTimeInStateFormat(final Path timeInStatePath)161     private void initializeTimeInStateFormat(final Path timeInStatePath) throws IOException {
162         // Read the bytes of the `time_in_state` file
163         byte[] timeInStateBytes = Files.readAllBytes(timeInStatePath);
164 
165         // Iterate over the lines of the time_in_state file, for each one adding a line to the
166         // formats. These formats are used to extract either the frequencies or the times from a
167         // time_in_state file
168         // Also check if each line is a header, and handle this in the created format arrays
169         ArrayList<Integer> timeInStateFrequencyFormat = new ArrayList<>();
170         ArrayList<Integer> timeInStateTimeFormat = new ArrayList<>();
171         int numFrequencies = 0;
172         for (int i = 0; i < timeInStateBytes.length; i++) {
173             // If the first character of the line is not a digit, we treat it as a header
174             if (!Character.isDigit(timeInStateBytes[i])) {
175                 timeInStateFrequencyFormat.addAll(TIME_IN_STATE_HEADER_LINE_FORMAT);
176                 timeInStateTimeFormat.addAll(TIME_IN_STATE_HEADER_LINE_FORMAT);
177             } else {
178                 timeInStateFrequencyFormat.addAll(TIME_IN_STATE_LINE_FREQUENCY_FORMAT);
179                 timeInStateTimeFormat.addAll(TIME_IN_STATE_LINE_TIME_FORMAT);
180                 numFrequencies++;
181             }
182             // Go to the next line
183             while (i < timeInStateBytes.length && timeInStateBytes[i] != '\n') {
184                 i++;
185             }
186         }
187 
188         if (numFrequencies == 0) {
189             throw new IOException("Empty time_in_state file");
190         }
191 
192         // Read the frequencies from the `time_in_state` file and store them, as they will be the
193         // same for every `time_in_state` file
194         final long[] readLongs = new long[numFrequencies];
195         final boolean readSuccess = Process.parseProcLine(
196                 timeInStateBytes, 0, timeInStateBytes.length,
197                 ArrayUtils.convertToIntArray(timeInStateFrequencyFormat), null, readLongs, null);
198         if (!readSuccess) {
199             throw new IOException("Failed to parse time_in_state file");
200         }
201 
202         mTimeInStateTimeFormat = ArrayUtils.convertToIntArray(timeInStateTimeFormat);
203         mFrequenciesKhz = readLongs;
204     }
205 }
206