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

Commit e7726dc4 authored by Tej Singh's avatar Tej Singh
Browse files

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
parent c691c052
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -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.
@@ -2621,6 +2622,30 @@ message CategorySize {
    optional int64 cache_time_millis = 3;
}

/**
 * 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.
 *
+6 −0
Original line number Diff line number Diff line
@@ -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) {
+113 −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 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);
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -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)
+171 −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.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);
    }
}
Loading