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; |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 19 | import android.os.SystemClock; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 20 | import android.text.TextUtils; |
| 21 | import android.util.Slog; |
| 22 | import android.util.SparseLongArray; |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 23 | import android.util.TimeUtils; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 24 | |
| 25 | import java.io.BufferedReader; |
| 26 | import java.io.FileReader; |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 27 | import java.io.FileWriter; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 28 | import java.io.IOException; |
| 29 | |
| 30 | /** |
| 31 | * Reads /proc/uid_cputime/show_uid_stat which has the line format: |
| 32 | * |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 33 | * 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] | 34 | * |
| 35 | * This provides the time a UID's processes spent executing in user-space and kernel-space. |
| 36 | * The file contains a monotonically increasing count of time for a single boot. This class |
| 37 | * maintains the previous results of a call to {@link #readDelta} in order to provide a proper |
| 38 | * delta. |
| 39 | */ |
| 40 | public class KernelUidCpuTimeReader { |
| 41 | private static final String TAG = "KernelUidCpuTimeReader"; |
| 42 | private static final String sProcFile = "/proc/uid_cputime/show_uid_stat"; |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 43 | private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range"; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 44 | |
| 45 | /** |
| 46 | * Callback interface for processing each line of the proc file. |
| 47 | */ |
| 48 | public interface Callback { |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 49 | /** |
| 50 | * @param uid UID of the app |
| 51 | * @param userTimeUs time spent executing in user space in microseconds |
| 52 | * @param systemTimeUs time spent executing in kernel space in microseconds |
| 53 | * @param powerMaUs power consumed executing, in milli-ampere microseconds |
| 54 | */ |
| 55 | void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs, long powerMaUs); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 56 | } |
| 57 | |
| 58 | private SparseLongArray mLastUserTimeUs = new SparseLongArray(); |
| 59 | private SparseLongArray mLastSystemTimeUs = new SparseLongArray(); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 60 | private SparseLongArray mLastPowerMaUs = 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. |
| 65 | * @param callback The callback to invoke for each line of the proc file. If null, |
| 66 | * the data is consumed and subsequent calls to readDelta will provide |
| 67 | * a fresh delta. |
| 68 | */ |
| 69 | public void readDelta(@Nullable Callback callback) { |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 70 | long nowUs = SystemClock.elapsedRealtime() * 1000; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 71 | try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) { |
| 72 | TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' '); |
| 73 | String line; |
| 74 | while ((line = reader.readLine()) != null) { |
| 75 | splitter.setString(line); |
| 76 | final String uidStr = splitter.next(); |
| 77 | final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10); |
| 78 | final long userTimeUs = Long.parseLong(splitter.next(), 10); |
| 79 | final long systemTimeUs = Long.parseLong(splitter.next(), 10); |
Adam Lesinski | 43ae441 | 2015-07-01 12:09:45 -0700 | [diff] [blame] | 80 | final long powerMaUs; |
| 81 | if (splitter.hasNext()) { |
| 82 | powerMaUs = Long.parseLong(splitter.next(), 10) / 1000; |
| 83 | } else { |
| 84 | powerMaUs = 0; |
| 85 | } |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 86 | |
| 87 | if (callback != null) { |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 88 | long userTimeDeltaUs = userTimeUs; |
| 89 | long systemTimeDeltaUs = systemTimeUs; |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 90 | long powerDeltaMaUs = powerMaUs; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 91 | int index = mLastUserTimeUs.indexOfKey(uid); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 92 | if (index >= 0) { |
| 93 | userTimeDeltaUs -= mLastUserTimeUs.valueAt(index); |
| 94 | systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 95 | powerDeltaMaUs -= mLastPowerMaUs.valueAt(index); |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 96 | |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 97 | final long timeDiffUs = nowUs - mLastTimeReadUs; |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 98 | if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0 || powerDeltaMaUs < 0 || |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 99 | userTimeDeltaUs > timeDiffUs || systemTimeDeltaUs > timeDiffUs) { |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 100 | StringBuilder sb = new StringBuilder("Malformed cpu data for UID="); |
| 101 | sb.append(uid).append("!\n"); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 102 | sb.append("Time between reads: "); |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 103 | TimeUtils.formatDuration(timeDiffUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 104 | sb.append("\n"); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 105 | sb.append("Previous times: u="); |
| 106 | TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 107 | sb.append(" s="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 108 | TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 109 | sb.append(" p=").append(mLastPowerMaUs.valueAt(index) / 1000); |
| 110 | sb.append("mAms\n"); |
| 111 | |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 112 | sb.append("Current times: u="); |
| 113 | TimeUtils.formatDuration(userTimeUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 114 | sb.append(" s="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 115 | TimeUtils.formatDuration(systemTimeUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 116 | sb.append(" p=").append(powerMaUs / 1000); |
| 117 | sb.append("mAms\n"); |
| 118 | sb.append("Delta: u="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 119 | TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 120 | sb.append(" s="); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 121 | TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 122 | sb.append(" p=").append(powerDeltaMaUs / 1000).append("mAms"); |
Adam Lesinski | 72478f0 | 2015-06-17 15:39:43 -0700 | [diff] [blame] | 123 | Slog.wtf(TAG, sb.toString()); |
| 124 | |
| 125 | userTimeDeltaUs = 0; |
| 126 | systemTimeDeltaUs = 0; |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 127 | powerDeltaMaUs = 0; |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 128 | } |
| 129 | } |
| 130 | |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 131 | if (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0 || powerDeltaMaUs != 0) { |
| 132 | callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs, |
| 133 | powerDeltaMaUs); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 134 | } |
| 135 | } |
| 136 | mLastUserTimeUs.put(uid, userTimeUs); |
| 137 | mLastSystemTimeUs.put(uid, systemTimeUs); |
Adam Lesinski | a7a4ccc | 2015-06-26 17:43:04 -0700 | [diff] [blame] | 138 | mLastPowerMaUs.put(uid, powerMaUs); |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 139 | } |
| 140 | } catch (IOException e) { |
| 141 | Slog.e(TAG, "Failed to read uid_cputime", e); |
| 142 | } |
Adam Lesinski | 7b3c752 | 2015-06-24 11:37:26 -0700 | [diff] [blame] | 143 | mLastTimeReadUs = nowUs; |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 144 | } |
Adam Lesinski | b83ffee | 2015-05-12 14:43:47 -0700 | [diff] [blame] | 145 | |
| 146 | /** |
| 147 | * Removes the UID from the kernel module and from internal accounting data. |
| 148 | * @param uid The UID to remove. |
| 149 | */ |
| 150 | public void removeUid(int uid) { |
| 151 | int index = mLastUserTimeUs.indexOfKey(uid); |
| 152 | if (index >= 0) { |
| 153 | mLastUserTimeUs.removeAt(index); |
| 154 | mLastSystemTimeUs.removeAt(index); |
| 155 | } |
| 156 | |
| 157 | try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) { |
| 158 | writer.write(Integer.toString(uid) + "-" + Integer.toString(uid)); |
| 159 | writer.flush(); |
| 160 | } catch (IOException e) { |
| 161 | Slog.e(TAG, "failed to remove uid from uid_cputime module", e); |
| 162 | } |
| 163 | } |
Adam Lesinski | 06af1fa | 2015-05-05 17:35:35 -0700 | [diff] [blame] | 164 | } |