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 */
16
17package com.android.internal.os;
18
19import static org.junit.Assert.assertArrayEquals;
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertNotNull;
22import static org.mockito.Mockito.when;
23
24import android.support.test.filters.SmallTest;
25import android.support.test.runner.AndroidJUnit4;
26import android.util.SparseArray;
27
28import org.junit.Before;
29import org.junit.Test;
30import org.junit.runner.RunWith;
31import org.mockito.Mock;
32import org.mockito.Mockito;
33import org.mockito.MockitoAnnotations;
34
35import java.nio.ByteBuffer;
36import java.nio.ByteOrder;
37import java.util.Arrays;
38import java.util.Random;
39
40/**
41 * Test class for {@link KernelUidCpuClusterTimeReader}.
42 *
43 * To run it:
44 * bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
45 */
46@SmallTest
47@RunWith(AndroidJUnit4.class)
48public class KernelUidCpuClusterTimeReaderTest {
49    @Mock
50    private KernelCpuProcReader mProcReader;
51    private KernelUidCpuClusterTimeReader mReader;
52    private VerifiableCallback mCallback;
53
54    @Before
55    public void setUp() {
56        MockitoAnnotations.initMocks(this);
57        mReader = new KernelUidCpuClusterTimeReader(mProcReader);
58        mCallback = new VerifiableCallback();
59        mReader.setThrottleInterval(0);
60    }
61
62    @Test
63    public void testReadDelta() throws Exception {
64        VerifiableCallback cb = new VerifiableCallback();
65        final int cores = 6;
66        final int[] clusters = {2, 4};
67        final int[] uids = {1, 22, 333, 4444, 5555};
68
69        // Verify initial call
70        final long[][] times = increaseTime(new long[uids.length][cores]);
71        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
72        mReader.readDelta(cb);
73        for (int i = 0; i < uids.length; i++) {
74            cb.verify(uids[i], getTotal(clusters, times[i]));
75        }
76        cb.verifyNoMoreInteractions();
77
78        // Verify that a second call will only return deltas.
79        cb.clear();
80        Mockito.reset(mProcReader);
81        final long[][] times1 = increaseTime(times);
82        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
83        mReader.readDelta(cb);
84        for (int i = 0; i < uids.length; i++) {
85            cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
86        }
87        cb.verifyNoMoreInteractions();
88
89        // Verify that there won't be a callback if the proc file values didn't change.
90        cb.clear();
91        Mockito.reset(mProcReader);
92        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
93        mReader.readDelta(cb);
94        cb.verifyNoMoreInteractions();
95
96        // Verify that calling with a null callback doesn't result in any crashes
97        Mockito.reset(mProcReader);
98        final long[][] times2 = increaseTime(times1);
99        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
100        mReader.readDelta(null);
101
102        // Verify that the readDelta call will only return deltas when
103        // the previous call had null callback.
104        cb.clear();
105        Mockito.reset(mProcReader);
106        final long[][] times3 = increaseTime(times2);
107        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
108        mReader.readDelta(cb);
109        for (int i = 0; i < uids.length; i++) {
110            cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
111        }
112        cb.verifyNoMoreInteractions();
113
114    }
115
116    @Test
117    public void testReadAbsolute() throws Exception {
118        VerifiableCallback cb = new VerifiableCallback();
119        final int cores = 6;
120        final int[] clusters = {2, 4};
121        final int[] uids = {1, 22, 333, 4444, 5555};
122
123        // Verify return absolute value
124        final long[][] times = increaseTime(new long[uids.length][cores]);
125        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
126        mReader.readAbsolute(cb);
127        for (int i = 0; i < uids.length; i++) {
128            cb.verify(uids[i], getTotal(clusters, times[i]));
129        }
130        cb.verifyNoMoreInteractions();
131
132        // Verify that a second call should return the same absolute value
133        cb.clear();
134        Mockito.reset(mProcReader);
135        final long[][] times1 = increaseTime(times);
136        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
137        mReader.readAbsolute(cb);
138        for (int i = 0; i < uids.length; i++) {
139            cb.verify(uids[i], getTotal(clusters, times1[i]));
140        }
141        cb.verifyNoMoreInteractions();
142    }
143
144    @Test
145    public void testReadDelta_malformedData() throws Exception {
146        final int cores = 6;
147        final int[] clusters = {2, 4};
148        final int[] uids = {1, 22, 333, 4444, 5555};
149
150        // Verify initial call
151        final long[][] times = increaseTime(new long[uids.length][cores]);
152        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
153        mReader.readDelta(mCallback);
154        for (int i = 0; i < uids.length; i++) {
155            mCallback.verify(uids[i], getTotal(clusters, times[i]));
156        }
157        mCallback.verifyNoMoreInteractions();
158
159        // Verify that there is no callback if a call has wrong format
160        mCallback.clear();
161        Mockito.reset(mProcReader);
162        final long[][] temp = increaseTime(times);
163        final long[][] times1 = new long[uids.length][];
164        for (int i = 0; i < temp.length; i++) {
165            times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
166        }
167        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
168        mReader.readDelta(mCallback);
169        mCallback.verifyNoMoreInteractions();
170
171        // Verify that the internal state was not modified if the given core count does not match
172        // the following # of entries.
173        mCallback.clear();
174        Mockito.reset(mProcReader);
175        final long[][] times2 = increaseTime(times);
176        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
177        mReader.readDelta(mCallback);
178        for (int i = 0; i < uids.length; i++) {
179            mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
180        }
181        mCallback.verifyNoMoreInteractions();
182
183        // Verify that there is no callback if any value in the proc file is -ve.
184        mCallback.clear();
185        Mockito.reset(mProcReader);
186        final long[][] times3 = increaseTime(times2);
187        times3[uids.length - 1][cores - 1] *= -1;
188        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
189        mReader.readDelta(mCallback);
190        for (int i = 0; i < uids.length - 1; i++) {
191            mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
192        }
193        mCallback.verifyNoMoreInteractions();
194
195        // Verify that the internal state was not modified when the proc file had -ve value.
196        mCallback.clear();
197        Mockito.reset(mProcReader);
198        for (int i = 0; i < cores; i++) {
199            times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
200        }
201        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
202        mReader.readDelta(mCallback);
203        mCallback.verify(uids[uids.length - 1],
204                getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
205
206        // Verify that there is no callback if the values in the proc file are decreased.
207        mCallback.clear();
208        Mockito.reset(mProcReader);
209        final long[][] times4 = increaseTime(times3);
210        System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
211        times4[uids.length - 1][cores - 1] -= 100;
212        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
213        mReader.readDelta(mCallback);
214        for (int i = 0; i < uids.length - 1; i++) {
215            mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
216        }
217        mCallback.verifyNoMoreInteractions();
218
219        // Verify that the internal state was not modified when the proc file had decreased values.
220        mCallback.clear();
221        Mockito.reset(mProcReader);
222        for (int i = 0; i < cores; i++) {
223            times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
224        }
225        when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
226        mReader.readDelta(mCallback);
227        mCallback.verify(uids[uids.length - 1],
228                getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
229        mCallback.verifyNoMoreInteractions();
230    }
231
232
233    private long[] subtract(long[] a1, long[] a2) {
234        long[] val = new long[a1.length];
235        for (int i = 0; i < val.length; ++i) {
236            val[i] = a1[i] - a2[i];
237        }
238        return val;
239    }
240
241    /**
242     * Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
243     * divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
244     * rounding errors.
245     */
246    private long[][] increaseTime(long[][] original) {
247        long[][] newTime = new long[original.length][original[0].length];
248        Random rand = new Random();
249        for (int i = 0; i < original.length; i++) {
250            for (int j = 0; j < original[0].length; j++) {
251                newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
252            }
253        }
254        return newTime;
255    }
256
257    // Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
258    private long[] getTotal(int[] cluster, long[] times) {
259        int core = 0;
260        long[] sumTimes = new long[cluster.length];
261        for (int i = 0; i < cluster.length; i++) {
262            double sum = 0;
263            for (int j = 0; j < cluster[i]; j++) {
264                sum += (double) times[core++] * 10 / (j + 1);
265            }
266            sumTimes[i] = (long) sum;
267        }
268        return sumTimes;
269    }
270
271    private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
272
273        SparseArray<long[]> mData = new SparseArray<>();
274        int count = 0;
275
276        public void verify(int uid, long[] cpuClusterTimeMs) {
277            long[] array = mData.get(uid);
278            assertNotNull(array);
279            assertArrayEquals(cpuClusterTimeMs, array);
280            count++;
281        }
282
283        public void clear() {
284            mData.clear();
285            count = 0;
286        }
287
288        @Override
289        public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
290            long[] array = new long[cpuClusterTimeMs.length];
291            System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
292            mData.put(uid, array);
293        }
294
295        public void verifyNoMoreInteractions() {
296            assertEquals(mData.size(), count);
297        }
298    }
299
300    /**
301     * Format uids and times (in 10ms) into the following format:
302     * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
303     * uid1, time1a, time1b, ..., time1n,
304     * uid2, time2a, time2b, ..., time2n, etc.]
305     * where n is the number of policies
306     * xi is the number cpus on a particular policy
307     */
308    private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
309        int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
310        ByteBuffer buf = ByteBuffer.allocate(size);
311        buf.order(ByteOrder.nativeOrder());
312        buf.putInt(clusters.length);
313        for (int i = 0; i < clusters.length; i++) {
314            buf.putInt(clusters[i]);
315        }
316        for (int i = 0; i < uids.length; i++) {
317            buf.putInt(uids[i]);
318            for (int j = 0; j < times[i].length; j++) {
319                buf.putInt((int) (times[i][j]));
320            }
321        }
322        buf.flip();
323        return buf.order(ByteOrder.nativeOrder());
324    }
325}
326