Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 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 | package com.android.internal.os; |
| 17 | |
| 18 | import android.annotation.Nullable; |
Jeff Sharkey | 8918298 | 2017-11-01 19:02:56 -0600 | [diff] [blame] | 19 | import android.os.StrictMode; |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 20 | import android.os.SystemClock; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 21 | import android.text.TextUtils; |
| 22 | import android.util.Slog; |
| 23 | import android.util.SparseLongArray; |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 24 | import android.util.TimeUtils; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 25 | |
| 26 | import java.io.BufferedReader; |
| 27 | import java.io.FileReader; |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 28 | import java.io.FileWriter; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 29 | import java.io.IOException; |
| 30 | |
| 31 | /** |
| 32 | * Reads /proc/uid_cputime/show_uid_stat which has the line format: |
| 33 | * |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 34 | * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 35 | * |
| 36 | * This provides the time a UID's processes spent executing in user-space and kernel-space. |
| 37 | * The file contains a monotonically increasing count of time for a single boot. This class |
| 38 | * maintains the previous results of a call to {@link #readDelta} in order to provide a proper |
| 39 | * delta. |
| 40 | */ |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 41 | public class KernelUidCpuTimeReader extends |
| 42 | KernelUidCpuTimeReaderBase<KernelUidCpuTimeReader.Callback> { |
| 43 | private static final String TAG = KernelUidCpuTimeReader.class.getSimpleName(); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 44 | private static final String sProcFile = "/proc/uid_cputime/show_uid_stat"; |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 45 | private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range"; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 46 | |
| 47 | /** |
| 48 | * Callback interface for processing each line of the proc file. |
| 49 | */ |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 50 | public interface Callback extends KernelUidCpuTimeReaderBase.Callback { |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 51 | /** |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 52 | * @param uid UID of the app |
| 53 | * @param userTimeUs time spent executing in user space in microseconds |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 54 | * @param systemTimeUs time spent executing in kernel space in microseconds |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 55 | */ |
Adam Lesinski | d4abd1e | 2017-04-12 11:29:13 -0700 | [diff] [blame] | 56 | void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 57 | } |
| 58 | |
| 59 | private SparseLongArray mLastUserTimeUs = new SparseLongArray(); |
| 60 | private SparseLongArray mLastSystemTimeUs = new SparseLongArray(); |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 61 | private long mLastTimeReadUs = 0; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 62 | |
| 63 | /** |
| 64 | * Reads the proc file, calling into the callback with a delta of time for each UID. |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 65 | * |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 66 | * @param callback The callback to invoke for each line of the proc file. If null, |
| 67 | * the data is consumed and subsequent calls to readDelta will provide |
| 68 | * a fresh delta. |
| 69 | */ |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 70 | @Override |
| 71 | protected void readDeltaImpl(@Nullable Callback callback) { |
Jeff Sharkey | 8918298 | 2017-11-01 19:02:56 -0600 | [diff] [blame] | 72 | final int oldMask = StrictMode.allowThreadDiskReadsMask(); |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 73 | long nowUs = SystemClock.elapsedRealtime() * 1000; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 74 | try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) { |
| 75 | TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); |
| 76 | String line; |
| 77 | while ((line = reader.readLine()) != null) { |
| 78 | splitter.setString(line); |
| 79 | final String uidStr = splitter.next(); |
| 80 | final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10); |
| 81 | final long userTimeUs = Long.parseLong(splitter.next(), 10); |
| 82 | final long systemTimeUs = Long.parseLong(splitter.next(), 10); |
| 83 | |
Sudheer Shanka | 318a67d | 2018-02-10 20:05:22 -0800 | [diff] [blame] | 84 | boolean notifyCallback = false; |
| 85 | long userTimeDeltaUs = userTimeUs; |
| 86 | long systemTimeDeltaUs = systemTimeUs; |
Adam Lesinski | e7f4f02 | 2016-06-08 14:19:09 -0700 | [diff] [blame] | 87 | // Only report if there is a callback and if this is not the first read. |
| 88 | if (callback != null && mLastTimeReadUs != 0) { |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 89 | int index = mLastUserTimeUs.indexOfKey(uid); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 90 | if (index >= 0) { |
| 91 | userTimeDeltaUs -= mLastUserTimeUs.valueAt(index); |
| 92 | systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index); |
| 93 | |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 94 | final long timeDiffUs = nowUs - mLastTimeReadUs; |
Adam Lesinski | d4abd1e | 2017-04-12 11:29:13 -0700 | [diff] [blame] | 95 | if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) { |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 96 | StringBuilder sb = new StringBuilder("Malformed cpu data for UID="); |
| 97 | sb.append(uid).append("!\n"); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 98 | sb.append("Time between reads: "); |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 99 | TimeUtils.formatDuration(timeDiffUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 100 | sb.append("\n"); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 101 | sb.append("Previous times: u="); |
| 102 | TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 103 | sb.append(" s="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 104 | TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 105 | |
Adam Lesinski | d4abd1e | 2017-04-12 11:29:13 -0700 | [diff] [blame] | 106 | sb.append("\nCurrent times: u="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 107 | TimeUtils.formatDuration(userTimeUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 108 | sb.append(" s="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 109 | TimeUtils.formatDuration(systemTimeUs / 1000, sb); |
Adam Lesinski | d4abd1e | 2017-04-12 11:29:13 -0700 | [diff] [blame] | 110 | sb.append("\nDelta: u="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 111 | TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 112 | sb.append(" s="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 113 | TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb); |
Adam Lesinski | 1964739 | 2016-07-27 16:11:27 -0700 | [diff] [blame] | 114 | Slog.e(TAG, sb.toString()); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 115 | |
| 116 | userTimeDeltaUs = 0; |
| 117 | systemTimeDeltaUs = 0; |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 118 | } |
| 119 | } |
| 120 | |
Sudheer Shanka | 318a67d | 2018-02-10 20:05:22 -0800 | [diff] [blame] | 121 | notifyCallback = (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 122 | } |
| 123 | mLastUserTimeUs.put(uid, userTimeUs); |
| 124 | mLastSystemTimeUs.put(uid, systemTimeUs); |
Sudheer Shanka | 318a67d | 2018-02-10 20:05:22 -0800 | [diff] [blame] | 125 | if (notifyCallback) { |
| 126 | callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs); |
| 127 | } |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 128 | } |
| 129 | } catch (IOException e) { |
Adam Lesinski | 8057133 | 2015-09-18 14:56:34 -0700 | [diff] [blame] | 130 | Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage()); |
Jeff Sharkey | 8918298 | 2017-11-01 19:02:56 -0600 | [diff] [blame] | 131 | } finally { |
| 132 | StrictMode.setThreadPolicyMask(oldMask); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 133 | } |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 134 | mLastTimeReadUs = nowUs; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 135 | } |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 136 | |
| 137 | /** |
Chenjie Yu | ec67661 | 2018-03-07 09:19:17 -0800 | [diff] [blame] | 138 | * Reads the proc file, calling into the callback with raw absolute value of time for each UID. |
| 139 | * @param callback The callback to invoke for each line of the proc file. |
| 140 | */ |
| 141 | public void readAbsolute(Callback callback) { |
| 142 | final int oldMask = StrictMode.allowThreadDiskReadsMask(); |
| 143 | try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) { |
| 144 | TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); |
| 145 | String line; |
| 146 | while ((line = reader.readLine()) != null) { |
| 147 | splitter.setString(line); |
| 148 | final String uidStr = splitter.next(); |
| 149 | final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10); |
| 150 | final long userTimeUs = Long.parseLong(splitter.next(), 10); |
| 151 | final long systemTimeUs = Long.parseLong(splitter.next(), 10); |
| 152 | callback.onUidCpuTime(uid, userTimeUs, systemTimeUs); |
| 153 | } |
| 154 | } catch (IOException e) { |
| 155 | Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage()); |
| 156 | } finally { |
| 157 | StrictMode.setThreadPolicyMask(oldMask); |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | /** |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 162 | * Removes the UID from the kernel module and from internal accounting data. Only |
| 163 | * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is |
| 164 | * visible system wide. |
| 165 | * |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 166 | * @param uid The UID to remove. |
| 167 | */ |
| 168 | public void removeUid(int uid) { |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 169 | final int index = mLastSystemTimeUs.indexOfKey(uid); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 170 | if (index >= 0) { |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 171 | mLastSystemTimeUs.removeAt(index); |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 172 | mLastUserTimeUs.removeAt(index); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 173 | } |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 174 | removeUidsFromKernelModule(uid, uid); |
| 175 | } |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 176 | |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 177 | /** |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 178 | * Removes UIDs in a given range from the kernel module and internal accounting data. Only |
| 179 | * {@link BatteryStatsImpl} and its child processes should call this, as the change on Kernel is |
| 180 | * visible system wide. |
| 181 | * |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 182 | * @param startUid the first uid to remove |
Mike Ma | 69d8b3e | 2018-02-23 14:51:26 -0800 | [diff] [blame] | 183 | * @param endUid the last uid to remove |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 184 | */ |
| 185 | public void removeUidsInRange(int startUid, int endUid) { |
| 186 | if (endUid < startUid) { |
| 187 | return; |
| 188 | } |
| 189 | mLastSystemTimeUs.put(startUid, 0); |
| 190 | mLastUserTimeUs.put(startUid, 0); |
| 191 | mLastSystemTimeUs.put(endUid, 0); |
| 192 | mLastUserTimeUs.put(endUid, 0); |
| 193 | final int startIndex = mLastSystemTimeUs.indexOfKey(startUid); |
| 194 | final int endIndex = mLastSystemTimeUs.indexOfKey(endUid); |
| 195 | mLastSystemTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1); |
| 196 | mLastUserTimeUs.removeAtRange(startIndex, endIndex - startIndex + 1); |
| 197 | removeUidsFromKernelModule(startUid, endUid); |
| 198 | } |
| 199 | |
| 200 | private void removeUidsFromKernelModule(int startUid, int endUid) { |
| 201 | Slog.d(TAG, "Removing uids " + startUid + "-" + endUid); |
Jeff Sharkey | 8918298 | 2017-11-01 19:02:56 -0600 | [diff] [blame] | 202 | final int oldMask = StrictMode.allowThreadDiskWritesMask(); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 203 | try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) { |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 204 | writer.write(startUid + "-" + endUid); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 205 | writer.flush(); |
| 206 | } catch (IOException e) { |
Suprabh Shukla | e6e723d | 2017-06-14 16:14:43 -0700 | [diff] [blame] | 207 | Slog.e(TAG, "failed to remove uids " + startUid + " - " + endUid |
| 208 | + " from uid_cputime module", e); |
Jeff Sharkey | 8918298 | 2017-11-01 19:02:56 -0600 | [diff] [blame] | 209 | } finally { |
| 210 | StrictMode.setThreadPolicyMask(oldMask); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 211 | } |
| 212 | } |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 213 | } |