Add DiskIo to Statsd
Adds diskio information to statsd. The puller queries the
proc/uid_io/stats file to get cumulative counts since boot.
Bug: b/116331466
Test: unit test
Test: manually verified that the puller and "cat /proc/uid_io/stats"
returned almost identical output when executed at the same time
Change-Id: Iac446f8dd879ab6bf859eed6e779cc16fdee6c5b
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 30d8bfc..988ffc4 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -178,6 +178,7 @@
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 @@
}
/**
+ * 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 5fb196f..fd86714 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -211,6 +211,12 @@
// 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 0000000..9b03469
--- /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 efdd7e9..8360126 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 @@
/**
* 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 0000000..c051a1c
--- /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 3c64dd2..d0de940 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.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 @@
new KernelUidCpuActiveTimeReader();
private KernelUidCpuClusterTimeReader mKernelUidCpuClusterTimeReader =
new KernelUidCpuClusterTimeReader();
+ private StoragedUidIoStatsReader mStoragedUidIoStatsReader =
+ new StoragedUidIoStatsReader();
private static IThermalService sThermalService;
private File mBaseDir =
@@ -1333,6 +1336,27 @@
}
}
+ 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 @@
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;