12.优化 - 文件IO监控(matrix)

  关于文件的监控,主要有那么几个维度:主线程读写文件,读写文件时长,读写文件时buffer大小,文件使用后未关闭。主线程读写文件可能会堵塞主线程造成卡顿甚至 ANR;读写文件时buffer过小会造成应用层代码重复运行,底层有可能有大量写入问题(也有可能是累积到一页大小写入);文件使用后未关闭会造成资源的浪费,严重时会出现 OOM 的问题。

监听文件使用后未关闭

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

  通常使用文件如上,打开文件流读取文件后将流关闭。如果文件流不关闭的话,在未来的GC回收时(有可能)或者进程快结束时,会调用文件流的 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();
        }
    }

  如果文件流写代码时忘记关闭的话,从finalize方法的调用中也会将其关闭,而且还会调用 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 方法调用后为 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);
        }
    }
}

  可以看到如果文件未关闭的话,会调用接口类的一个report的方法,可以想到利用反射将REPORTER替换,就可以在文件流使用后自己未关闭而被自动关闭时被调用report方法来进行检测。

代码如下:

    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", "文件打开没有关闭");
                }
                return method.invoke(originalReporter, args);
            }
        });

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

  在 android10 之后dalvik.system.CloseGuard不但被标记为 hide,还被注解为@SystemApi。在反射获取方法时会报错,需注意版本兼容。

主线程读写文件,读写文件时长,读写文件时buffer大小

  这里主要是运用了 xhook 的能力来监控系统打开、读写、关闭文件的操作。在 matrix 代码中监控的维度有文件的路径,java堆栈,线程名,读写的次数,大小和,时间和,单次读写的最大耗时,最小buffer等。并根据相应的检测逻辑判断,如主线程检测需判断单次读写的最大耗时是否大于阈值,二次操作的间隙等。

  • hook 的代码在 : io_canary_jni.cc

主要处理在文件打开时记录其文件的路径,java堆栈,单次读写的时间,关闭

  • 处理的代码在 : io_canary.cc io_info_collector.cc

主要处理在聚合统计文件读写的维度,读写的次数,大小和,时间和,单次读写的最大耗时,最小buffer等

  • 检测的代码在 : 父类:detector.h 子类:reppeat_read_detector.cc main_thread_detector.cc small_buffer_detector.cc

子类各种处理自己关心的事情

  1. 主线程检测需判断单次读写的最大耗时是否大于阈值,二次操作的间隙。
  2. 小buffer检测文件操作的次數大于阈值 20,平均每次操作buffer的大小小于4096 最長的一次操作時間大于 13*1000 μs。

matrix 到此分析结束。

给出参考 matrix 的代码实现:

//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", "监听到文件打开:%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", "监听到文件打开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", "监听到文件读:%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", "监听到文件读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", "监听到文件写%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", "监听到文件写:%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", "监听到文件关闭:%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", "监听到文件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) {
            //主线程读写超时
            __android_log_print(ANDROID_LOG_ERROR, "TAG", "主线程操作文件太耗时:%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", "最小缓冲区:%ld 平均缓冲区域太小:%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;
    }
}


©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容