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