diff options
6 files changed, 344 insertions, 1 deletions
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 30d8bfce5d2a..988ffc4d95b7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -178,6 +178,7 @@ message Atom { BatteryVoltage battery_voltage = 10030; NumFingerprints num_fingerprints = 10031; ProcStats proc_stats = 10029; + DiskIo disk_io = 10032; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -2622,6 +2623,30 @@ message CategorySize { } /** + * Pulls per uid I/O stats. The stats are cumulative since boot. + * + * Read/write bytes are I/O events from a storage device + * Read/write chars are data requested by read/write syscalls, and can be + * satisfied by caching. + * + * Pulled from StatsCompanionService, which reads proc/uid_io/stats. + */ +message DiskIo { + optional int32 uid = 1 [(is_uid) = true]; + optional int64 fg_chars_read = 2; + optional int64 fg_chars_write = 3; + optional int64 fg_bytes_read = 4; + optional int64 fg_bytes_write = 5; + optional int64 bg_chars_read = 6; + optional int64 bg_chars_write = 7; + optional int64 bg_bytes_read = 8; + optional int64 bg_bytes_write = 9; + optional int64 fg_fsync = 10; + optional int64 bg_fsync= 11; +} + + +/** * Pulls the number of fingerprints for each user. * * Pulled from StatsCompanionService, which queries FingerprintManager. diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 5fb196ff4884..fd8671406051 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -211,6 +211,12 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { // ProcStats. {android::util::PROC_STATS, {{}, {}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROC_STATS)}}, + // Disk I/O stats per uid. + {android::util::DISK_IO, + {{2,3,4,5,6,7,8,9,10,11}, + {}, + 3 * NS_PER_SEC, + new StatsCompanionServicePuller(android::util::DISK_IO)}}, }; StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) { diff --git a/core/java/com/android/internal/os/StoragedUidIoStatsReader.java b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java new file mode 100644 index 000000000000..9b0346923cd3 --- /dev/null +++ b/core/java/com/android/internal/os/StoragedUidIoStatsReader.java @@ -0,0 +1,113 @@ +/* + * 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 android.os.StrictMode; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + + +/** + * Reads /proc/uid_io/stats which has the line format: + * + * uid: foreground_read_chars foreground_write_chars foreground_read_bytes foreground_write_bytes + * background_read_chars background_write_chars background_read_bytes background_write_bytes + * foreground_fsync background_fsync + * + * This provides the number of bytes/chars read/written in foreground/background for each uid. + * The file contains a monotonically increasing count of bytes/chars for a single boot. + */ +public class StoragedUidIoStatsReader { + + private static final String TAG = StoragedUidIoStatsReader.class.getSimpleName(); + private static String sUidIoFile = "/proc/uid_io/stats"; + + public StoragedUidIoStatsReader() { + } + + @VisibleForTesting + public StoragedUidIoStatsReader(String file) { + sUidIoFile = file; + } + + /** + * Notifies when new data is available. + */ + public interface Callback { + + /** + * Provides data to the client. + * + * Note: Bytes are I/O events from a storage device. Chars are data requested by syscalls, + * and can be satisfied by caching. + */ + void onUidStorageStats(int uid, long fgCharsRead, long fgCharsWrite, long fgBytesRead, + long fgBytesWrite, long bgCharsRead, long bgCharsWrite, long bgBytesRead, + long bgBytesWrite, long fgFsync, long bgFsync); + } + + /** + * Reads the proc file, calling into the callback with raw absolute value of I/O stats + * for each UID. + * + * @param callback The callback to invoke for each line of the proc file. + */ + public void readAbsolute(Callback callback) { + final int oldMask = StrictMode.allowThreadDiskReadsMask(); + File file = new File(sUidIoFile); + try (BufferedReader reader = Files.newBufferedReader(file.toPath())) { + String line; + while ((line = reader.readLine()) != null) { + String[] fields = TextUtils.split(line, " "); + if (fields.length != 11) { + Slog.e(TAG, "Malformed entry in " + sUidIoFile + ": " + line); + continue; + } + try { + final String uidStr = fields[0]; + final int uid = Integer.parseInt(fields[0], 10); + final long fgCharsRead = Long.parseLong(fields[1], 10); + final long fgCharsWrite = Long.parseLong(fields[2], 10); + final long fgBytesRead = Long.parseLong(fields[3], 10); + final long fgBytesWrite = Long.parseLong(fields[4], 10); + final long bgCharsRead = Long.parseLong(fields[5], 10); + final long bgCharsWrite = Long.parseLong(fields[6], 10); + final long bgBytesRead = Long.parseLong(fields[7], 10); + final long bgBytesWrite = Long.parseLong(fields[8], 10); + final long fgFsync = Long.parseLong(fields[9], 10); + final long bgFsync = Long.parseLong(fields[10], 10); + callback.onUidStorageStats(uid, fgCharsRead, fgCharsWrite, fgBytesRead, + fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite, + fgFsync, bgFsync); + } catch (NumberFormatException e) { + Slog.e(TAG, "Could not parse entry in " + sUidIoFile + ": " + e.getMessage()); + } + } + } catch (IOException e) { + Slog.e(TAG, "Failed to read " + sUidIoFile + ": " + e.getMessage()); + } finally { + StrictMode.setThreadPolicyMask(oldMask); + } + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java index efdd7e978853..8360126f3751 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuProcReaderTest.java @@ -43,7 +43,7 @@ import java.util.Random; /** * Test class for {@link KernelCpuProcReader}. * - * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReader + * $ atest FrameworksCoreTests:com.android.internal.os.KernelCpuProcReaderTest */ @SmallTest @RunWith(AndroidJUnit4.class) diff --git a/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java new file mode 100644 index 000000000000..c051a1cdf052 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/StoragedUidIoStatsReaderTest.java @@ -0,0 +1,171 @@ +/* + * 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.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; + +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 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.nio.file.Files; + + +/** + * Test class for {@link StoragedUidIoStatsReader}. + * + * To run it: + * atest FrameworksCoreTests:com.android.internal.os.StoragedUidIoStatsReaderTest + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class StoragedUidIoStatsReaderTest { + + private File mRoot; + private File mTestDir; + private File mTestFile; + // private Random mRand = new Random(); + + private StoragedUidIoStatsReader mStoragedUidIoStatsReader; + @Mock + private StoragedUidIoStatsReader.Callback mCallback; + + private Context getContext() { + return InstrumentationRegistry.getContext(); + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTestDir = getContext().getDir("test", Context.MODE_PRIVATE); + mRoot = getContext().getFilesDir(); + mTestFile = new File(mTestDir, "test.file"); + mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(mTestFile.getAbsolutePath()); + } + + @After + public void tearDown() throws Exception { + FileUtils.deleteContents(mTestDir); + FileUtils.deleteContents(mRoot); + } + + + /** + * Tests that reading will never call the callback. + */ + @Test + public void testReadNonexistentFile() throws Exception { + mStoragedUidIoStatsReader.readAbsolute(mCallback); + verifyZeroInteractions(mCallback); + + } + + /** + * Tests that reading a file with 3 uids works as expected. + */ + @Test + public void testReadExpected() throws Exception { + BufferedWriter bufferedWriter = Files.newBufferedWriter(mTestFile.toPath()); + int[] uids = {0, 100, 200}; + long[] fg_chars_read = {1L, 101L, 201L}; + long[] fg_chars_write = {2L, 102L, 202L}; + long[] fg_bytes_read = {3L, 103L, 203L}; + long[] fg_bytes_write = {4L, 104L, 204L}; + long[] bg_chars_read = {5L, 105L, 205L}; + long[] bg_chars_write = {6L, 106L, 206L}; + long[] bg_bytes_read = {7L, 107L, 207L}; + long[] bg_bytes_write = {8L, 108L, 208L}; + long[] fg_fsync = {9L, 109L, 209L}; + long[] bg_fsync = {10L, 110L, 210L}; + + for (int i = 0; i < uids.length; i++) { + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d %d\n", uids[i], fg_chars_read[i], + fg_chars_write[i], fg_bytes_read[i], fg_bytes_write[i], + bg_chars_read[i], bg_chars_write[i], bg_bytes_read[i], + bg_bytes_write[i], fg_fsync[i], bg_fsync[i])); + } + bufferedWriter.close(); + + mStoragedUidIoStatsReader.readAbsolute(mCallback); + for (int i = 0; i < uids.length; i++) { + verify(mCallback).onUidStorageStats(uids[i], fg_chars_read[i], fg_chars_write[i], + fg_bytes_read[i], fg_bytes_write[i], bg_chars_read[i], bg_chars_write[i], + bg_bytes_read[i], bg_bytes_write[i], fg_fsync[i], bg_fsync[i]); + } + verifyNoMoreInteractions(mCallback); + + } + + /** + * Tests that a line with less than 11 items is passed over. + */ + @Test + public void testLineDoesNotElevenEntries() throws Exception { + BufferedWriter bufferedWriter = Files.newBufferedWriter(mTestFile.toPath()); + + // Only has 10 numbers. + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d\n", 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d %d\n", 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20)); + bufferedWriter.close(); + + // Make sure we get the second line, but the first is skipped. + mStoragedUidIoStatsReader.readAbsolute(mCallback); + verify(mCallback).onUidStorageStats(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); + verifyNoMoreInteractions(mCallback); + } + + + /** + * Tests that a line that is malformed is passed over. + */ + @Test + public void testLineIsMalformed() throws Exception { + BufferedWriter bufferedWriter = Files.newBufferedWriter(mTestFile.toPath()); + + // Line is not formatted properly. It has a string. + bufferedWriter.write(String + .format("%d %d %d %d %d %s %d %d %d %d %d\n", 0, 1, 2, 3, 4, "NotANumber", 5, 6, 7, + 8, 9)); + + bufferedWriter.write(String + .format("%d %d %d %d %d %d %d %d %d %d %d\n", 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20)); + bufferedWriter.close(); + + // Make sure we get the second line, but the first is skipped. + mStoragedUidIoStatsReader.readAbsolute(mCallback); + verify(mCallback).onUidStorageStats(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20); + verifyNoMoreInteractions(mCallback); + } +} diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 3c64dd278f78..d0de9409c49e 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -82,6 +82,7 @@ import com.android.internal.os.KernelWakelockReader; import com.android.internal.os.KernelWakelockStats; import com.android.internal.os.LooperStats; import com.android.internal.os.PowerProfile; +import com.android.internal.os.StoragedUidIoStatsReader; import com.android.internal.util.DumpUtils; import com.android.server.BinderCallsStatsService; import com.android.server.LocalServices; @@ -178,6 +179,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { new KernelUidCpuActiveTimeReader(); private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader = new KernelUidCpuClusterTimeReader(); + private StoragedUidIoStatsReader mStoragedUidIoStatsReader = + new StoragedUidIoStatsReader(); private static IThermalService sThermalService; private File mBaseDir = @@ -1333,6 +1336,27 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } } + private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos, + List<StatsLogEventWrapper> pulledData) { + mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead, + fgBytesWrite, bgCharsRead, bgCharsWrite, bgBytesRead, bgBytesWrite, + fgFsync, bgFsync) -> { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeInt(uid); + e.writeLong(fgCharsRead); + e.writeLong(fgCharsWrite); + e.writeLong(fgBytesRead); + e.writeLong(fgBytesWrite); + e.writeLong(bgCharsRead); + e.writeLong(bgCharsWrite); + e.writeLong(bgBytesRead); + e.writeLong(bgBytesWrite); + e.writeLong(fgFsync); + e.writeLong(bgFsync); + pulledData.add(e); + }); + } + /** * Pulls various data. */ @@ -1450,6 +1474,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullProcessStats(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.DISK_IO: { + pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret); + break; + } default: Slog.w(TAG, "No such tagId data as " + tagId); return null; |