blob: 1d3a98a89d953a124b7f9c53f6531774e041ed21 [file] [log] [blame]
/*
* Copyright (C) 2018 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.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.os.FileUtils;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.SparseArray;
import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Random;
/**
* Test class for {@link KernelCpuUidFreqTimeReader}.
*
* $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidFreqTimeReaderTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelCpuUidFreqTimeReaderTest {
private File mTestDir;
private File mTestFile;
private KernelCpuUidFreqTimeReader mReader;
private VerifiableCallback mCallback;
@Mock
private PowerProfile mPowerProfile;
private Random mRand = new Random(12345);
private final int[] mUids = {0, 1, 22, 333, 4444, 55555};
private Context getContext() {
return InstrumentationRegistry.getContext();
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
mTestFile = new File(mTestDir, "test.file");
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
mCallback = new VerifiableCallback();
}
@After
public void tearDown() throws Exception {
FileUtils.deleteContents(mTestDir);
FileUtils.deleteContents(getContext().getFilesDir());
}
@Test
public void testReadFreqs_perClusterTimesNotAvailable() throws Exception {
final long[][] freqs = {
{1, 12, 123, 1234},
{1, 12, 123, 23, 123, 1234, 12345, 123456},
{1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345},
{1, 12, 123, 23, 2345, 234567}
};
final int[] numClusters = {2, 2, 3, 1};
final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}};
for (int i = 0; i < freqs.length; ++i) {
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
setCpuClusterFreqs(numClusters[i], numFreqs[i]);
writeToFile(freqsLine(freqs[i]));
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
assertFalse(errMsg, mReader.perClusterTimesAvailable());
// Verify that a second call won't read the proc file again
assertTrue(mTestFile.delete());
actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
assertFalse(errMsg, mReader.perClusterTimesAvailable());
}
}
@Test
public void testReadFreqs_perClusterTimesAvailable() throws Exception {
final long[][] freqs = {
{1, 12, 123, 1234},
{1, 12, 123, 23, 123, 1234, 12345, 123456},
{1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567}
};
final int[] numClusters = {1, 2, 3};
final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}};
for (int i = 0; i < freqs.length; ++i) {
mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(),
new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
setCpuClusterFreqs(numClusters[i], numFreqs[i]);
writeToFile(freqsLine(freqs[i]));
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s",
Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i]));
assertTrue(errMsg, mReader.perClusterTimesAvailable());
// Verify that a second call won't read the proc file again
assertTrue(mTestFile.delete());
actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs[i], actualFreqs);
assertTrue(errMsg, mReader.perClusterTimesAvailable());
}
}
@Test
public void testReadDelta() throws Exception {
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times = increaseTime(new long[mUids.length][freqs.length]);
writeToFile(freqsLine(freqs) + uidLines(mUids, times));
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], times[i]);
}
mCallback.verifyNoMoreInteractions();
// Verify that readDelta also reads the frequencies if not already available.
assertTrue(mTestFile.delete());
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs, actualFreqs);
// Verify that a second call will only return deltas.
mCallback.clear();
final long[][] newTimes1 = increaseTime(times);
writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes1));
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(newTimes1[i], times[i]));
}
mCallback.verifyNoMoreInteractions();
// Verify that there won't be a callback if the proc file values didn't change.
mCallback.clear();
mReader.readDelta(mCallback);
mCallback.verifyNoMoreInteractions();
// Verify that calling with a null callback doesn't result in any crashes
mCallback.clear();
final long[][] newTimes2 = increaseTime(newTimes1);
writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes2));
mReader.readDelta(null);
mCallback.verifyNoMoreInteractions();
// Verify that the readDelta call will only return deltas when
// the previous call had null callback.
mCallback.clear();
final long[][] newTimes3 = increaseTime(newTimes2);
writeToFile(freqsLine(freqs) + uidLines(mUids, newTimes3));
mReader.readDelta(mCallback);
for (int i = 0; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(newTimes3[i], newTimes2[i]));
}
mCallback.verifyNoMoreInteractions();
assertTrue(mTestFile.delete());
}
@Test
public void testReadAbsolute() throws Exception {
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], times1[i]);
}
mCallback.verifyNoMoreInteractions();
// Verify that readDelta also reads the frequencies if not already available.
assertTrue(mTestFile.delete());
long[] actualFreqs = mReader.readFreqs(mPowerProfile);
assertArrayEquals(freqs, actualFreqs);
// Verify that a second call should still return absolute values
mCallback.clear();
final long[][] times2 = increaseTime(times1);
writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
mReader.readAbsolute(mCallback);
for (int i = 0; i < mUids.length; i++) {
mCallback.verify(mUids[i], times2[i]);
}
mCallback.verifyNoMoreInteractions();
assertTrue(mTestFile.delete());
}
@Test
public void testReadDeltaWrongData() throws Exception {
final long[] freqs = {110, 123, 145, 167, 289, 997};
final long[][] times1 = increaseTime(new long[mUids.length][freqs.length]);
writeToFile(freqsLine(freqs) + uidLines(mUids, times1));
mReader.readDelta(mCallback);
// Verify that there should not be a callback for a particular UID if its time decreases.
mCallback.clear();
final long[][] times2 = increaseTime(times1);
times2[0][0] = 1000;
writeToFile(freqsLine(freqs) + uidLines(mUids, times2));
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times2[i], times1[i]));
}
mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified.
mCallback.clear();
final long[][] times3 = increaseTime(times2);
times3[0] = increaseTime(times1)[0];
writeToFile(freqsLine(freqs) + uidLines(mUids, times3));
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(times3[0], times1[0]));
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times3[i], times2[i]));
}
mCallback.verifyNoMoreInteractions();
// Verify that there is no callback if any value in the proc file is -ve.
mCallback.clear();
final long[][] times4 = increaseTime(times3);
times4[0][0] *= -1;
writeToFile(freqsLine(freqs) + uidLines(mUids, times4));
mReader.readDelta(mCallback);
for (int i = 1; i < mUids.length; ++i) {
mCallback.verify(mUids[i], subtract(times4[i], times3[i]));
}
mCallback.verifyNoMoreInteractions();
// Verify that the internal state was not modified when the proc file had -ve value.
mCallback.clear();
final long[][] times5 = increaseTime(times4);
times5[0] = increaseTime(times3)[0];
writeToFile(freqsLine(freqs) + uidLines(mUids, times5));
mReader.readDelta(mCallback);
mCallback.verify(mUids[0], subtract(times5[0], times3[0]));
for (int i = 1; i < mUids.length; i++) {
mCallback.verify(mUids[i], subtract(times5[i], times4[i]));
}
assertTrue(mTestFile.delete());
}
private String freqsLine(long[] freqs) {
final StringBuilder sb = new StringBuilder();
sb.append("uid:");
for (int i = 0; i < freqs.length; ++i) {
sb.append(" " + freqs[i]);
}
return sb.append('\n').toString();
}
private void setCpuClusterFreqs(int numClusters, int... clusterFreqs) {
assertEquals(numClusters, clusterFreqs.length);
when(mPowerProfile.getNumCpuClusters()).thenReturn(numClusters);
for (int i = 0; i < numClusters; ++i) {
when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)).thenReturn(clusterFreqs[i]);
}
}
private String uidLines(int[] uids, long[][] times) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < uids.length; i++) {
sb.append(uids[i]).append(':');
for (int j = 0; j < times[i].length; j++) {
sb.append(' ').append(times[i][j] / 10);
}
sb.append('\n');
}
return sb.toString();
}
private void writeToFile(String s) throws IOException {
try (BufferedWriter w = Files.newBufferedWriter(mTestFile.toPath())) {
w.write(s);
w.flush();
}
}
private long[][] increaseTime(long[][] original) {
long[][] newTime = new long[original.length][original[0].length];
for (int i = 0; i < original.length; i++) {
for (int j = 0; j < original[0].length; j++) {
newTime[i][j] = original[i][j] + mRand.nextInt(10000) * 10 + 10;
}
}
return newTime;
}
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;
}
private class VerifiableCallback implements KernelCpuUidTimeReader.Callback<long[]> {
SparseArray<long[]> mData = new SparseArray<>();
public void verify(int uid, long[] cpuTimes) {
long[] array = mData.get(uid);
assertNotNull(array);
assertArrayEquals(cpuTimes, array);
mData.remove(uid);
}
public void clear() {
mData.clear();
}
@Override
public void onUidCpuTime(int uid, long[] times) {
long[] array = new long[times.length];
System.arraycopy(times, 0, array, 0, array.length);
mData.put(uid, array);
}
public void verifyNoMoreInteractions() {
assertEquals(0, mData.size());
}
}
}