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