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 */
16package com.android.internal.os;
17
18import android.annotation.Nullable;
19import android.os.SystemClock;
20import android.text.TextUtils;
21import android.util.Slog;
22import android.util.SparseLongArray;
23import android.util.TimeUtils;
24
25import java.io.BufferedReader;
26import java.io.FileReader;
27import java.io.FileWriter;
28import java.io.IOException;
29
30/**
31 * Reads /proc/uid_cputime/show_uid_stat which has the line format:
32 *
33 * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
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 */
40public class KernelUidCpuTimeReader {
41    private static final String TAG = "KernelUidCpuTimeReader";
42    private static final String sProcFile = "/proc/uid_cputime/show_uid_stat";
43    private static final String sRemoveUidProcFile = "/proc/uid_cputime/remove_uid_range";
44
45    /**
46     * Callback interface for processing each line of the proc file.
47     */
48    public interface Callback {
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);
56    }
57
58    private SparseLongArray mLastUserTimeUs = new SparseLongArray();
59    private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
60    private SparseLongArray mLastPowerMaUs = new SparseLongArray();
61    private long mLastTimeReadUs = 0;
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) {
70        long nowUs = SystemClock.elapsedRealtime() * 1000;
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);
80                final long powerMaUs;
81                if (splitter.hasNext()) {
82                    powerMaUs = Long.parseLong(splitter.next(), 10) / 1000;
83                } else {
84                    powerMaUs = 0;
85                }
86
87                // Only report if there is a callback and if this is not the first read.
88                if (callback != null && mLastTimeReadUs != 0) {
89                    long userTimeDeltaUs = userTimeUs;
90                    long systemTimeDeltaUs = systemTimeUs;
91                    long powerDeltaMaUs = powerMaUs;
92                    int index = mLastUserTimeUs.indexOfKey(uid);
93                    if (index >= 0) {
94                        userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
95                        systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
96                        powerDeltaMaUs -= mLastPowerMaUs.valueAt(index);
97
98                        final long timeDiffUs = nowUs - mLastTimeReadUs;
99                        if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0 || powerDeltaMaUs < 0) {
100                            StringBuilder sb = new StringBuilder("Malformed cpu data for UID=");
101                            sb.append(uid).append("!\n");
102                            sb.append("Time between reads: ");
103                            TimeUtils.formatDuration(timeDiffUs / 1000, sb);
104                            sb.append("\n");
105                            sb.append("Previous times: u=");
106                            TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb);
107                            sb.append(" s=");
108                            TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb);
109                            sb.append(" p=").append(mLastPowerMaUs.valueAt(index) / 1000);
110                            sb.append("mAms\n");
111
112                            sb.append("Current times: u=");
113                            TimeUtils.formatDuration(userTimeUs / 1000, sb);
114                            sb.append(" s=");
115                            TimeUtils.formatDuration(systemTimeUs / 1000, sb);
116                            sb.append(" p=").append(powerMaUs / 1000);
117                            sb.append("mAms\n");
118                            sb.append("Delta: u=");
119                            TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb);
120                            sb.append(" s=");
121                            TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
122                            sb.append(" p=").append(powerDeltaMaUs / 1000).append("mAms");
123                            Slog.wtf(TAG, sb.toString());
124
125                            userTimeDeltaUs = 0;
126                            systemTimeDeltaUs = 0;
127                            powerDeltaMaUs = 0;
128                        }
129                    }
130
131                    if (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0 || powerDeltaMaUs != 0) {
132                        callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs,
133                                powerDeltaMaUs);
134                    }
135                }
136                mLastUserTimeUs.put(uid, userTimeUs);
137                mLastSystemTimeUs.put(uid, systemTimeUs);
138                mLastPowerMaUs.put(uid, powerMaUs);
139            }
140        } catch (IOException e) {
141            Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
142        }
143        mLastTimeReadUs = nowUs;
144    }
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            mLastPowerMaUs.removeAt(index);
156        }
157
158        try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
159            writer.write(Integer.toString(uid) + "-" + Integer.toString(uid));
160            writer.flush();
161        } catch (IOException e) {
162            Slog.e(TAG, "failed to remove uid from uid_cputime module", e);
163        }
164    }
165}
166