/*
 * Linux /proc/vmstat metrics cluster
 *
 * Copyright (c) 2013-2014 Red Hat.
 * Copyright (c) 2007,2011 Aconex.  All Rights Reserved.
 * Copyright (c) 2004 Silicon Graphics, Inc.  All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

#include <ctype.h>
#include "pmapi.h"
#include "pmda.h"
#include "indom.h"
#include "proc_vmstat.h"

static struct {
    const char	*field;
    __uint64_t	*offset; 
} vmstat_fields[] = {
    /* sorted by name to make maintenance easier */
    { .field = "allocstall",
     .offset = &_pm_proc_vmstat.allocstall },
    { .field = "compact_blocks_moved",
     .offset = &_pm_proc_vmstat.compact_blocks_moved },
    { .field = "compact_fail",
     .offset = &_pm_proc_vmstat.compact_fail },
    { .field = "compact_pagemigrate_failed",
     .offset = &_pm_proc_vmstat.compact_pagemigrate_failed },
    { .field = "compact_pages_moved",
     .offset = &_pm_proc_vmstat.compact_pages_moved },
    { .field = "compact_stall",
     .offset = &_pm_proc_vmstat.compact_stall },
    { .field = "compact_success",
     .offset = &_pm_proc_vmstat.compact_success },
    { .field = "htlb_buddy_alloc_fail",
     .offset = &_pm_proc_vmstat.htlb_buddy_alloc_fail },
    { .field = "htlb_buddy_alloc_success",
     .offset = &_pm_proc_vmstat.htlb_buddy_alloc_success },
    { .field = "kswapd_inodesteal",
     .offset = &_pm_proc_vmstat.kswapd_inodesteal },
    { .field = "kswapd_low_wmark_hit_quickly",
     .offset = &_pm_proc_vmstat.kswapd_low_wmark_hit_quickly },
    { .field = "kswapd_high_wmark_hit_quickly",
     .offset = &_pm_proc_vmstat.kswapd_high_wmark_hit_quickly },
    { .field = "kswapd_skip_congestion_wait",
     .offset = &_pm_proc_vmstat.kswapd_skip_congestion_wait },
    { .field = "kswapd_steal",
     .offset = &_pm_proc_vmstat.kswapd_steal },
    { .field = "nr_active_anon",
     .offset = &_pm_proc_vmstat.nr_active_anon },
    { .field = "nr_active_file",
     .offset = &_pm_proc_vmstat.nr_active_file },
    { .field = "nr_anon_pages",
     .offset = &_pm_proc_vmstat.nr_anon_pages },
    { .field = "nr_anon_transparent_hugepages",
     .offset = &_pm_proc_vmstat.nr_anon_transparent_hugepages },
    { .field = "nr_bounce",
     .offset = &_pm_proc_vmstat.nr_bounce },
    { .field = "nr_dirty",
     .offset = &_pm_proc_vmstat.nr_dirty },
    { .field = "nr_dirtied",
     .offset = &_pm_proc_vmstat.nr_dirtied },
    { .field = "nr_dirty_threshold",
     .offset = &_pm_proc_vmstat.nr_dirty_threshold },
    { .field = "nr_dirty_background_threshold",
     .offset = &_pm_proc_vmstat.nr_dirty_background_threshold },
    { .field = "nr_file_pages",
     .offset = &_pm_proc_vmstat.nr_file_pages },
    { .field = "nr_free_pages",
     .offset = &_pm_proc_vmstat.nr_free_pages },
    { .field = "nr_inactive_anon",
     .offset = &_pm_proc_vmstat.nr_inactive_anon },
    { .field = "nr_inactive_file",
     .offset = &_pm_proc_vmstat.nr_inactive_file },
    { .field = "nr_isolated_anon",
     .offset = &_pm_proc_vmstat.nr_isolated_anon },
    { .field = "nr_isolated_file",
     .offset = &_pm_proc_vmstat.nr_isolated_file },
    { .field = "nr_kernel_stack",
     .offset = &_pm_proc_vmstat.nr_kernel_stack },
    { .field = "nr_mapped",
     .offset = &_pm_proc_vmstat.nr_mapped },
    { .field = "nr_mlock",
     .offset = &_pm_proc_vmstat.nr_mlock },
    { .field = "nr_page_table_pages",
     .offset = &_pm_proc_vmstat.nr_page_table_pages },
    { .field = "nr_shmem",
     .offset = &_pm_proc_vmstat.nr_shmem },
    { .field = "nr_slab_reclaimable",
     .offset = &_pm_proc_vmstat.nr_slab_reclaimable },
    { .field = "nr_slab_unreclaimable",
     .offset = &_pm_proc_vmstat.nr_slab_unreclaimable },
    { .field = "nr_slab",
     .offset = &_pm_proc_vmstat.nr_slab }, /* not in later kernels */
    { .field = "nr_unevictable",
     .offset = &_pm_proc_vmstat.nr_unevictable },
    { .field = "nr_unstable",
     .offset = &_pm_proc_vmstat.nr_unstable },
    { .field = "nr_vmscan_write",
     .offset = &_pm_proc_vmstat.nr_vmscan_write },
    { .field = "nr_writeback",
     .offset = &_pm_proc_vmstat.nr_writeback },
    { .field = "nr_writeback_temp",
     .offset = &_pm_proc_vmstat.nr_writeback_temp },
    { .field = "nr_written",
     .offset = &_pm_proc_vmstat.nr_written },
    { .field = "numa_hit",
     .offset = &_pm_proc_vmstat.numa_hit },
    { .field = "numa_miss",
     .offset = &_pm_proc_vmstat.numa_miss },
    { .field = "numa_foreign",
     .offset = &_pm_proc_vmstat.numa_foreign },
    { .field = "numa_interleave",
     .offset = &_pm_proc_vmstat.numa_interleave },
    { .field = "numa_local",
     .offset = &_pm_proc_vmstat.numa_local },
    { .field = "numa_other",
     .offset = &_pm_proc_vmstat.numa_other },
    { .field = "pageoutrun",
     .offset = &_pm_proc_vmstat.pageoutrun },
    { .field = "pgactivate",
     .offset = &_pm_proc_vmstat.pgactivate },
    { .field = "pgalloc_dma",
     .offset = &_pm_proc_vmstat.pgalloc_dma },
    { .field = "pgalloc_dma32",
     .offset = &_pm_proc_vmstat.pgalloc_dma32 },
    { .field = "pgalloc_high",
     .offset = &_pm_proc_vmstat.pgalloc_high },
    { .field = "pgalloc_movable",
     .offset = &_pm_proc_vmstat.pgalloc_movable },
    { .field = "pgalloc_normal",
     .offset = &_pm_proc_vmstat.pgalloc_normal },
    { .field = "pgdeactivate",
     .offset = &_pm_proc_vmstat.pgdeactivate },
    { .field = "pgfault",
     .offset = &_pm_proc_vmstat.pgfault },
    { .field = "pgfree",
     .offset = &_pm_proc_vmstat.pgfree },
    { .field = "pginodesteal",
     .offset = &_pm_proc_vmstat.pginodesteal },
    { .field = "pgmajfault",
     .offset = &_pm_proc_vmstat.pgmajfault },
    { .field = "pgpgin",
     .offset = &_pm_proc_vmstat.pgpgin },
    { .field = "pgpgout",
     .offset = &_pm_proc_vmstat.pgpgout },
    { .field = "pgrefill_dma",
     .offset = &_pm_proc_vmstat.pgrefill_dma },
    { .field = "pgrefill_dma32",
     .offset = &_pm_proc_vmstat.pgrefill_dma32 },
    { .field = "pgrefill_high",
     .offset = &_pm_proc_vmstat.pgrefill_high },
    { .field = "pgrefill_movable",
     .offset = &_pm_proc_vmstat.pgrefill_movable },
    { .field = "pgrefill_normal",
     .offset = &_pm_proc_vmstat.pgrefill_normal },
    { .field = "pgrotated",
     .offset = &_pm_proc_vmstat.pgrotated },
    { .field = "pgscan_direct_dma",
     .offset = &_pm_proc_vmstat.pgscan_direct_dma },
    { .field = "pgscan_direct_dma32",
     .offset = &_pm_proc_vmstat.pgscan_direct_dma32 },
    { .field = "pgscan_direct_high",
     .offset = &_pm_proc_vmstat.pgscan_direct_high },
    { .field = "pgscan_direct_movable",
     .offset = &_pm_proc_vmstat.pgscan_direct_movable },
    { .field = "pgscan_direct_normal",
     .offset = &_pm_proc_vmstat.pgscan_direct_normal },
    { .field = "pgscan_kswapd_dma",
     .offset = &_pm_proc_vmstat.pgscan_kswapd_dma },
    { .field = "pgscan_kswapd_dma32",
     .offset = &_pm_proc_vmstat.pgscan_kswapd_dma32 },
    { .field = "pgscan_kswapd_high",
     .offset = &_pm_proc_vmstat.pgscan_kswapd_high },
    { .field = "pgscan_kswapd_movable",
     .offset = &_pm_proc_vmstat.pgscan_kswapd_movable },
    { .field = "pgscan_kswapd_normal",
     .offset = &_pm_proc_vmstat.pgscan_kswapd_normal },
    { .field = "pgsteal_dma",
     .offset = &_pm_proc_vmstat.pgsteal_dma },
    { .field = "pgsteal_dma32",
     .offset = &_pm_proc_vmstat.pgsteal_dma32 },
    { .field = "pgsteal_high",
     .offset = &_pm_proc_vmstat.pgsteal_high },
    { .field = "pgsteal_movable",
     .offset = &_pm_proc_vmstat.pgsteal_movable },
    { .field = "pgsteal_normal",
     .offset = &_pm_proc_vmstat.pgsteal_normal },
    { .field = "pswpin",
     .offset = &_pm_proc_vmstat.pswpin },
    { .field = "pswpout",
     .offset = &_pm_proc_vmstat.pswpout },
    { .field = "slabs_scanned",
     .offset = &_pm_proc_vmstat.slabs_scanned },
    { .field = "thp_collapse_alloc",
     .offset = &_pm_proc_vmstat.thp_collapse_alloc },
    { .field = "thp_collapse_alloc_failed",
     .offset = &_pm_proc_vmstat.thp_collapse_alloc_failed },
    { .field = "thp_fault_alloc",
     .offset = &_pm_proc_vmstat.thp_fault_alloc },
    { .field = "thp_fault_fallback",
     .offset = &_pm_proc_vmstat.thp_fault_fallback },
    { .field = "thp_split",
     .offset = &_pm_proc_vmstat.thp_split },
    { .field = "unevictable_pgs_cleared",
     .offset = &_pm_proc_vmstat.unevictable_pgs_cleared },
    { .field = "unevictable_pgs_culled",
     .offset = &_pm_proc_vmstat.unevictable_pgs_culled },
    { .field = "unevictable_pgs_mlocked",
     .offset = &_pm_proc_vmstat.unevictable_pgs_mlocked },
    { .field = "unevictable_pgs_mlockfreed",
     .offset = &_pm_proc_vmstat.unevictable_pgs_mlockfreed },
    { .field = "unevictable_pgs_munlocked",
     .offset = &_pm_proc_vmstat.unevictable_pgs_munlocked },
    { .field = "unevictable_pgs_rescued",
     .offset = &_pm_proc_vmstat.unevictable_pgs_rescued },
    { .field = "unevictable_pgs_scanned",
     .offset = &_pm_proc_vmstat.unevictable_pgs_scanned },
    { .field = "unevictable_pgs_stranded",
     .offset = &_pm_proc_vmstat.unevictable_pgs_stranded },
    { .field = "zone_reclaim_failed",
     .offset = &_pm_proc_vmstat.zone_reclaim_failed },

    { .field = NULL, .offset = NULL }
};

#define VMSTAT_OFFSET(ii, pp) (int64_t *)((char *)pp + \
    (__psint_t)vmstat_fields[ii].offset - (__psint_t)&_pm_proc_vmstat)

void
proc_vmstat_init(void)
{
    char	buf[1024];

    /*
     * The swap metrics moved from /proc/stat to /proc/vmstat early in 2.6.
     * In addition, the swap operation count was removed; the fetch routine
     * needs to deal with these quirks and return something sensible based
     * (initially) on whether the vmstat file exists.
     *
     * We'll re-evaluate this on each fetch of the mem.vmstat metrics, but
     * that is not a problem.  This routine makes sure any swap.xxx metric
     * fetch without a preceding mem.vmstat fetch has the correct state.
     */
    snprintf(buf, sizeof(buf), "%s/proc/vmstat", linux_statspath);
    _pm_have_proc_vmstat = (access(buf, R_OK) == 0);
}

int
refresh_proc_vmstat(proc_vmstat_t *proc_vmstat)
{
    char	buf[1024];
    char	*bufp;
    int64_t	*p;
    int		i;
    FILE	*fp;

    for (i = 0; vmstat_fields[i].field != NULL; i++) {
	p = VMSTAT_OFFSET(i, proc_vmstat);
	*p = -1; /* marked as "no value available" */
    }

    if ((fp = linux_statsfile("/proc/vmstat", buf, sizeof(buf))) == NULL)
    	return -oserror();

    _pm_have_proc_vmstat = 1;

    while (fgets(buf, sizeof(buf), fp) != NULL) {
	if ((bufp = strchr(buf, ' ')) == NULL)
	    continue;
	*bufp = '\0';
	for (i = 0; vmstat_fields[i].field != NULL; i++) {
	    if (strcmp(buf, vmstat_fields[i].field) != 0)
		continue;
	    p = VMSTAT_OFFSET(i, proc_vmstat);
	    for (bufp++; *bufp; bufp++) {
	    	if (isdigit((int)*bufp)) {
		    sscanf(bufp, "%llu", (unsigned long long *)p);
		    break;
		}
	    }
	}
    }
    fclose(fp);

    if (proc_vmstat->nr_slab == -1)	/* split apart in 2.6.18 */
	proc_vmstat->nr_slab = proc_vmstat->nr_slab_reclaimable +
				proc_vmstat->nr_slab_unreclaimable;
    return 0;
}
