12.優(yōu)化 - 文件IO監(jiān)控(matrix)

??關(guān)于文件的監(jiān)控,主要有那么幾個(gè)維度:主線程讀寫文件,讀寫文件時(shí)長,讀寫文件時(shí)buffer大小,文件使用后未關(guān)閉。主線程讀寫文件可能會(huì)堵塞主線程造成卡頓甚至 ANR;讀寫文件時(shí)buffer過小會(huì)造成應(yīng)用層代碼重復(fù)運(yùn)行,底層有可能有大量寫入問題(也有可能是累積到一頁大小寫入);文件使用后未關(guān)閉會(huì)造成資源的浪費(fèi),嚴(yán)重時(shí)會(huì)出現(xiàn) OOM 的問題。

監(jiān)聽文件使用后未關(guān)閉

  • 原理
        File file = new File(getApplication().getFilesDir(), "zz.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        int read = fileInputStream.read();
        fileInputStream.close();

??通常使用文件如上,打開文件流讀取文件后將流關(guān)閉。如果文件流不關(guān)閉的話,在未來的GC回收時(shí)(有可能)或者進(jìn)程快結(jié)束時(shí),會(huì)調(diào)用文件流的 finalize 方法。

    //FileInputStream.java
    protected void finalize() throws IOException {
        // Android-added: CloseGuard support.
        if (guard != null) {
            guard.warnIfOpen();
        }

        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            // Android-removed: Obsoleted comment about shared FileDescriptor handling.
            close();
        }
    }

??如果文件流寫代碼時(shí)忘記關(guān)閉的話,從finalize方法的調(diào)用中也會(huì)將其關(guān)閉,而且還會(huì)調(diào)用 guard.warnIfOpen();方法,來看看該方法

/* 
 * @hide
 */
public final class CloseGuard {
    private static volatile boolean ENABLED = true;
    private static volatile Reporter REPORTER = new DefaultReporter();
        public static void setEnabled(boolean enabled) {
        ENABLED = enabled;
    }

    public static boolean isEnabled() {
        return ENABLED;
    }

    public static void setReporter(Reporter reporter) {
        if (reporter == null) {
            throw new NullPointerException("reporter == null");
        }
        REPORTER = reporter;
    }

    public static Reporter getReporter() {
        return REPORTER;
    }
    public void close() {
        currentTracker.close(allocationSite);
        allocationSite = null;
    }
    //allocationSite 在 close 方法調(diào)用后為 null。
    public void warnIfOpen() {
        if (allocationSite == null || !ENABLED) {
            return;
        }

        String message =
                ("A resource was acquired at attached stack trace but never released. "
                 + "See java.io.Closeable for information on avoiding resource leaks.");

        REPORTER.report(message, allocationSite);
    }
    public interface Reporter {
        void report (String message, Throwable allocationSite);
    }
    private static final class DefaultReporter implements Reporter {
        @Override public void report (String message, Throwable allocationSite) {
            System.logW(message, allocationSite);
        }
    }
}

??可以看到如果文件未關(guān)閉的話,會(huì)調(diào)用接口類的一個(gè)report的方法,可以想到利用反射將REPORTER替換,就可以在文件流使用后自己未關(guān)閉而被自動(dòng)關(guān)閉時(shí)被調(diào)用report方法來進(jìn)行檢測。

代碼如下:

    private void startFileNotCloseProxy() throws Exception {
        Class<?> closeGuardClass = Class.forName("dalvik.system.CloseGuard");

        Method getReporter = closeGuardClass.getDeclaredMethod("getReporter", null);
        getReporter.setAccessible(true);
        originalReporter = getReporter.invoke(null);

        Method setEnableMethod = closeGuardClass.getDeclaredMethod("setEnabled", boolean.class);
        setEnableMethod.setAccessible(true);
        orginalEnabled = setEnableMethod.invoke(null);
        setEnableMethod.invoke(null, true);

        Class<?> reportInterfaceClass = Class.forName("dalvik.system.CloseGuard$Reporter");
        Object newReporter = Proxy.newProxyInstance(closeGuardClass.getClassLoader(), new Class[]{reportInterfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if ("report".equals(method.getName())) {
                    report();
                    Log.e("TAG", "文件打開沒有關(guān)閉");
                }
                return method.invoke(originalReporter, args);
            }
        });

        Method setReporter = closeGuardClass.getDeclaredMethod("setReporter", reportInterfaceClass);
        setReporter.setAccessible(true);
        setReporter.invoke(null, newReporter);
    }
  • 注意點(diǎn)

??在 android10 之后dalvik.system.CloseGuard不但被標(biāo)記為 hide,還被注解為@SystemApi。在反射獲取方法時(shí)會(huì)報(bào)錯(cuò),需注意版本兼容。

主線程讀寫文件,讀寫文件時(shí)長,讀寫文件時(shí)buffer大小

??這里主要是運(yùn)用了 xhook 的能力來監(jiān)控系統(tǒng)打開、讀寫、關(guān)閉文件的操作。在 matrix 代碼中監(jiān)控的維度有文件的路徑,java堆棧,線程名,讀寫的次數(shù),大小和,時(shí)間和,單次讀寫的最大耗時(shí),最小buffer等。并根據(jù)相應(yīng)的檢測邏輯判斷,如主線程檢測需判斷單次讀寫的最大耗時(shí)是否大于閾值,二次操作的間隙等。

  • hook 的代碼在 : io_canary_jni.cc

主要處理在文件打開時(shí)記錄其文件的路徑,java堆棧,單次讀寫的時(shí)間,關(guān)閉

  • 處理的代碼在 : io_canary.cc io_info_collector.cc

主要處理在聚合統(tǒng)計(jì)文件讀寫的維度,讀寫的次數(shù),大小和,時(shí)間和,單次讀寫的最大耗時(shí),最小buffer等

  • 檢測的代碼在 : 父類:detector.h 子類:reppeat_read_detector.cc main_thread_detector.cc small_buffer_detector.cc

子類各種處理自己關(guān)心的事情

  1. 主線程檢測需判斷單次讀寫的最大耗時(shí)是否大于閾值,二次操作的間隙。
  2. 小buffer檢測文件操作的次數(shù)大于閾值 20,平均每次操作buffer的大小小于4096 最長的一次操作時(shí)間大于 13*1000 μs。

matrix 到此分析結(jié)束。

給出參考 matrix 的代碼實(shí)現(xiàn):

//jni
#include <jni.h>
#include <string>
#include <android/log.h>
#include "xhook.h"
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include "IOCanary.h"

namespace iocanary {
    static int (*original_open)(const char *pathname, int flags, mode_t mode);

    static int (*original_open64)(const char *pathname, int flags, mode_t mode);

    static ssize_t (*original_read)(int fd, void *buf, size_t size);

    static ssize_t (*original_read_chk)(int fd, void *buf, size_t count, size_t buf_size);

    static ssize_t (*original_write)(int fd, const void *buf, size_t size);

    static ssize_t (*original_write_chk)(int fd, const void *buf, size_t count, size_t buf_size);

    static int (*original_close)(int fd);

    static int (*original_android_fdsan_close_with_tag)(int fd, uint64_t ownerId);

    int64_t getTickCount() {
        timeval tv;
        gettimeofday(&tv, 0);
        return (int64_t) tv.tv_sec * 1000000 + (int64_t) tv.tv_usec;
    }

    bool isMain() {
        return getpid() == gettid();
    }

    int proxy_open(const char *path, int flags, mode_t mode) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件打開:%s", path);

        int fd = original_open(path, flags, mode);
        iocanary::IOCanary::Get().OnOpen(fd, path, "java_stack");

        return fd;
    }

    int proxy_open64(const char *path, int flags, mode_t mode) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件打開64:%s", path);

        int fd = original_open64(path, flags, mode);
        iocanary::IOCanary::Get().OnOpen(fd, path, "");

        return fd;
    }

    ssize_t proxy_read(int fd, void *buf, size_t size) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件讀:%d  %d", fd, size);

        int64_t start = getTickCount();
        int readSize = original_read(fd, buf, size);
        ino64_t cost = getTickCount() - start;
        iocanary::IOCanary::Get().OnRead(fd, size, cost);
        return readSize;
    }

    ssize_t proxy_read_chk(int fd, void *buf, size_t count, size_t buf_size) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件讀chk:%d  %d", fd, buf_size);
        int64_t start = getTickCount();
        int readSize = original_read_chk(fd, buf, count, buf_size);
        ino64_t cost = getTickCount() - start;
        iocanary::IOCanary::Get().OnRead(fd, buf_size, cost);
        return readSize;
    }

    ssize_t proxy_write(int fd, const void *buf, size_t size) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件寫%d  %d", fd, size);
        int64_t start = getTickCount();
        int writeSize = original_write(fd, buf, size);
        ino64_t cost = getTickCount() - start;
        iocanary::IOCanary::Get().OnWrite(fd, size, cost);
        return writeSize;
    }

    ssize_t proxy_write_chk(int fd, const void *buf, size_t count, size_t buf_size) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件寫:%d  %d", fd, buf_size);
        int64_t start = getTickCount();
        int writeSize = original_write_chk(fd, buf, count, buf_size);
        ino64_t cost = getTickCount() - start;
        iocanary::IOCanary::Get().OnWrite(fd, buf_size, cost);
        return writeSize;
    }

    int proxy_close(int fd) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件關(guān)閉:%d", fd);

        int result = original_close(fd);
        iocanary::IOCanary::Get().OnClose(fd);
        return result;
    }

    int proxy_android_fdsan_close_with_tag(int fd, uint64_t ownerId) {
        __android_log_print(ANDROID_LOG_ERROR, "TAG", "監(jiān)聽到文件android_fdsan_close_with_tag:%d", fd);

        int result = original_android_fdsan_close_with_tag(fd, ownerId);
        iocanary::IOCanary::Get().OnClose(fd);
        return result;
    }
}

extern "C"
JNIEXPORT void JNICALL
Java_com_dabaicai_miniter_1hook_NativeBridge_fileIOHook(JNIEnv *env, jclass clazz, jboolean is_moniter_main) {
    iocanary::IOCanary::Get().setMoniterMain(is_moniter_main);
    //libopenjdkjvm.so
    xhook_register("libopenjdkjvm.so", "open", (void *) iocanary::proxy_open, (void **) &iocanary::original_open);
    xhook_register("libopenjdkjvm.so", "open64", (void *) iocanary::proxy_open64, (void **) &iocanary::original_open64);
    xhook_register("libopenjdkjvm.so", "close", (void *) iocanary::proxy_close, (void **) &iocanary::original_close);
    xhook_register("libopenjdkjvm.so", "android_fdsan_close_with_tag", (void *) iocanary::proxy_android_fdsan_close_with_tag,
                   (void **) &iocanary::original_android_fdsan_close_with_tag);

    //libjavacore.so
    xhook_register("libjavacore.so", "open", (void *) iocanary::proxy_open, (void **) &iocanary::original_open);
    xhook_register("libjavacore.so", "open64", (void *) iocanary::proxy_open64, (void **) &iocanary::original_open64);
    xhook_register("libjavacore.so", "close", (void *) iocanary::proxy_close, (void **) &iocanary::original_close);
    xhook_register("libjavacore.so", "android_fdsan_close_with_tag", (void *) iocanary::proxy_android_fdsan_close_with_tag,
                   (void **) &iocanary::original_android_fdsan_close_with_tag);
    if (xhook_register("libjavacore.so", "read", (void *) iocanary::proxy_read, (void **) &iocanary::original_read) != 0) {
        if (xhook_register("libjavacore.so", "__read_chk", (void *) iocanary::proxy_read_chk, (void **) &iocanary::original_read_chk) != 0) {
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "libjavacore hook read file");
        }
    }
    if (xhook_register("libjavacore.so", "write", (void *) iocanary::proxy_write, (void **) &iocanary::original_write) != 0) {
        if (xhook_register("libjavacore.so", "__write_chk", (void *) iocanary::proxy_write_chk, (void **) &iocanary::original_write_chk) != 0) {
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "libjavacore hook read file");
        }
    }


    //libopenjdk.so
    xhook_register("libopenjdk.so", "open", (void *) iocanary::proxy_open, (void **) &iocanary::original_open);
    xhook_register("libopenjdk.so", "open64", (void *) iocanary::proxy_open64, (void **) &iocanary::original_open64);
    xhook_register("libopenjdk.so", "close", (void *) iocanary::proxy_close, (void **) &iocanary::original_close);
    xhook_register("libopenjdk.so", "android_fdsan_close_with_tag", (void *) iocanary::proxy_android_fdsan_close_with_tag,
                   (void **) &iocanary::original_android_fdsan_close_with_tag);

    xhook_refresh(1);
    __android_log_print(ANDROID_LOG_ERROR, "TAG", "fileIOHook");
}
//header
#ifndef MONITER_IOCANARY_H
#define MONITER_IOCANARY_H

#include <string>
#include <map>
#include <jni.h>
#include <android/log.h>

namespace iocanary {


    class FileInfo {
    public:
        FileInfo(std::string path, std::string stack) : file_path(path), java_stack(stack) {}

        long cost_time = 0;
        long average_buffer_size = 0;
        long min_buffer_size = 0;
        long max_buffer_size = 0;
        int action_count = 0;
        std::string file_path;
        std::string java_stack;
    };

    class IOCanary {
    public:
        static IOCanary &Get();

        void setMoniterMain(bool isMoniterMain);

        void OnOpen(int fd, std::string file_path, std::string java_stack);

        void OnRead(int fd, int count, long cost_time);

        void OnWrite(int fd, int count, long cost_time);

        void OnClose(int fd);

    private:
        IOCanary();

        std::map<int, FileInfo *> file_map;
        bool isMoniterMain;
    };

}

#endif //MONITER_IOCANARY_H


//cpp
#include "IOCanary.h"

namespace iocanary {
    IOCanary::IOCanary() {

    }

    IOCanary &iocanary::IOCanary::Get() {
        static IOCanary kInstance;
        return kInstance;
    }

    void iocanary::IOCanary::setMoniterMain(bool isMoniterMain) {
        this->isMoniterMain = isMoniterMain;
    }

    void IOCanary::OnOpen(int fd, std::string file_path, std::string java_stack) {
        FileInfo *fileInfo = new FileInfo(file_path, java_stack);
        file_map.insert(std::make_pair(fd, fileInfo));
    }

    void IOCanary::OnRead(int fd, int count, long cost_time) {
        if (file_map.find(fd) == file_map.end()) {
            //沒有記錄
            return;
        }
        FileInfo *fileInfo = file_map.at(fd);
        fileInfo->cost_time += cost_time;
        fileInfo->action_count++;
        fileInfo->average_buffer_size += count;
        if (fileInfo->min_buffer_size > count) {
            fileInfo->min_buffer_size = count;
        }
        if (fileInfo->max_buffer_size < count) {
            fileInfo->max_buffer_size = count;
        }
    }

    void IOCanary::OnWrite(int fd, int count, long cost_time) {
        if (file_map.find(fd) == file_map.end()) {
            //沒有記錄
            return;
        }
        FileInfo *fileInfo = file_map.at(fd);
        fileInfo->cost_time += cost_time;
        fileInfo->action_count++;
        fileInfo->average_buffer_size += count;
        if (fileInfo->min_buffer_size > count) {
            fileInfo->min_buffer_size = count;
        }
        if (fileInfo->max_buffer_size < count) {
            fileInfo->max_buffer_size = count;
        }
    }

    void IOCanary::OnClose(int fd) {
        if (file_map.find(fd) == file_map.end()) {
            //沒有記錄
            return;
        }
        FileInfo *fileInfo = file_map.at(fd);
        fileInfo->average_buffer_size = fileInfo->average_buffer_size / fileInfo->action_count;
        if (isMoniterMain && fileInfo->cost_time > 100) {
            //主線程讀寫超時(shí)
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "主線程操作文件太耗時(shí):%d", fileInfo->cost_time);
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "%s", fileInfo->java_stack.c_str());
        }
        if (fileInfo->min_buffer_size < 4096 || fileInfo->average_buffer_size < 4096) {
            //讀寫小buffer 平均讀寫小buffer
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "最小緩沖區(qū):%ld 平均緩沖區(qū)域太?。?ld", fileInfo->min_buffer_size, fileInfo->average_buffer_size);
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "%s", fileInfo->java_stack.c_str());
        }


        file_map.erase(fd);
        delete fileInfo;
    }
}


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容