// SPDX-License-Identifier: GPL-3.0-or-later

#include "apps_plugin.h"

static inline void clear_pid_io(struct pid_stat *p) {
    p->io_logical_bytes_read        = 0;
    p->io_logical_bytes_written     = 0;
    p->io_read_calls                = 0;
    p->io_write_calls               = 0;
    p->io_storage_bytes_read        = 0;
    p->io_storage_bytes_written     = 0;
    p->io_cancelled_write_bytes     = 0;
}

#if defined(__FreeBSD__)
static inline bool read_proc_pid_io_per_os(struct pid_stat *p, void *ptr) {
    struct kinfo_proc *proc_info = (struct kinfo_proc *)ptr;

    pid_incremental_rate(io, p->io_storage_bytes_read,       proc_info->ki_rusage.ru_inblock);
    pid_incremental_rate(io, p->io_storage_bytes_written,    proc_info->ki_rusage.ru_oublock);

    p->io_logical_bytes_read = 0;
    p->io_logical_bytes_written = 0;
    p->io_read_calls = 0;
    p->io_write_calls = 0;
    p->io_cancelled_write_bytes = 0;

    return true;
}
#endif

#ifdef __APPLE__
static inline bool read_proc_pid_io_per_os(struct pid_stat *p, void *ptr) {
    struct pid_info *pi = ptr;

    // On MacOS, the proc_pid_rusage provides disk_io_statistics which includes io bytes read and written
    // but does not provide the same level of detail as Linux, like separating logical and physical I/O bytes.
    pid_incremental_rate(io, p->io_storage_bytes_read, pi->rusageinfo.ri_diskio_bytesread);
    pid_incremental_rate(io, p->io_storage_bytes_written, pi->rusageinfo.ri_diskio_byteswritten);

    p->io_logical_bytes_read = 0;
    p->io_logical_bytes_written = 0;
    p->io_read_calls = 0;
    p->io_write_calls = 0;
    p->io_cancelled_write_bytes = 0;

    return true;
}
#endif // __APPLE__

#if !defined(__FreeBSD__) && !defined(__APPLE__)
static inline int read_proc_pid_io_per_os(struct pid_stat *p, void *ptr __maybe_unused) {
    static procfile *ff = NULL;

    if(unlikely(!p->io_filename)) {
        char filename[FILENAME_MAX + 1];
        snprintfz(filename, FILENAME_MAX, "%s/proc/%d/io", netdata_configured_host_prefix, p->pid);
        p->io_filename = strdupz(filename);
    }

    // open the file
    ff = procfile_reopen(ff, p->io_filename, NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
    if(unlikely(!ff)) goto cleanup;

    ff = procfile_readall(ff);
    if(unlikely(!ff)) goto cleanup;

    pid_incremental_rate(io, p->io_logical_bytes_read,       str2kernel_uint_t(procfile_lineword(ff, 0,  1)));
    pid_incremental_rate(io, p->io_logical_bytes_written,    str2kernel_uint_t(procfile_lineword(ff, 1,  1)));
    pid_incremental_rate(io, p->io_read_calls,               str2kernel_uint_t(procfile_lineword(ff, 2,  1)));
    pid_incremental_rate(io, p->io_write_calls,              str2kernel_uint_t(procfile_lineword(ff, 3,  1)));
    pid_incremental_rate(io, p->io_storage_bytes_read,       str2kernel_uint_t(procfile_lineword(ff, 4,  1)));
    pid_incremental_rate(io, p->io_storage_bytes_written,    str2kernel_uint_t(procfile_lineword(ff, 5,  1)));
    pid_incremental_rate(io, p->io_cancelled_write_bytes,    str2kernel_uint_t(procfile_lineword(ff, 6,  1)));

    return true;

cleanup:
    clear_pid_io(p);
    return false;
}
#endif // !__FreeBSD__ !__APPLE__

int read_proc_pid_io(struct pid_stat *p, void *ptr) {
    p->last_io_collected_usec = p->io_collected_usec;
    p->io_collected_usec = now_monotonic_usec();
    calls_counter++;

    bool ret = read_proc_pid_io_per_os(p, ptr);

    if(unlikely(global_iterations_counter == 1))
        clear_pid_io(p);

    return ret ? 1 : 0;
}
