KernelSingleUidTimeReader.java revision 5c19b897ddb89481c5981195d2470f6ce5de4b1c
1/*
2 * Copyright (C) 2017 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 static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
19
20import android.annotation.NonNull;
21import android.util.Slog;
22import android.util.SparseArray;
23
24import com.android.internal.annotations.GuardedBy;
25import com.android.internal.annotations.VisibleForTesting;
26
27import java.io.IOException;
28import java.nio.ByteBuffer;
29import java.nio.ByteOrder;
30import java.nio.file.Files;
31import java.nio.file.Paths;
32import java.util.Arrays;
33
34@VisibleForTesting(visibility = PACKAGE)
35public class KernelSingleUidTimeReader {
36    private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
37    private final boolean DBG = false;
38
39    private final String PROC_FILE_DIR = "/proc/uid/";
40    private final String PROC_FILE_NAME = "/time_in_state";
41
42    @VisibleForTesting
43    public static final int TOTAL_READ_ERROR_COUNT = 5;
44
45    @GuardedBy("this")
46    private final int mCpuFreqsCount;
47
48    @GuardedBy("this")
49    private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
50
51    @GuardedBy("this")
52    private int mReadErrorCounter;
53    @GuardedBy("this")
54    private boolean mSingleUidCpuTimesAvailable = true;
55    @GuardedBy("this")
56    private boolean mHasStaleData;
57
58    private final Injector mInjector;
59
60    KernelSingleUidTimeReader(int cpuFreqsCount) {
61        this(cpuFreqsCount, new Injector());
62    }
63
64    public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
65        mInjector = injector;
66        mCpuFreqsCount = cpuFreqsCount;
67        if (mCpuFreqsCount == 0) {
68            mSingleUidCpuTimesAvailable = false;
69        }
70    }
71
72    public boolean singleUidCpuTimesAvailable() {
73        return mSingleUidCpuTimesAvailable;
74    }
75
76    public long[] readDeltaMs(int uid) {
77        synchronized (this) {
78            if (!mSingleUidCpuTimesAvailable) {
79                return null;
80            }
81            // Read total cpu times from the proc file.
82            final String procFile = new StringBuilder(PROC_FILE_DIR)
83                    .append(uid)
84                    .append(PROC_FILE_NAME).toString();
85            final long[] cpuTimesMs = new long[mCpuFreqsCount];
86            try {
87                final byte[] data = mInjector.readData(procFile);
88                final ByteBuffer buffer = ByteBuffer.wrap(data);
89                buffer.order(ByteOrder.nativeOrder());
90                for (int i = 0; i < mCpuFreqsCount; ++i) {
91                    // Times read will be in units of 10ms
92                    cpuTimesMs[i] = buffer.getLong() * 10;
93                }
94            } catch (Exception e) {
95                if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
96                    mSingleUidCpuTimesAvailable = false;
97                }
98                if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
99                return null;
100            }
101
102            return computeDelta(uid, cpuTimesMs);
103        }
104    }
105
106    /**
107     * Compute and return cpu times delta of an uid using previously read cpu times and
108     * {@param latestCpuTimesMs}.
109     *
110     * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
111     */
112    public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
113        synchronized (this) {
114            if (!mSingleUidCpuTimesAvailable) {
115                return null;
116            }
117            // Subtract the last read cpu times to get deltas.
118            final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
119            final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
120            if (deltaTimesMs == null) {
121                if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
122                        + "; last=" + Arrays.toString(lastCpuTimesMs)
123                        + "; latest=" + Arrays.toString(latestCpuTimesMs));
124                return null;
125            }
126            // If all elements are zero, return null to avoid unnecessary work on the caller side.
127            boolean hasNonZero = false;
128            for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
129                if (deltaTimesMs[i] > 0) {
130                    hasNonZero = true;
131                    break;
132                }
133            }
134            if (hasNonZero) {
135                mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
136                return deltaTimesMs;
137            } else {
138                return null;
139            }
140        }
141    }
142
143    /**
144     * Returns null if the latest cpu times are not valid**, otherwise delta of
145     * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
146     *
147     * **latest cpu times are considered valid if all the cpu times are +ve and
148     * greater than or equal to previously read cpu times.
149     */
150    @GuardedBy("this")
151    @VisibleForTesting(visibility = PACKAGE)
152    public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
153        for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
154            if (latestCpuTimesMs[i] < 0) {
155                return null;
156            }
157        }
158        if (lastCpuTimesMs == null) {
159            return latestCpuTimesMs;
160        }
161        final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
162        for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
163            deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
164            if (deltaTimesMs[i] < 0) {
165                return null;
166            }
167        }
168        return deltaTimesMs;
169    }
170
171    public void markDataAsStale(boolean hasStaleData) {
172        synchronized (this) {
173            mHasStaleData = hasStaleData;
174        }
175    }
176
177    public boolean hasStaleData() {
178        synchronized (this) {
179            return mHasStaleData;
180        }
181    }
182
183    public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
184        synchronized (this) {
185            mLastUidCpuTimeMs.clear();
186            for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
187                final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
188                if (cpuTimesMs != null) {
189                    mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
190                }
191            }
192        }
193    }
194
195    public void removeUid(int uid) {
196        synchronized (this) {
197            mLastUidCpuTimeMs.delete(uid);
198        }
199    }
200
201    public void removeUidsInRange(int startUid, int endUid) {
202        if (endUid < startUid) {
203            return;
204        }
205        synchronized (this) {
206            mLastUidCpuTimeMs.put(startUid, null);
207            mLastUidCpuTimeMs.put(endUid, null);
208            final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
209            final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
210            mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
211        }
212    }
213
214    @VisibleForTesting
215    public static class Injector {
216        public byte[] readData(String procFile) throws IOException {
217            return Files.readAllBytes(Paths.get(procFile));
218        }
219    }
220
221    @VisibleForTesting
222    public SparseArray<long[]> getLastUidCpuTimeMs() {
223        return mLastUidCpuTimeMs;
224    }
225
226    @VisibleForTesting
227    public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
228        mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
229    }
230}