//
//  Copyright (c) 1994, 1995, 2015, 2016, 2018, 2021
//  by Mike Romberg ( romberg@fsl.noaa.gov )
//
//  NetBSD port:
//  Copyright (c) 1995, 1996, 1997-2002 by Brian Grayson (bgrayson@netbsd.org)
//
//  This file was originally written by Brian Grayson for the NetBSD and
//    xosview projects.
//  The NetBSD memmeter was improved by Tom Pavel (pavel@slac.stanford.edu)
//    to provide active and inactive values, rather than just "used."
//
//  This file may be distributed under terms of the GPL or of the BSD
//    license, whichever you choose.  The full license notices are
//    contained in the files COPYING.GPL and COPYING.BSD, which you
//    should have received.  If not, contact one of the xosview
//    authors for a copy.
//

#include "memmeter.h"
#include "sctl.h"

#if defined(HAVE_UVM)
#include <sys/device.h>
#include <uvm/uvm_extern.h>
#endif

#if defined(HAVE_VMMETER)
#include <sys/vmmeter.h>
#endif


#if defined(XOSVIEW_FREEBSD)
static const char * const LEGEND = "WRD/ACT/INACT/LND/BUF/FREE";
static const size_t NFIELDS = 6;
#else
static const char * const LEGEND = "WRD/ACT/INACT/CA/FREE";
static const size_t NFIELDS = 5;
#endif



MemMeter::MemMeter(void)
    : FieldMeterGraph(NFIELDS, "MEM", LEGEND),
      _meminfo(numfields(), 0) {
}


void MemMeter::checkResources(const ResDB &rdb) {

    FieldMeterGraph::checkResources(rdb);

#if defined(XOSVIEW_FREEBSD)
    setfieldcolor(0, rdb.getColor("memWiredColor"));
    setfieldcolor(1, rdb.getColor("memActiveColor"));
    setfieldcolor(2, rdb.getColor("memInactiveColor"));
    setfieldcolor(3, rdb.getColor("memOtherColor"));
    setfieldcolor(4, rdb.getColor("memCacheColor"));
    setfieldcolor(5, rdb.getColor("memFreeColor"));
#else
    setfieldcolor(0, rdb.getColor("memWiredColor"));
    setfieldcolor(1, rdb.getColor("memActiveColor"));
    setfieldcolor(2, rdb.getColor("memInactiveColor"));
    setfieldcolor(3, rdb.getColor("memCacheColor"));
    setfieldcolor(4, rdb.getColor("memFreeColor"));
#endif
}


void MemMeter::checkevent(void) {
    getmeminfo();
}


void MemMeter::getmeminfo(void) {
    getMemStats(_meminfo);
    _total = 0;
    for (size_t i = 0 ; i < _fields.size() ; i++) {
        _fields[i] = _meminfo[i];
        _total += _fields[i];
    }

    setUsed(_total - _fields.back(), _total);
}


#if defined(XOSVIEW_DFBSD)
void MemMeter::getMemStats(std::vector<uint64_t> &meminfo) const {

    static SysCtl vmstats_sc("vm.vmstats");

    struct vmstats vms;
    if (!vmstats_sc.get(vms))
        logFatal << vmstats_sc.callid() << " failed." << std::endl;

    meminfo.resize(numfields());
    meminfo[0] = vms.v_wire_count;
    meminfo[1] = vms.v_active_count;
    meminfo[2] = vms.v_inactive_count;
    meminfo[3] = vms.v_cache_count;
    meminfo[4] = vms.v_free_count;

    for (auto &v : meminfo)
        v *= vms.v_page_size;;
}
#endif


#if defined(XOSVIEW_FREEBSD)
void MemMeter::getMemStats(std::vector<uint64_t> &meminfo) const {

    static SysCtl psize_sc("hw.pagesize");
    static SysCtl physmem_sc("hw.realmem");
    static SysCtl wired_sc("vm.stats.vm.v_wire_count");
    static SysCtl active_sc("vm.stats.vm.v_active_count");
    static SysCtl inactive_sc("vm.stats.vm.v_inactive_count");
    static SysCtl buff_sc("vfs.bufspace");
    static SysCtl laundry_sc("vm.stats.vm.v_laundry_count");

    int psize = 0, active = 0, inactive = 0, wired = 0, laundry = 0;
    uint64_t physmem = 0;
    long buff = 0;
    try {
        psize = psize_sc.get<int>();
        physmem = physmem_sc.get<uint64_t>();
        active = active_sc.get<int>();
        inactive = inactive_sc.get<int>();
        wired = wired_sc.get<int>();
        buff = buff_sc.get<long>();
        laundry = laundry_sc.get<int>();
    }
    catch (std::system_error &e) {
        logFatal << e.what() << std::endl;
    }

    const int subtotal = active + inactive + wired + laundry + buff / psize;
    const uint64_t free = physmem - subtotal * psize;

    auto mb = [](uint64_t val) { return val / (1024 * 1000); };
    const uint64_t total = (active + inactive + wired) * psize + buff + free;

    logDebug << "== memory stats --\n"
             << "physmem: " << mb(physmem) << '\n'
             << "active : " << mb(active * psize) << '\n'
             << "iactive: " << mb(inactive * psize) << '\n'
             << "wired  : " << mb(wired * psize) << '\n'
             << "laundry: " << mb(laundry * psize) << '\n'
             << "buff   : " << mb(buff) << '\n'
             << "free   : " << mb(free) << '\n'
             << "total  : " << mb(total)
             << std::endl;

    meminfo.resize(numfields());
    meminfo[0] = wired * psize;
    meminfo[1] = active * psize;
    meminfo[2] = inactive * psize;
    meminfo[3] = laundry * psize;
    meminfo[4] = buff;
    meminfo[5] = free;
}
#endif


#if defined(XOSVIEW_NETBSD)
void MemMeter::getMemStats(std::vector<uint64_t> &meminfo) const {
    static SysCtl uvmexp2_sc("vm.uvmexp2");

    struct uvmexp_sysctl uvm;
    if (!uvmexp2_sc.get(uvm))
        logFatal << uvmexp2_sc.callid() << " failed." << std::endl;

    meminfo.resize(numfields());
    meminfo[0] = uvm.wired;
    // UVM excludes kernel memory -> assume it is active mem
    meminfo[1] = uvm.npages - uvm.inactive - uvm.wired - uvm.free;
    meminfo[2] = uvm.inactive;
    // cache is already included in active and inactive memory and
    // there's no way to know how much is in which -> disable cache
    meminfo[3] = 0;
    meminfo[4] = uvm.free;

    for (auto &v : meminfo)
        v *= uvm.pagesize;
}
#endif


#if defined(XOSVIEW_OPENBSD)
void MemMeter::getMemStats(std::vector<uint64_t> &meminfo) const {

    static SysCtl uvmexp_sc = { CTL_VM, VM_UVMEXP };

    struct uvmexp uvm;
    if (!uvmexp_sc.get(uvm))
        logFatal << uvmexp_sc.callid() << " failed." << std::endl;

    meminfo.resize(numfields());
    meminfo[0] = uvm.wired;
    // UVM excludes kernel memory -> assume it is active mem
    meminfo[1] = uvm.npages - uvm.inactive - uvm.wired - uvm.free;
    meminfo[2] = uvm.inactive;
    // cache is already included in active and inactive memory and
    // there's no way to know how much is in which -> disable cache
    meminfo[3] = 0;
    meminfo[4] = uvm.free;

    for (auto &v : meminfo)
        v *= uvm.pagesize;
}
#endif
