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         */
54        void onUidCpuTime(int uid, long userTimeUs, long systemTimeUs);
55    }
56
57    private SparseLongArray mLastUserTimeUs = new SparseLongArray();
58    private SparseLongArray mLastSystemTimeUs = new SparseLongArray();
59    private long mLastTimeReadUs = 0;
60
61    /**
62     * Reads the proc file, calling into the callback with a delta of time for each UID.
63     * @param callback The callback to invoke for each line of the proc file. If null,
64     *                 the data is consumed and subsequent calls to readDelta will provide
65     *                 a fresh delta.
66     */
67    public void readDelta(@Nullable Callback callback) {
68        long nowUs = SystemClock.elapsedRealtime() * 1000;
69        try (BufferedReader reader = new BufferedReader(new FileReader(sProcFile))) {
70            TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
71            String line;
72            while ((line = reader.readLine()) != null) {
73                splitter.setString(line);
74                final String uidStr = splitter.next();
75                final int uid = Integer.parseInt(uidStr.substring(0, uidStr.length() - 1), 10);
76                final long userTimeUs = Long.parseLong(splitter.next(), 10);
77                final long systemTimeUs = Long.parseLong(splitter.next(), 10);
78
79                // Only report if there is a callback and if this is not the first read.
80                if (callback != null && mLastTimeReadUs != 0) {
81                    long userTimeDeltaUs = userTimeUs;
82                    long systemTimeDeltaUs = systemTimeUs;
83                    int index = mLastUserTimeUs.indexOfKey(uid);
84                    if (index >= 0) {
85                        userTimeDeltaUs -= mLastUserTimeUs.valueAt(index);
86                        systemTimeDeltaUs -= mLastSystemTimeUs.valueAt(index);
87
88                        final long timeDiffUs = nowUs - mLastTimeReadUs;
89                        if (userTimeDeltaUs < 0 || systemTimeDeltaUs < 0) {
90                            StringBuilder sb = new StringBuilder("Malformed cpu data for UID=");
91                            sb.append(uid).append("!\n");
92                            sb.append("Time between reads: ");
93                            TimeUtils.formatDuration(timeDiffUs / 1000, sb);
94                            sb.append("\n");
95                            sb.append("Previous times: u=");
96                            TimeUtils.formatDuration(mLastUserTimeUs.valueAt(index) / 1000, sb);
97                            sb.append(" s=");
98                            TimeUtils.formatDuration(mLastSystemTimeUs.valueAt(index) / 1000, sb);
99
100                            sb.append("\nCurrent times: u=");
101                            TimeUtils.formatDuration(userTimeUs / 1000, sb);
102                            sb.append(" s=");
103                            TimeUtils.formatDuration(systemTimeUs / 1000, sb);
104                            sb.append("\nDelta: u=");
105                            TimeUtils.formatDuration(userTimeDeltaUs / 1000, sb);
106                            sb.append(" s=");
107                            TimeUtils.formatDuration(systemTimeDeltaUs / 1000, sb);
108                            Slog.e(TAG, sb.toString());
109
110                            userTimeDeltaUs = 0;
111                            systemTimeDeltaUs = 0;
112                        }
113                    }
114
115                    if (userTimeDeltaUs != 0 || systemTimeDeltaUs != 0) {
116                        callback.onUidCpuTime(uid, userTimeDeltaUs, systemTimeDeltaUs);
117                    }
118                }
119                mLastUserTimeUs.put(uid, userTimeUs);
120                mLastSystemTimeUs.put(uid, systemTimeUs);
121            }
122        } catch (IOException e) {
123            Slog.e(TAG, "Failed to read uid_cputime: " + e.getMessage());
124        }
125        mLastTimeReadUs = nowUs;
126    }
127
128    /**
129     * Removes the UID from the kernel module and from internal accounting data.
130     * @param uid The UID to remove.
131     */
132    public void removeUid(int uid) {
133        int index = mLastUserTimeUs.indexOfKey(uid);
134        if (index >= 0) {
135            mLastUserTimeUs.removeAt(index);
136            mLastSystemTimeUs.removeAt(index);
137        }
138
139        try (FileWriter writer = new FileWriter(sRemoveUidProcFile)) {
140            writer.write(Integer.toString(uid) + "-" + Integer.toString(uid));
141            writer.flush();
142        } catch (IOException e) {
143            Slog.e(TAG, "failed to remove uid from uid_cputime module", e);
144        }
145    }
146}
147