Loading core/java/com/android/internal/os/KernelCpuThreadReader.java +139 −6 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.function.Predicate; /** * Given a process, will iterate over the child threads of the process, and return the CPU usage Loading Loading @@ -69,6 +70,11 @@ public class KernelCpuThreadReader { */ private static final String THREAD_NAME_FILENAME = "comm"; /** * Glob pattern for the process directory names under {@code proc} */ private static final String PROCESS_DIRECTORY_FILTER = "[0-9]*"; /** * Default process name when the name can't be read */ Loading @@ -95,6 +101,18 @@ public class KernelCpuThreadReader { */ private static final int NUM_BUCKETS = 8; /** * Default predicate for what UIDs to check for when getting processes. This filters to only * select system UIDs (1000-1999) */ private static final Predicate<Integer> DEFAULT_UID_PREDICATE = uid -> 1000 <= uid && uid < 2000; /** * Value returned when there was an error getting an integer ID value (e.g. PID, UID) */ private static final int ID_ERROR = -1; /** * Where the proc filesystem is mounted */ Loading @@ -116,8 +134,13 @@ public class KernelCpuThreadReader { */ private final FrequencyBucketCreator mFrequencyBucketCreator; private final Injector mInjector; private KernelCpuThreadReader() throws IOException { this(DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH); this( DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH, new Injector()); } /** Loading @@ -128,9 +151,13 @@ public class KernelCpuThreadReader { * format */ @VisibleForTesting public KernelCpuThreadReader(Path procPath, Path initialTimeInStatePath) throws IOException { public KernelCpuThreadReader( Path procPath, Path initialTimeInStatePath, Injector injector) throws IOException { mProcPath = procPath; mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath); mInjector = injector; // Copy mProcTimeInState's frequencies and initialize bucketing final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz(); Loading @@ -153,6 +180,67 @@ public class KernelCpuThreadReader { } } /** * Get the per-thread CPU usage of all processes belonging to UIDs between {@code [1000, 2000)} */ @Nullable public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() { return getProcessCpuUsageByUids(DEFAULT_UID_PREDICATE); } /** * Get the per-thread CPU usage of all processes belonging to a set of UIDs * * <p>This function will crawl through all process {@code proc} directories found by the pattern * {@code /proc/[0-9]*}, and then check the UID using {@code /proc/$PID/status}. This takes * approximately 500ms on a Pixel 2. Therefore, this method can be computationally expensive, * and should not be called more than once an hour. * * @param uidPredicate only get usage from processes owned by UIDs that match this predicate */ @Nullable public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids(Predicate<Integer> uidPredicate) { if (DEBUG) { Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs"); } final ArrayList<ProcessCpuUsage> processCpuUsages = new ArrayList<>(); try (DirectoryStream<Path> processPaths = Files.newDirectoryStream(mProcPath, PROCESS_DIRECTORY_FILTER)) { for (Path processPath : processPaths) { final int processId = getProcessId(processPath); final int uid = mInjector.getUidForPid(processId); if (uid == ID_ERROR || processId == ID_ERROR) { continue; } if (!uidPredicate.test(uid)) { continue; } final ProcessCpuUsage processCpuUsage = getProcessCpuUsage(processPath, processId, uid); if (processCpuUsage != null) { processCpuUsages.add(processCpuUsage); } } } catch (IOException e) { Slog.w("Failed to iterate over process paths", e); return null; } if (processCpuUsages.isEmpty()) { Slog.w(TAG, "Didn't successfully get any process CPU information for UIDs specified"); return null; } if (DEBUG) { Slog.d(TAG, "Read usage for " + processCpuUsages.size() + " processes"); } return processCpuUsages; } /** * Read all of the CPU usage statistics for each child thread of the current process * Loading @@ -162,8 +250,8 @@ public class KernelCpuThreadReader { public ProcessCpuUsage getCurrentProcessCpuUsage() { return getProcessCpuUsage( mProcPath.resolve("self"), Process.myPid(), Process.myUid()); mInjector.myPid(), mInjector.myUid()); } /** Loading @@ -172,7 +260,8 @@ public class KernelCpuThreadReader { * @param processPath the {@code /proc} path of the thread * @param processId the ID of the process * @param uid the ID of the user who owns the process * @return process CPU usage containing usage of all child threads * @return process CPU usage containing usage of all child threads. Null if the process exited * and its {@code proc} directory was removed while collecting information */ @Nullable private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) { Loading Loading @@ -224,7 +313,8 @@ public class KernelCpuThreadReader { * Get a thread's CPU usage * * @param threadDirectory the {@code /proc} directory of the thread * @return null in the case that the directory read failed * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was * removed while collecting information */ @Nullable private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) { Loading Loading @@ -279,6 +369,22 @@ public class KernelCpuThreadReader { return threadName; } /** * Get the ID of a process from its path * * @param processPath {@code proc} path of the process * @return the ID, {@link #ID_ERROR} if the path could not be parsed */ private int getProcessId(Path processPath) { String fileName = processPath.getFileName().toString(); try { return Integer.parseInt(fileName); } catch (NumberFormatException e) { Slog.w(TAG, "Failed to parse " + fileName + " as process ID", e); return ID_ERROR; } } /** * Puts frequencies and usage times into buckets */ Loading Loading @@ -443,4 +549,31 @@ public class KernelCpuThreadReader { this.usageTimesMillis = usageTimesMillis; } } /** * Used to inject static methods from {@link Process} */ @VisibleForTesting public static class Injector { /** * Get the PID of the current process */ public int myPid() { return Process.myPid(); } /** * Get the UID that owns the current process */ public int myUid() { return Process.myUid(); } /** * Get the UID for the process with ID {@code pid} */ public int getUidForPid(int pid) { return Process.getUidForPid(pid); } } } core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +113 −33 Original line number Diff line number Diff line Loading @@ -39,13 +39,16 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.function.Predicate; @SmallTest @RunWith(AndroidJUnit4.class) public class KernelCpuThreadReaderTest { private static final String PROCESS_NAME = "test_process"; private static final int UID = 1000; private static final int PROCESS_ID = 1234; private static final int[] THREAD_IDS = {0, 1000, 1235, 4321}; private static final String PROCESS_NAME = "test_process"; private static final String[] THREAD_NAMES = { "test_thread_1", "test_thread_2", "test_thread_3", "test_thread_4" }; Loading Loading @@ -73,49 +76,126 @@ public class KernelCpuThreadReaderTest { } @Test public void testSimple() throws IOException { // Make /proc/self final Path selfPath = mProcDirectory.toPath().resolve("self"); assertTrue(selfPath.toFile().mkdirs()); public void testReader_currentProcess() throws IOException { KernelCpuThreadReader.Injector processUtils = new KernelCpuThreadReader.Injector() { @Override public int myPid() { return PROCESS_ID; } @Override public int myUid() { return UID; } @Override public int getUidForPid(int pid) { return 0; } }; setupDirectory(mProcDirectory.toPath().resolve("self"), THREAD_IDS, PROCESS_NAME, THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES); final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( mProcDirectory.toPath(), mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state"), processUtils); final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = kernelCpuThreadReader.getCurrentProcessCpuUsage(); checkResults(processCpuUsage, kernelCpuThreadReader.getCpuFrequenciesKhz(), UID, PROCESS_ID, THREAD_IDS, PROCESS_NAME, THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES); } @Test public void testReader_byUids() throws IOException { int[] uids = new int[]{0, 2, 3, 4, 5, 6000}; Predicate<Integer> uidPredicate = uid -> uid == 0 || uid >= 4; int[] expectedUids = new int[]{0, 4, 5, 6000}; KernelCpuThreadReader.Injector processUtils = new KernelCpuThreadReader.Injector() { @Override public int myPid() { return 0; } // Make /proc/self/task final Path selfThreadsPath = selfPath.resolve("task"); @Override public int myUid() { return 0; } @Override public int getUidForPid(int pid) { return pid; } }; for (int uid : uids) { setupDirectory(mProcDirectory.toPath().resolve(String.valueOf(uid)), new int[]{uid * 10}, "process" + uid, new String[]{"thread" + uid}, new int[]{1000}, new int[][]{{uid}}); } final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( mProcDirectory.toPath(), mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"), processUtils); ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids = kernelCpuThreadReader.getProcessCpuUsageByUids(uidPredicate); processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.processId)); assertEquals(expectedUids.length, processCpuUsageByUids.size()); for (int i = 0; i < expectedUids.length; i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsageByUids.get(i); int uid = expectedUids[i]; checkResults(processCpuUsage, kernelCpuThreadReader.getCpuFrequenciesKhz(), uid, uid, new int[]{uid * 10}, "process" + uid, new String[]{"thread" + uid}, new int[]{1000}, new int[][]{{uid}}); } } private void setupDirectory(Path processPath, int[] threadIds, String processName, String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) throws IOException { // Make /proc/$PID assertTrue(processPath.toFile().mkdirs()); // Make /proc/$PID/task final Path selfThreadsPath = processPath.resolve("task"); assertTrue(selfThreadsPath.toFile().mkdirs()); // Make /proc/self/cmdline Files.write(selfPath.resolve("cmdline"), PROCESS_NAME.getBytes()); // Make /proc/$PID/cmdline Files.write(processPath.resolve("cmdline"), processName.getBytes()); // Make thread directories in reverse order, as they are read in order of creation by // CpuThreadProcReader for (int i = 0; i < THREAD_IDS.length; i++) { // Make /proc/self/task/$TID final Path threadPath = selfThreadsPath.resolve(String.valueOf(THREAD_IDS[i])); for (int i = 0; i < threadIds.length; i++) { // Make /proc/$PID/task/$TID final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i])); assertTrue(threadPath.toFile().mkdirs()); // Make /proc/self/task/$TID/comm Files.write(threadPath.resolve("comm"), THREAD_NAMES[i].getBytes()); // Make /proc/$PID/task/$TID/comm Files.write(threadPath.resolve("comm"), threadNames[i].getBytes()); // Make /proc/self/task/$TID/time_in_state // Make /proc/$PID/task/$TID/time_in_state final OutputStream timeInStateStream = Files.newOutputStream(threadPath.resolve("time_in_state")); for (int j = 0; j < THREAD_CPU_FREQUENCIES.length; j++) { final String line = String.valueOf(THREAD_CPU_FREQUENCIES[j]) + " " + String.valueOf(THREAD_CPU_TIMES[i][j]) + "\n"; for (int j = 0; j < cpuFrequencies.length; j++) { final String line = String.valueOf(cpuFrequencies[j]) + " " + String.valueOf(cpuTimes[i][j]) + "\n"; timeInStateStream.write(line.getBytes()); } timeInStateStream.close(); } } final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( mProcDirectory.toPath(), mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state")); final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = kernelCpuThreadReader.getCurrentProcessCpuUsage(); private void checkResults(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage, int[] readerCpuFrequencies, int uid, int processId, int[] threadIds, String processName, String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) { assertNotNull(processCpuUsage); assertEquals(android.os.Process.myPid(), processCpuUsage.processId); assertEquals(android.os.Process.myUid(), processCpuUsage.uid); assertEquals(PROCESS_NAME, processCpuUsage.processName); assertEquals(processId, processCpuUsage.processId); assertEquals(uid, processCpuUsage.uid); assertEquals(processName, processCpuUsage.processName); // Sort the thread CPU usages to compare with test case final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = Loading @@ -124,21 +204,21 @@ public class KernelCpuThreadReaderTest { int threadCount = 0; for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage : threadCpuUsages) { assertEquals(THREAD_IDS[threadCount], threadCpuUsage.threadId); assertEquals(THREAD_NAMES[threadCount], threadCpuUsage.threadName); assertEquals(threadIds[threadCount], threadCpuUsage.threadId); assertEquals(threadNames[threadCount], threadCpuUsage.threadName); for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) { assertEquals( THREAD_CPU_TIMES[threadCount][i] * 10, cpuTimes[threadCount][i] * 10, threadCpuUsage.usageTimesMillis[i]); assertEquals( THREAD_CPU_FREQUENCIES[i], kernelCpuThreadReader.getCpuFrequenciesKhz()[i]); cpuFrequencies[i], readerCpuFrequencies[i]); } threadCount++; } assertEquals(threadCount, THREAD_IDS.length); assertEquals(threadCount, threadIds.length); } @Test Loading services/core/java/com/android/server/stats/StatsCompanionService.java +30 −25 Original line number Diff line number Diff line Loading @@ -1630,14 +1630,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (this.mKernelCpuThreadReader == null) { return; } KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = this.mKernelCpuThreadReader .getCurrentProcessCpuUsage(); if (processCpuUsage == null) { ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages = this.mKernelCpuThreadReader.getProcessCpuUsageByUids(); if (processCpuUsages == null) { return; } int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz(); for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage : processCpuUsage.threadCpuUsages) { for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = processCpuUsage.threadCpuUsages; for (int j = 0; j < threadCpuUsages.size(); j++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = threadCpuUsages.get(j); if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) { Slog.w(TAG, "Unexpected number of usage times," + " expected " + cpuFrequencies.length Loading @@ -1645,9 +1649,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { continue; } for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) { for (int k = 0; k < threadCpuUsage.usageTimesMillis.length; k++) { // Do not report CPU usage at a frequency when it's zero if (threadCpuUsage.usageTimesMillis[i] == 0) { if (threadCpuUsage.usageTimesMillis[k] == 0) { continue; } Loading @@ -1658,12 +1662,13 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeInt(threadCpuUsage.threadId); e.writeString(processCpuUsage.processName); e.writeString(threadCpuUsage.threadName); e.writeInt(cpuFrequencies[i]); e.writeInt(threadCpuUsage.usageTimesMillis[i]); e.writeInt(cpuFrequencies[k]); e.writeInt(threadCpuUsage.usageTimesMillis[k]); pulledData.add(e); } } } } /** * Pulls various data. Loading Loading
core/java/com/android/internal/os/KernelCpuThreadReader.java +139 −6 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.function.Predicate; /** * Given a process, will iterate over the child threads of the process, and return the CPU usage Loading Loading @@ -69,6 +70,11 @@ public class KernelCpuThreadReader { */ private static final String THREAD_NAME_FILENAME = "comm"; /** * Glob pattern for the process directory names under {@code proc} */ private static final String PROCESS_DIRECTORY_FILTER = "[0-9]*"; /** * Default process name when the name can't be read */ Loading @@ -95,6 +101,18 @@ public class KernelCpuThreadReader { */ private static final int NUM_BUCKETS = 8; /** * Default predicate for what UIDs to check for when getting processes. This filters to only * select system UIDs (1000-1999) */ private static final Predicate<Integer> DEFAULT_UID_PREDICATE = uid -> 1000 <= uid && uid < 2000; /** * Value returned when there was an error getting an integer ID value (e.g. PID, UID) */ private static final int ID_ERROR = -1; /** * Where the proc filesystem is mounted */ Loading @@ -116,8 +134,13 @@ public class KernelCpuThreadReader { */ private final FrequencyBucketCreator mFrequencyBucketCreator; private final Injector mInjector; private KernelCpuThreadReader() throws IOException { this(DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH); this( DEFAULT_PROC_PATH, DEFAULT_INITIAL_TIME_IN_STATE_PATH, new Injector()); } /** Loading @@ -128,9 +151,13 @@ public class KernelCpuThreadReader { * format */ @VisibleForTesting public KernelCpuThreadReader(Path procPath, Path initialTimeInStatePath) throws IOException { public KernelCpuThreadReader( Path procPath, Path initialTimeInStatePath, Injector injector) throws IOException { mProcPath = procPath; mProcTimeInStateReader = new ProcTimeInStateReader(initialTimeInStatePath); mInjector = injector; // Copy mProcTimeInState's frequencies and initialize bucketing final long[] frequenciesKhz = mProcTimeInStateReader.getFrequenciesKhz(); Loading @@ -153,6 +180,67 @@ public class KernelCpuThreadReader { } } /** * Get the per-thread CPU usage of all processes belonging to UIDs between {@code [1000, 2000)} */ @Nullable public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids() { return getProcessCpuUsageByUids(DEFAULT_UID_PREDICATE); } /** * Get the per-thread CPU usage of all processes belonging to a set of UIDs * * <p>This function will crawl through all process {@code proc} directories found by the pattern * {@code /proc/[0-9]*}, and then check the UID using {@code /proc/$PID/status}. This takes * approximately 500ms on a Pixel 2. Therefore, this method can be computationally expensive, * and should not be called more than once an hour. * * @param uidPredicate only get usage from processes owned by UIDs that match this predicate */ @Nullable public ArrayList<ProcessCpuUsage> getProcessCpuUsageByUids(Predicate<Integer> uidPredicate) { if (DEBUG) { Slog.d(TAG, "Reading CPU thread usages for processes owned by UIDs"); } final ArrayList<ProcessCpuUsage> processCpuUsages = new ArrayList<>(); try (DirectoryStream<Path> processPaths = Files.newDirectoryStream(mProcPath, PROCESS_DIRECTORY_FILTER)) { for (Path processPath : processPaths) { final int processId = getProcessId(processPath); final int uid = mInjector.getUidForPid(processId); if (uid == ID_ERROR || processId == ID_ERROR) { continue; } if (!uidPredicate.test(uid)) { continue; } final ProcessCpuUsage processCpuUsage = getProcessCpuUsage(processPath, processId, uid); if (processCpuUsage != null) { processCpuUsages.add(processCpuUsage); } } } catch (IOException e) { Slog.w("Failed to iterate over process paths", e); return null; } if (processCpuUsages.isEmpty()) { Slog.w(TAG, "Didn't successfully get any process CPU information for UIDs specified"); return null; } if (DEBUG) { Slog.d(TAG, "Read usage for " + processCpuUsages.size() + " processes"); } return processCpuUsages; } /** * Read all of the CPU usage statistics for each child thread of the current process * Loading @@ -162,8 +250,8 @@ public class KernelCpuThreadReader { public ProcessCpuUsage getCurrentProcessCpuUsage() { return getProcessCpuUsage( mProcPath.resolve("self"), Process.myPid(), Process.myUid()); mInjector.myPid(), mInjector.myUid()); } /** Loading @@ -172,7 +260,8 @@ public class KernelCpuThreadReader { * @param processPath the {@code /proc} path of the thread * @param processId the ID of the process * @param uid the ID of the user who owns the process * @return process CPU usage containing usage of all child threads * @return process CPU usage containing usage of all child threads. Null if the process exited * and its {@code proc} directory was removed while collecting information */ @Nullable private ProcessCpuUsage getProcessCpuUsage(Path processPath, int processId, int uid) { Loading Loading @@ -224,7 +313,8 @@ public class KernelCpuThreadReader { * Get a thread's CPU usage * * @param threadDirectory the {@code /proc} directory of the thread * @return null in the case that the directory read failed * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was * removed while collecting information */ @Nullable private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) { Loading Loading @@ -279,6 +369,22 @@ public class KernelCpuThreadReader { return threadName; } /** * Get the ID of a process from its path * * @param processPath {@code proc} path of the process * @return the ID, {@link #ID_ERROR} if the path could not be parsed */ private int getProcessId(Path processPath) { String fileName = processPath.getFileName().toString(); try { return Integer.parseInt(fileName); } catch (NumberFormatException e) { Slog.w(TAG, "Failed to parse " + fileName + " as process ID", e); return ID_ERROR; } } /** * Puts frequencies and usage times into buckets */ Loading Loading @@ -443,4 +549,31 @@ public class KernelCpuThreadReader { this.usageTimesMillis = usageTimesMillis; } } /** * Used to inject static methods from {@link Process} */ @VisibleForTesting public static class Injector { /** * Get the PID of the current process */ public int myPid() { return Process.myPid(); } /** * Get the UID that owns the current process */ public int myUid() { return Process.myUid(); } /** * Get the UID for the process with ID {@code pid} */ public int getUidForPid(int pid) { return Process.getUidForPid(pid); } } }
core/tests/coretests/src/com/android/internal/os/KernelCpuThreadReaderTest.java +113 −33 Original line number Diff line number Diff line Loading @@ -39,13 +39,16 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.function.Predicate; @SmallTest @RunWith(AndroidJUnit4.class) public class KernelCpuThreadReaderTest { private static final String PROCESS_NAME = "test_process"; private static final int UID = 1000; private static final int PROCESS_ID = 1234; private static final int[] THREAD_IDS = {0, 1000, 1235, 4321}; private static final String PROCESS_NAME = "test_process"; private static final String[] THREAD_NAMES = { "test_thread_1", "test_thread_2", "test_thread_3", "test_thread_4" }; Loading Loading @@ -73,49 +76,126 @@ public class KernelCpuThreadReaderTest { } @Test public void testSimple() throws IOException { // Make /proc/self final Path selfPath = mProcDirectory.toPath().resolve("self"); assertTrue(selfPath.toFile().mkdirs()); public void testReader_currentProcess() throws IOException { KernelCpuThreadReader.Injector processUtils = new KernelCpuThreadReader.Injector() { @Override public int myPid() { return PROCESS_ID; } @Override public int myUid() { return UID; } @Override public int getUidForPid(int pid) { return 0; } }; setupDirectory(mProcDirectory.toPath().resolve("self"), THREAD_IDS, PROCESS_NAME, THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES); final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( mProcDirectory.toPath(), mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state"), processUtils); final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = kernelCpuThreadReader.getCurrentProcessCpuUsage(); checkResults(processCpuUsage, kernelCpuThreadReader.getCpuFrequenciesKhz(), UID, PROCESS_ID, THREAD_IDS, PROCESS_NAME, THREAD_NAMES, THREAD_CPU_FREQUENCIES, THREAD_CPU_TIMES); } @Test public void testReader_byUids() throws IOException { int[] uids = new int[]{0, 2, 3, 4, 5, 6000}; Predicate<Integer> uidPredicate = uid -> uid == 0 || uid >= 4; int[] expectedUids = new int[]{0, 4, 5, 6000}; KernelCpuThreadReader.Injector processUtils = new KernelCpuThreadReader.Injector() { @Override public int myPid() { return 0; } // Make /proc/self/task final Path selfThreadsPath = selfPath.resolve("task"); @Override public int myUid() { return 0; } @Override public int getUidForPid(int pid) { return pid; } }; for (int uid : uids) { setupDirectory(mProcDirectory.toPath().resolve(String.valueOf(uid)), new int[]{uid * 10}, "process" + uid, new String[]{"thread" + uid}, new int[]{1000}, new int[][]{{uid}}); } final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( mProcDirectory.toPath(), mProcDirectory.toPath().resolve(uids[0] + "/task/" + uids[0] + "/time_in_state"), processUtils); ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsageByUids = kernelCpuThreadReader.getProcessCpuUsageByUids(uidPredicate); processCpuUsageByUids.sort(Comparator.comparing(usage -> usage.processId)); assertEquals(expectedUids.length, processCpuUsageByUids.size()); for (int i = 0; i < expectedUids.length; i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsageByUids.get(i); int uid = expectedUids[i]; checkResults(processCpuUsage, kernelCpuThreadReader.getCpuFrequenciesKhz(), uid, uid, new int[]{uid * 10}, "process" + uid, new String[]{"thread" + uid}, new int[]{1000}, new int[][]{{uid}}); } } private void setupDirectory(Path processPath, int[] threadIds, String processName, String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) throws IOException { // Make /proc/$PID assertTrue(processPath.toFile().mkdirs()); // Make /proc/$PID/task final Path selfThreadsPath = processPath.resolve("task"); assertTrue(selfThreadsPath.toFile().mkdirs()); // Make /proc/self/cmdline Files.write(selfPath.resolve("cmdline"), PROCESS_NAME.getBytes()); // Make /proc/$PID/cmdline Files.write(processPath.resolve("cmdline"), processName.getBytes()); // Make thread directories in reverse order, as they are read in order of creation by // CpuThreadProcReader for (int i = 0; i < THREAD_IDS.length; i++) { // Make /proc/self/task/$TID final Path threadPath = selfThreadsPath.resolve(String.valueOf(THREAD_IDS[i])); for (int i = 0; i < threadIds.length; i++) { // Make /proc/$PID/task/$TID final Path threadPath = selfThreadsPath.resolve(String.valueOf(threadIds[i])); assertTrue(threadPath.toFile().mkdirs()); // Make /proc/self/task/$TID/comm Files.write(threadPath.resolve("comm"), THREAD_NAMES[i].getBytes()); // Make /proc/$PID/task/$TID/comm Files.write(threadPath.resolve("comm"), threadNames[i].getBytes()); // Make /proc/self/task/$TID/time_in_state // Make /proc/$PID/task/$TID/time_in_state final OutputStream timeInStateStream = Files.newOutputStream(threadPath.resolve("time_in_state")); for (int j = 0; j < THREAD_CPU_FREQUENCIES.length; j++) { final String line = String.valueOf(THREAD_CPU_FREQUENCIES[j]) + " " + String.valueOf(THREAD_CPU_TIMES[i][j]) + "\n"; for (int j = 0; j < cpuFrequencies.length; j++) { final String line = String.valueOf(cpuFrequencies[j]) + " " + String.valueOf(cpuTimes[i][j]) + "\n"; timeInStateStream.write(line.getBytes()); } timeInStateStream.close(); } } final KernelCpuThreadReader kernelCpuThreadReader = new KernelCpuThreadReader( mProcDirectory.toPath(), mProcDirectory.toPath().resolve("self/task/" + THREAD_IDS[0] + "/time_in_state")); final KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = kernelCpuThreadReader.getCurrentProcessCpuUsage(); private void checkResults(KernelCpuThreadReader.ProcessCpuUsage processCpuUsage, int[] readerCpuFrequencies, int uid, int processId, int[] threadIds, String processName, String[] threadNames, int[] cpuFrequencies, int[][] cpuTimes) { assertNotNull(processCpuUsage); assertEquals(android.os.Process.myPid(), processCpuUsage.processId); assertEquals(android.os.Process.myUid(), processCpuUsage.uid); assertEquals(PROCESS_NAME, processCpuUsage.processName); assertEquals(processId, processCpuUsage.processId); assertEquals(uid, processCpuUsage.uid); assertEquals(processName, processCpuUsage.processName); // Sort the thread CPU usages to compare with test case final ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = Loading @@ -124,21 +204,21 @@ public class KernelCpuThreadReaderTest { int threadCount = 0; for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage : threadCpuUsages) { assertEquals(THREAD_IDS[threadCount], threadCpuUsage.threadId); assertEquals(THREAD_NAMES[threadCount], threadCpuUsage.threadName); assertEquals(threadIds[threadCount], threadCpuUsage.threadId); assertEquals(threadNames[threadCount], threadCpuUsage.threadName); for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) { assertEquals( THREAD_CPU_TIMES[threadCount][i] * 10, cpuTimes[threadCount][i] * 10, threadCpuUsage.usageTimesMillis[i]); assertEquals( THREAD_CPU_FREQUENCIES[i], kernelCpuThreadReader.getCpuFrequenciesKhz()[i]); cpuFrequencies[i], readerCpuFrequencies[i]); } threadCount++; } assertEquals(threadCount, THREAD_IDS.length); assertEquals(threadCount, threadIds.length); } @Test Loading
services/core/java/com/android/server/stats/StatsCompanionService.java +30 −25 Original line number Diff line number Diff line Loading @@ -1630,14 +1630,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { if (this.mKernelCpuThreadReader == null) { return; } KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = this.mKernelCpuThreadReader .getCurrentProcessCpuUsage(); if (processCpuUsage == null) { ArrayList<KernelCpuThreadReader.ProcessCpuUsage> processCpuUsages = this.mKernelCpuThreadReader.getProcessCpuUsageByUids(); if (processCpuUsages == null) { return; } int[] cpuFrequencies = mKernelCpuThreadReader.getCpuFrequenciesKhz(); for (KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage : processCpuUsage.threadCpuUsages) { for (int i = 0; i < processCpuUsages.size(); i++) { KernelCpuThreadReader.ProcessCpuUsage processCpuUsage = processCpuUsages.get(i); ArrayList<KernelCpuThreadReader.ThreadCpuUsage> threadCpuUsages = processCpuUsage.threadCpuUsages; for (int j = 0; j < threadCpuUsages.size(); j++) { KernelCpuThreadReader.ThreadCpuUsage threadCpuUsage = threadCpuUsages.get(j); if (threadCpuUsage.usageTimesMillis.length != cpuFrequencies.length) { Slog.w(TAG, "Unexpected number of usage times," + " expected " + cpuFrequencies.length Loading @@ -1645,9 +1649,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { continue; } for (int i = 0; i < threadCpuUsage.usageTimesMillis.length; i++) { for (int k = 0; k < threadCpuUsage.usageTimesMillis.length; k++) { // Do not report CPU usage at a frequency when it's zero if (threadCpuUsage.usageTimesMillis[i] == 0) { if (threadCpuUsage.usageTimesMillis[k] == 0) { continue; } Loading @@ -1658,12 +1662,13 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeInt(threadCpuUsage.threadId); e.writeString(processCpuUsage.processName); e.writeString(threadCpuUsage.threadName); e.writeInt(cpuFrequencies[i]); e.writeInt(threadCpuUsage.usageTimesMillis[i]); e.writeInt(cpuFrequencies[k]); e.writeInt(threadCpuUsage.usageTimesMillis[k]); pulledData.add(e); } } } } /** * Pulls various data. Loading