Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e45b9b16 authored by Mike Ma's avatar Mike Ma
Browse files

New Kernel Per-UID CPU Time Readers

Kernel per-UID CPU system & user / concurrent active / concurrent
policy / per-frequency time readers that reads the string version
of the per-UID CPU time proc files via the new
KernelCpuProcStringReader.

They are all subclasses of and nested under KernelCpuUidTimeReader,
for a cleaner directory organization.

Bug: 111216804
Test: 4 Unit Tests
Change-Id: I00836c02fff639245c4285b5edaf7b4c94611405
parent bef82587
Loading
Loading
Loading
Loading
+75 −45
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
@@ -59,6 +60,7 @@ public class KernelCpuProcStringReader {
    private static final String PROC_UID_FREQ_TIME = "/proc/uid_time_in_state";
    private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_concurrent_active_time";
    private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_concurrent_policy_time";
    private static final String PROC_UID_USER_SYS_TIME = "/proc/uid_cputime/show_uid_stat";

    private static final KernelCpuProcStringReader FREQ_TIME_READER =
            new KernelCpuProcStringReader(PROC_UID_FREQ_TIME);
@@ -66,19 +68,25 @@ public class KernelCpuProcStringReader {
            new KernelCpuProcStringReader(PROC_UID_ACTIVE_TIME);
    private static final KernelCpuProcStringReader CLUSTER_TIME_READER =
            new KernelCpuProcStringReader(PROC_UID_CLUSTER_TIME);
    private static final KernelCpuProcStringReader USER_SYS_TIME_READER =
            new KernelCpuProcStringReader(PROC_UID_USER_SYS_TIME);

    public static KernelCpuProcStringReader getFreqTimeReaderInstance() {
    static KernelCpuProcStringReader getFreqTimeReaderInstance() {
        return FREQ_TIME_READER;
    }

    public static KernelCpuProcStringReader getActiveTimeReaderInstance() {
    static KernelCpuProcStringReader getActiveTimeReaderInstance() {
        return ACTIVE_TIME_READER;
    }

    public static KernelCpuProcStringReader getClusterTimeReaderInstance() {
    static KernelCpuProcStringReader getClusterTimeReaderInstance() {
        return CLUSTER_TIME_READER;
    }

    static KernelCpuProcStringReader getUserSysTimeReaderInstance() {
        return USER_SYS_TIME_READER;
    }

    private int mErrors = 0;
    private final Path mFile;
    private char[] mBuf;
@@ -164,12 +172,12 @@ public class KernelCpuProcStringReader {
            // ReentrantReadWriteLock allows lock downgrading.
            mReadLock.lock();
            return new ProcFileIterator(total);
        } catch (FileNotFoundException e) {
        } catch (FileNotFoundException | NoSuchFileException e) {
            mErrors++;
            Slog.w(TAG, "File not found. It's normal if not implemented: " + mFile);
        } catch (IOException e) {
            mErrors++;
            Slog.e(TAG, "Error reading: " + mFile, e);
            Slog.e(TAG, "Error reading " + mFile, e);
        } finally {
            StrictMode.setThreadPolicyMask(oldMask);
            mWriteLock.unlock();
@@ -193,6 +201,11 @@ public class KernelCpuProcStringReader {
            mSize = size;
        }

        /** @return Whether there are more lines in the iterator. */
        public boolean hasNextLine() {
            return mPos < mSize;
        }

        /**
         * Fetches the next line. Note that all subsequent return values share the same char[]
         * under the hood.
@@ -214,25 +227,48 @@ public class KernelCpuProcStringReader {
            return CharBuffer.wrap(mBuf, start, i - start);
        }

        /** Total size of the proc file in chars. */
        public int size() {
            return mSize;
        }

        /** Must call close at the end to release the read lock! Or use try-with-resources. */
        public void close() {
            mReadLock.unlock();
        }


    }

    /**
         * Fetches the next line, converts all numbers into long, and puts into the given long[].
         * To avoid GC, caller should try to use the same array for all calls. All non-numeric
         * chars are treated as delimiters. All numbers are non-negative.
     * Converts all numbers in the CharBuffer into longs, and puts into the given long[].
     *
     * Space and colon are treated as delimiters. All other chars are not allowed. All numbers
     * are non-negative. To avoid GC, caller should try to use the same array for all calls.
     *
     * This method also resets the given buffer to the original position before return so that
     * it can be read again.
     *
     * @param buf   The char buffer to be converted.
     * @param array An array to store the parsed numbers.
         * @return The number of elements written to the given array. -1 if there is no more line.
     * @return The number of elements written to the given array. -1 if buf is null, -2 if buf
     * contains invalid char, -3 if any number overflows.
     */
        public int nextLineAsArray(long[] array) {
            CharBuffer buf = nextLine();
    public static int asLongs(CharBuffer buf, long[] array) {
        if (buf == null) {
            return -1;
        }
        final int initialPos = buf.position();
        int count = 0;
        long num = -1;
        char c;

        while (buf.remaining() > 0 && count < array.length) {
            c = buf.get();
            if (!(isNumber(c) || c == ' ' || c == ':')) {
                buf.position(initialPos);
                return -2;
            }
            if (num < 0) {
                if (isNumber(c)) {
                    num = c - '0';
@@ -240,6 +276,10 @@ public class KernelCpuProcStringReader {
            } else {
                if (isNumber(c)) {
                    num = num * 10 + c - '0';
                    if (num < 0) {
                        buf.position(initialPos);
                        return -3;
                    }
                } else {
                    array[count++] = num;
                    num = -1;
@@ -249,21 +289,11 @@ public class KernelCpuProcStringReader {
        if (num >= 0) {
            array[count++] = num;
        }
        buf.position(initialPos);
        return count;
    }

        /** Total size of the proc file in chars. */
        public int size() {
            return mSize;
        }

        /** Must call close at the end to release the read lock! Or use try-with-resources. */
        public void close() {
            mReadLock.unlock();
        }

        private boolean isNumber(char c) {
    private static boolean isNumber(char c) {
        return c >= '0' && c <= '9';
    }
}
}
+776 −0

File added.

Preview size limit exceeded, changes collapsed.

+4 −0
Original line number Diff line number Diff line
@@ -39,6 +39,10 @@ import org.junit.runners.Suite;
        BatteryStatsUserLifecycleTests.class,
        KernelCpuProcReaderTest.class,
        KernelCpuProcStringReaderTest.class,
        KernelCpuUidActiveTimeReaderTest.class,
        KernelCpuUidClusterTimeReaderTest.class,
        KernelCpuUidFreqTimeReaderTest.class,
        KernelCpuUidUserSysTimeReaderTest.class,
        KernelMemoryBandwidthStatsTest.class,
        KernelSingleUidTimeReaderTest.class,
        KernelUidCpuFreqTimeReaderTest.class,
+24 −3
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import java.io.BufferedWriter;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
@@ -149,7 +150,7 @@ public class KernelCpuProcStringReaderTest {
                            + "0 0 1 1 1 0 2 0 221",
                    iter.nextLine().toString());
            long[] actual = new long[43];
            iter.nextLineAsArray(actual);
            KernelCpuProcStringReader.asLongs(iter.nextLine(), actual);
            assertArrayEquals(
                    new long[]{50227, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0,
                            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 721},
@@ -183,7 +184,7 @@ public class KernelCpuProcStringReaderTest {
        }
    }

    /** Tests nextLineToArray functionality. */
    /** Tests reading lines, then converting to long[]. */
    @Test
    public void testReadLineToArray() throws Exception {
        final long[][] data = getTestArray(800, 50);
@@ -193,12 +194,32 @@ public class KernelCpuProcStringReaderTest {
        long[] actual = new long[50];
        try (KernelCpuProcStringReader.ProcFileIterator iter = mReader.open()) {
            for (long[] expected : data) {
                assertEquals(50, iter.nextLineAsArray(actual));
                CharBuffer cb = iter.nextLine();
                String before = cb.toString();
                assertEquals(50, KernelCpuProcStringReader.asLongs(cb, actual));
                assertArrayEquals(expected, actual);
                assertEquals("Buffer not reset to the pos before reading", before, cb.toString());
            }
        }
    }

    /** Tests error handling of converting to long[]. */
    @Test
    public void testLineToArrayErrorHandling() {
        long[] actual = new long[100];
        String invalidChar = "123: -1234 456";
        String overflow = "123: 999999999999999999999999999999999999999999999999999999999 123";
        CharBuffer cb = CharBuffer.wrap("----" + invalidChar + "+++", 4, 4 + invalidChar.length());
        assertEquals("Failed to report err for: " + invalidChar, -2,
                KernelCpuProcStringReader.asLongs(cb, actual));
        assertEquals("Buffer not reset to the same pos before reading", invalidChar, cb.toString());

        cb = CharBuffer.wrap("----" + overflow + "+++", 4, 4 + overflow.length());
        assertEquals("Failed to report err for: " + overflow, -3,
                KernelCpuProcStringReader.asLongs(cb, actual));
        assertEquals("Buffer not reset to the pos before reading", overflow, cb.toString());
    }

    /**
     * Tests that reading a file over the limit (1MB) will return null.
     */
+262 −0
Original line number Diff line number Diff line
/*
 * 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.assertEquals;
import static org.junit.Assert.assertTrue;

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.SparseLongArray;

import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Random;

/**
 * Test class for {@link KernelCpuUidActiveTimeReader}.
 *
 * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuUidActiveTimeReaderTest
 */
@SmallTest
@RunWith(AndroidJUnit4.class)
public class KernelCpuUidActiveTimeReaderTest {
    private File mTestDir;
    private File mTestFile;
    private KernelCpuUidActiveTimeReader mReader;
    private VerifiableCallback mCallback;

    private Random mRand = new Random(12345);
    private final int mCpus = 4;
    private final String mHeadline = "cpus: 4\n";
    private final int[] mUids = {0, 1, 22, 333, 4444, 55555};

    private Context getContext() {
        return InstrumentationRegistry.getContext();
    }

    @Before
    public void setUp() {
        mTestDir = getContext().getDir("test", Context.MODE_PRIVATE);
        mTestFile = new File(mTestDir, "test.file");
        mReader = new KernelCpuUidActiveTimeReader(
                new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), false);
        mCallback = new VerifiableCallback();
    }

    @After
    public void tearDown() throws Exception {
        FileUtils.deleteContents(mTestDir);
        FileUtils.deleteContents(getContext().getFilesDir());
    }

    @Test
    public void testReadDelta() throws Exception {
        final long[][] times = increaseTime(new long[mUids.length][mCpus]);
        writeToFile(mHeadline + uidLines(mUids, times));
        mReader.readDelta(mCallback);
        for (int i = 0; i < mUids.length; ++i) {
            mCallback.verify(mUids[i], getActiveTime(times[i]));
        }
        mCallback.verifyNoMoreInteractions();

        // Verify that a second call will only return deltas.
        mCallback.clear();
        final long[][] newTimes1 = increaseTime(times);
        writeToFile(mHeadline + uidLines(mUids, newTimes1));
        mReader.readDelta(mCallback);
        for (int i = 0; i < mUids.length; ++i) {
            mCallback.verify(mUids[i], getActiveTime(newTimes1[i]) - getActiveTime(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(mHeadline + 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(mHeadline + uidLines(mUids, newTimes3));
        mReader.readDelta(mCallback);
        for (int i = 0; i < mUids.length; ++i) {
            mCallback.verify(mUids[i], getActiveTime(newTimes3[i]) - getActiveTime(newTimes2[i]));
        }
        mCallback.verifyNoMoreInteractions();
        assertTrue(mTestFile.delete());
    }

    @Test
    public void testReadAbsolute() throws Exception {
        final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
        writeToFile(mHeadline + uidLines(mUids, times1));
        mReader.readAbsolute(mCallback);
        for (int i = 0; i < mUids.length; i++) {
            mCallback.verify(mUids[i], getActiveTime(times1[i]));
        }
        mCallback.verifyNoMoreInteractions();

        // Verify that a second call should still return absolute values
        mCallback.clear();
        final long[][] times2 = increaseTime(times1);
        writeToFile(mHeadline + uidLines(mUids, times2));
        mReader.readAbsolute(mCallback);
        for (int i = 0; i < mUids.length; i++) {
            mCallback.verify(mUids[i], getActiveTime(times2[i]));
        }
        mCallback.verifyNoMoreInteractions();
        assertTrue(mTestFile.delete());
    }

    @Test
    public void testReadDeltaDecreasedTime() throws Exception {
        final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
        writeToFile(mHeadline + 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);
        System.arraycopy(times1[0], 0, times2[0], 0, mCpus);
        times2[0][0] = 100;
        writeToFile(mHeadline + uidLines(mUids, times2));
        mReader.readDelta(mCallback);
        for (int i = 1; i < mUids.length; i++) {
            mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
        }
        mCallback.verifyNoMoreInteractions();
        assertTrue(mTestFile.delete());

        // Verify that the internal state was not modified.
        mCallback.clear();
        final long[][] times3 = increaseTime(times2);
        times3[0] = increaseTime(times1)[0];
        writeToFile(mHeadline + uidLines(mUids, times3));
        mReader.readDelta(mCallback);
        mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
        for (int i = 1; i < mUids.length; i++) {
            mCallback.verify(mUids[i], getActiveTime(times3[i]) - getActiveTime(times2[i]));
        }
        mCallback.verifyNoMoreInteractions();
    }

    @Test
    public void testReadDeltaNegativeTime() throws Exception {
        final long[][] times1 = increaseTime(new long[mUids.length][mCpus]);
        writeToFile(mHeadline + uidLines(mUids, times1));
        mReader.readDelta(mCallback);

        // Verify that there should not be a callback for a particular UID if its time is -ve.
        mCallback.clear();
        final long[][] times2 = increaseTime(times1);
        times2[0][0] *= -1;
        writeToFile(mHeadline + uidLines(mUids, times2));
        mReader.readDelta(mCallback);
        for (int i = 1; i < mUids.length; i++) {
            mCallback.verify(mUids[i], getActiveTime(times2[i]) - getActiveTime(times1[i]));
        }
        mCallback.verifyNoMoreInteractions();
        assertTrue(mTestFile.delete());

        // Verify that the internal state was not modified.
        mCallback.clear();
        final long[][] times3 = increaseTime(times2);
        times3[0] = increaseTime(times1)[0];
        writeToFile(mHeadline + uidLines(mUids, times3));
        mReader.readDelta(mCallback);
        mCallback.verify(mUids[0], getActiveTime(times3[0]) - getActiveTime(times1[0]));
        for (int i = 1; i < mUids.length; i++) {
            mCallback.verify(mUids[i], getActiveTime(times3[i]) - getActiveTime(times2[i]));
        }
        mCallback.verifyNoMoreInteractions();
    }

    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) * 1000 + 1000;
            }
        }
        return newTime;
    }

    private long getActiveTime(long[] times) {
        return times[0] + times[1] / 2 + times[2] / 3 + times[3] / 4;
    }

    private class VerifiableCallback implements KernelCpuUidTimeReader.Callback<Long> {
        SparseLongArray mData = new SparseLongArray();

        public void verify(int uid, long time) {
            assertEquals(time, mData.get(uid));
            mData.delete(uid);
        }

        public void clear() {
            mData.clear();
        }

        @Override
        public void onUidCpuTime(int uid, Long time) {
            mData.put(uid, time);
        }

        public void verifyNoMoreInteractions() {
            assertEquals(0, mData.size());
        }
    }
}
Loading