blob: d21f541345291ab0cbc5a6d507f427d506ba973b [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.SparseArray;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Random;
/**
* Test class for {@link KernelUidCpuClusterTimeReader}.
*
* To run it:
* bit FrameworksCoreTests:com.android.internal.os.KernelUidCpuClusterTimeReaderTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelUidCpuClusterTimeReaderTest {
@Mock
private KernelCpuProcReader mProcReader;
private KernelUidCpuClusterTimeReader mReader;
private VerifiableCallback mCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mReader = new KernelUidCpuClusterTimeReader(mProcReader);
mCallback = new VerifiableCallback();
mReader.setThrottleInterval(0);
}
@Test
public void testReadDelta() throws Exception {
VerifiableCallback cb = new VerifiableCallback();
final int cores = 6;
final int[] clusters = {2, 4};
final int[] uids = {1, 22, 333, 4444, 5555};
// Verify initial call
final long[][] times = increaseTime(new long[uids.length][cores]);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
mReader.readDelta(cb);
for (int i = 0; i < uids.length; i++) {
cb.verify(uids[i], getTotal(clusters, times[i]));
}
cb.verifyNoMoreInteractions();
// Verify that a second call will only return deltas.
cb.clear();
Mockito.reset(mProcReader);
final long[][] times1 = increaseTime(times);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
mReader.readDelta(cb);
for (int i = 0; i < uids.length; i++) {
cb.verify(uids[i], getTotal(clusters, subtract(times1[i], times[i])));
}
cb.verifyNoMoreInteractions();
// Verify that there won't be a callback if the proc file values didn't change.
cb.clear();
Mockito.reset(mProcReader);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
mReader.readDelta(cb);
cb.verifyNoMoreInteractions();
// Verify that calling with a null callback doesn't result in any crashes
Mockito.reset(mProcReader);
final long[][] times2 = increaseTime(times1);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
mReader.readDelta(null);
// Verify that the readDelta call will only return deltas when
// the previous call had null callback.
cb.clear();
Mockito.reset(mProcReader);
final long[][] times3 = increaseTime(times2);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
mReader.readDelta(cb);
for (int i = 0; i < uids.length; i++) {
cb.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
}
cb.verifyNoMoreInteractions();
}
@Test
public void testReadDelta_malformedData() throws Exception {
final int cores = 6;
final int[] clusters = {2, 4};
final int[] uids = {1, 22, 333, 4444, 5555};
// Verify initial call
final long[][] times = increaseTime(new long[uids.length][cores]);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times));
mReader.readDelta(mCallback);
for (int i = 0; i < uids.length; i++) {
mCallback.verify(uids[i], getTotal(clusters, times[i]));
}
mCallback.verifyNoMoreInteractions();
// Verify that there is no callback if a call has wrong format
mCallback.clear();
Mockito.reset(mProcReader);
final long[][] temp = increaseTime(times);
final long[][] times1 = new long[uids.length][];
for (int i = 0; i < temp.length; i++) {
times1[i] = Arrays.copyOfRange(temp[i], 0, 4);
}
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times1));
mReader.readDelta(mCallback);
mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified if the given core count does not match
// the following # of entries.
mCallback.clear();
Mockito.reset(mProcReader);
final long[][] times2 = increaseTime(times);
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times2));
mReader.readDelta(mCallback);
for (int i = 0; i < uids.length; i++) {
mCallback.verify(uids[i], getTotal(clusters, subtract(times2[i], times[i])));
}
mCallback.verifyNoMoreInteractions();
// Verify that there is no callback if any value in the proc file is -ve.
mCallback.clear();
Mockito.reset(mProcReader);
final long[][] times3 = increaseTime(times2);
times3[uids.length - 1][cores - 1] *= -1;
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
mReader.readDelta(mCallback);
for (int i = 0; i < uids.length - 1; i++) {
mCallback.verify(uids[i], getTotal(clusters, subtract(times3[i], times2[i])));
}
mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had -ve value.
mCallback.clear();
Mockito.reset(mProcReader);
for (int i = 0; i < cores; i++) {
times3[uids.length - 1][i] = times2[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times3));
mReader.readDelta(mCallback);
mCallback.verify(uids[uids.length - 1],
getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
// Verify that there is no callback if the values in the proc file are decreased.
mCallback.clear();
Mockito.reset(mProcReader);
final long[][] times4 = increaseTime(times3);
System.arraycopy(times3[uids.length - 1], 0, times4[uids.length - 1], 0, cores);
times4[uids.length - 1][cores - 1] -= 100;
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
mReader.readDelta(mCallback);
for (int i = 0; i < uids.length - 1; i++) {
mCallback.verify(uids[i], getTotal(clusters, subtract(times4[i], times3[i])));
}
mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had decreased values.
mCallback.clear();
Mockito.reset(mProcReader);
for (int i = 0; i < cores; i++) {
times4[uids.length - 1][i] = times3[uids.length - 1][i] + uids[uids.length - 1] * 2520;
}
when(mProcReader.readBytes()).thenReturn(getUidTimesBytes(uids, clusters, times4));
mReader.readDelta(mCallback);
mCallback.verify(uids[uids.length - 1],
getTotal(clusters, subtract(times3[uids.length - 1], times2[uids.length - 1])));
mCallback.verifyNoMoreInteractions();
}
private long[] subtract(long[] a1, long[] a2) {
long[] val = new long[a1.length];
for (int i = 0; i < val.length; ++i) {
val[i] = a1[i] - a2[i];
}
return val;
}
/**
* Unit is 10ms. What's special about 2520? 2520 is LCM of 1, 2, 3, ..., 10. So that when we
* divide shared cpu time by concurrent thread count, we always get a nice integer, avoiding
* rounding errors.
*/
private long[][] increaseTime(long[][] original) {
long[][] newTime = new long[original.length][original[0].length];
Random rand = new Random();
for (int i = 0; i < original.length; i++) {
for (int j = 0; j < original[0].length; j++) {
newTime[i][j] = original[i][j] + rand.nextInt(1000) * 2520 + 2520;
}
}
return newTime;
}
// Format an array of cluster times according to the algorithm in KernelUidCpuClusterTimeReader
private long[] getTotal(int[] cluster, long[] times) {
int core = 0;
long[] sumTimes = new long[cluster.length];
for (int i = 0; i < cluster.length; i++) {
double sum = 0;
for (int j = 0; j < cluster[i]; j++) {
sum += (double) times[core++] * 10 / (j + 1);
}
sumTimes[i] = (long) sum;
}
return sumTimes;
}
private class VerifiableCallback implements KernelUidCpuClusterTimeReader.Callback {
SparseArray<long[]> mData = new SparseArray<>();
int count = 0;
public void verify(int uid, long[] cpuClusterTimeMs) {
long[] array = mData.get(uid);
assertNotNull(array);
assertArrayEquals(cpuClusterTimeMs, array);
count++;
}
public void clear() {
mData.clear();
count = 0;
}
@Override
public void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs) {
long[] array = new long[cpuClusterTimeMs.length];
System.arraycopy(cpuClusterTimeMs, 0, array, 0, array.length);
mData.put(uid, array);
}
public void verifyNoMoreInteractions() {
assertEquals(mData.size(), count);
}
}
/**
* Format uids and times (in 10ms) into the following format:
* [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
* uid1, time1a, time1b, ..., time1n,
* uid2, time2a, time2b, ..., time2n, etc.]
* where n is the number of policies
* xi is the number cpus on a particular policy
*/
private ByteBuffer getUidTimesBytes(int[] uids, int[] clusters, long[][] times) {
int size = (1 + clusters.length + uids.length * (times[0].length + 1)) * 4;
ByteBuffer buf = ByteBuffer.allocate(size);
buf.order(ByteOrder.nativeOrder());
buf.putInt(clusters.length);
for (int i = 0; i < clusters.length; i++) {
buf.putInt(clusters[i]);
}
for (int i = 0; i < uids.length; i++) {
buf.putInt(uids[i]);
for (int j = 0; j < times[i].length; j++) {
buf.putInt((int) (times[i][j]));
}
}
buf.flip();
return buf.order(ByteOrder.nativeOrder());
}
}