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