/*
 * FreeBSD Kernel PMDA
 *
 * Copyright (c) 2012 Red Hat.
 * Copyright (c) 2012 Ken McDonell.  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 "pmapi.h"
#include "impl.h"
#include "pmda.h"

#include <limits.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <vm/vm_param.h>

#include "domain.h"
#include "freebsd.h"

/* static instances */
static pmdaInstid loadav_indom[] = {
    { 1, "1 minute" }, { 5, "5 minute" }, { 15, "15 minute" }
};

/* instance domains */
pmdaIndom indomtab[] = {
    { LOADAV_INDOM, sizeof(loadav_indom)/sizeof(loadav_indom[0]), loadav_indom },
    { CPU_INDOM, 0, NULL },
    { DISK_INDOM, 0, NULL },
    { NETIF_INDOM, 0, NULL },
};
static int indomtablen = sizeof(indomtab) / sizeof(indomtab[0]);

#define CL_SYSCTL	0
#define CL_SPECIAL	1
#define CL_DISK		2
#define CL_NETIF	3

/*
 * All the PCP metrics.
 *
 * For sysctl metrics, m_user (the first field) is set the the PCP
 * name of the metric, and during initialization this is replaced by
 * a pointer to the corresponding entry in mib[] (based on matching,
 * or prefix matching (see matchname()) the PCP name here with
 * m_pcpname[] in the mib[] entries.
 *
 * cluster map
 * CL_SYSCTL	simple sysctl() metrics, either one metric per mib, or
 *		one struct per mib
 * CL_SPECIAL	trickier sysctl() metrics involving synthesis or arithmetic
 *		or other methods
 * CL_DISK	disk metrics
 * CL_NETIF	network interface metrics
 */

static pmdaMetric metrictab[] = {
    { (void *)"hinv.ncpu",
      { PMDA_PMID(CL_SYSCTL,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { (void *)"hinv.physmem",
      { PMDA_PMID(CL_SYSCTL,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { (void *)"kernel.all.load",
      { PMDA_PMID(CL_SYSCTL,2), PM_TYPE_FLOAT, LOADAV_INDOM, PM_SEM_INSTANT,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { (void *)"kernel.all.cpu.user",
      { PMDA_PMID(CL_SYSCTL,3), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.all.cpu.nice",
      { PMDA_PMID(CL_SYSCTL,4), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.all.cpu.sys",
      { PMDA_PMID(CL_SYSCTL,5), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.all.cpu.intr",
      { PMDA_PMID(CL_SYSCTL,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.all.cpu.idle",
      { PMDA_PMID(CL_SYSCTL,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.percpu.cpu.user",
      { PMDA_PMID(CL_SYSCTL,8), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.percpu.cpu.nice",
      { PMDA_PMID(CL_SYSCTL,9), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.percpu.cpu.sys",
      { PMDA_PMID(CL_SYSCTL,10), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.percpu.cpu.intr",
      { PMDA_PMID(CL_SYSCTL,11), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.percpu.cpu.idle",
      { PMDA_PMID(CL_SYSCTL,12), PM_TYPE_U64, CPU_INDOM, PM_SEM_COUNTER, 
	PMDA_PMUNITS(0,1,0,0,PM_TIME_MSEC,0) } },
    { (void *)"kernel.all.hz",
      { PMDA_PMID(CL_SYSCTL,13), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(0,1,0,0,PM_TIME_USEC,0) } },
    { (void *)"hinv.cpu.vendor",
      { PMDA_PMID(CL_SYSCTL,15), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { (void *)"hinv.cpu.model",
      { PMDA_PMID(CL_SYSCTL,16), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { (void *)"hinv.cpu.arch",
      { PMDA_PMID(CL_SYSCTL,17), PM_TYPE_STRING, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { (void *)"swap.pagesin",
      { PMDA_PMID(CL_SYSCTL,18), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"swap.pagesout",
      { PMDA_PMID(CL_SYSCTL,19), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"swap.in",
      { PMDA_PMID(CL_SYSCTL,20), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"swap.in",
      { PMDA_PMID(CL_SYSCTL,21), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"kernel.all.pswitch",
      { PMDA_PMID(CL_SYSCTL,22), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"kernel.all.syscall",
      { PMDA_PMID(CL_SYSCTL,23), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"kernel.all.intr",
      { PMDA_PMID(CL_SYSCTL,24), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { (void *)"swap.length",
      { PMDA_PMID(CL_SYSCTL,25), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { (void *)"swap.used",
      { PMDA_PMID(CL_SYSCTL,26), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },

    { NULL,	/* hinv.ndisk */
      { PMDA_PMID(CL_SPECIAL,0), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    /*
     * swap.free is the difference between sysctl variables vm.swap_total
     * and vm.swap_reserved, so it is important they are kept together
     * in mib[] below
     */
    { (void *)"swap.length",	/* swap.free */
      { PMDA_PMID(CL_SPECIAL,1), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* hinv.pagesize */
      { PMDA_PMID(CL_SPECIAL,3), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_DISCRETE,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { (void *)"mem.util.all",
      { PMDA_PMID(CL_SPECIAL,4), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    /*
     * mem.util.used is computed from several of the vm.stats.vm.v_*_count
     * sysctl metrics, so it is important they are kept together in mib[]
     * below
     */
    { (void *)"mem.util.all",	/* mem.util.used */
      { PMDA_PMID(CL_SPECIAL,5), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    { (void *)"mem.util.free",
      { PMDA_PMID(CL_SPECIAL,6), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    { (void *)"mem.util.bufmem",
      { PMDA_PMID(CL_SPECIAL,7), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    { (void *)"mem.util.cached",
      { PMDA_PMID(CL_SPECIAL,8), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    { (void *)"mem.util.wired",
      { PMDA_PMID(CL_SPECIAL,9), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    { (void *)"mem.util.active",
      { PMDA_PMID(CL_SPECIAL,10), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    { (void *)"mem.util.inactive",
      { PMDA_PMID(CL_SPECIAL,11), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },
    /*
     * mem.util.avail is computed from several of the vm.stats.vm.v_*_count
     * sysctl metrics, so it is important they are kept together in mib[]
     * below
     */
    { (void *)"mem.util.all",	/* mem.util.avail */
      { PMDA_PMID(CL_SPECIAL,12), PM_TYPE_U32, PM_INDOM_NULL, PM_SEM_INSTANT,
	PMDA_PMUNITS(1,0,0,PM_SPACE_KBYTE,0,0) } },

    { NULL,	/* disk.dev.read */
      { PMDA_PMID(CL_DISK,0), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.dev.write */
      { PMDA_PMID(CL_DISK,1), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.dev.total */
      { PMDA_PMID(CL_DISK,2), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.dev.read_bytes */
      { PMDA_PMID(CL_DISK,3), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* disk.dev.write_bytes */
      { PMDA_PMID(CL_DISK,4), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* disk.dev.total_bytes */
      { PMDA_PMID(CL_DISK,5), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* disk.all.read */
      { PMDA_PMID(CL_DISK,6), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.all.write */
      { PMDA_PMID(CL_DISK,7), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.all.total */
      { PMDA_PMID(CL_DISK,8), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.all.read_bytes */
      { PMDA_PMID(CL_DISK,9), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* disk.all.write_bytes */
      { PMDA_PMID(CL_DISK,10), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* disk.all.total_bytes */
      { PMDA_PMID(CL_DISK,11), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* disk.dev.blkread */
      { PMDA_PMID(CL_DISK,12), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.dev.blkwrite */
      { PMDA_PMID(CL_DISK,13), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.dev.blktotal */
      { PMDA_PMID(CL_DISK,14), PM_TYPE_U64, DISK_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.all.blkread */
      { PMDA_PMID(CL_DISK,15), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.all.blkwrite */
      { PMDA_PMID(CL_DISK,16), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* disk.all.blktotal */
      { PMDA_PMID(CL_DISK,17), PM_TYPE_U64, PM_INDOM_NULL, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },

    { NULL,	/* network.interface.mtu */
      { PMDA_PMID(CL_NETIF,0), PM_TYPE_U32, NETIF_INDOM, PM_SEM_INSTANT,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { NULL,	/* network.interface.up */
      { PMDA_PMID(CL_NETIF,1), PM_TYPE_U32, NETIF_INDOM, PM_SEM_INSTANT,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { NULL,	/* network.interface.baudrate */
      { PMDA_PMID(CL_NETIF,2), PM_TYPE_U64, NETIF_INDOM, PM_SEM_INSTANT,
	PMDA_PMUNITS(0,0,0,0,0,0) } },
    { NULL,	/* network.interface.in.bytes */
      { PMDA_PMID(CL_NETIF,3), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* network.interface.in.packets */
      { PMDA_PMID(CL_NETIF,4), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.in.mcasts */
      { PMDA_PMID(CL_NETIF,5), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.in.errors */
      { PMDA_PMID(CL_NETIF,6), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.in.drops */
      { PMDA_PMID(CL_NETIF,7), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.out.bytes */
      { PMDA_PMID(CL_NETIF,8), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* network.interface.out.packets */
      { PMDA_PMID(CL_NETIF,9), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.out.mcasts */
      { PMDA_PMID(CL_NETIF,10), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.out.errors */
      { PMDA_PMID(CL_NETIF,11), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.out.collisions */
      { PMDA_PMID(CL_NETIF,12), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.total.bytes */
      { PMDA_PMID(CL_NETIF,13), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(1,0,0,PM_SPACE_BYTE,0,0) } },
    { NULL,	/* network.interface.total.packets */
      { PMDA_PMID(CL_NETIF,14), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.total.mcasts */
      { PMDA_PMID(CL_NETIF,15), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
    { NULL,	/* network.interface.total.errors */
      { PMDA_PMID(CL_NETIF,16), PM_TYPE_U64, NETIF_INDOM, PM_SEM_COUNTER,
	PMDA_PMUNITS(0,0,1,0,0,PM_COUNT_ONE) } },
};
static int metrictablen = sizeof(metrictab) / sizeof(metrictab[0]);

/*
 * mapping between PCP metrics and sysctl metrics
 *
 * initialization note ... all elments after m_pcpname and m_name are OK
 * to be 0 or NULL
 */
typedef struct {
    const char	*m_pcpname;		/* PCP metric name or prefix (see matchname() */
    const char	*m_name;		/* sysctl metric name */
    size_t	m_miblen;		/* number of elements in m_mib[] */
    int		*m_mib;
    int		m_fetched;		/* 1 if m_data is current and valid */
    size_t	m_datalen;		/* number of bytes in m_data[] */
    void	*m_data;		/* value from sysctl */
} mib_t;
static mib_t map[] = {
    { "hinv.ncpu",		"hw.ncpu" },
    { "hinv.physmem",		"hw.physmem" },
    { "hinv.cpu.vendor",	"hw.machine" },
    { "hinv.cpu.model",		"hw.model" },
    { "hinv.cpu.arch",		"hw.machine_arch" },
    { "kernel.all.load",	"vm.loadavg" },
    { "kernel.all.hz",		"kern.clockrate" },
    { "kernel.all.cpu.*",	"kern.cp_time" },
    { "kernel.percpu.cpu.*",	"kern.cp_times" },
    { "swap.pagesin",		"vm.stats.vm.v_swappgsin" },
    { "swap.pagesout",		"vm.stats.vm.v_swappgsout" },
    { "swap.in",		"vm.stats.vm.v_swapin" },
    { "swap.out",		"vm.stats.vm.v_swapout" },
    { "kernel.all.pswitch",	"vm.stats.sys.v_swtch" },
    { "kernel.all.syscall",	"vm.stats.sys.v_syscall" },
    { "kernel.all.intr",	"vm.stats.sys.v_intr" },
    { "mem.util.bufmem",	"vfs.bufspace" },
/*
 * DO NOT MOVE next 2 entries ... see note above for swap.free
 */
    { "swap.length",		"vm.swap_total" },
    { "swap.used",		"vm.swap_reserved" },
/*
 * DO NOT MOVE next 6 entries ... see note above for mem.util.avail
 * and mem.util.used
 */
    { "mem.util.all",		"vm.stats.vm.v_page_count" },
    { "mem.util.free",		"vm.stats.vm.v_free_count" },
    { "mem.util.cached",	"vm.stats.vm.v_cache_count" },
    { "mem.util.wired",		"vm.stats.vm.v_wire_count" },
    { "mem.util.active",	"vm.stats.vm.v_active_count" },
    { "mem.util.inactive",	"vm.stats.vm.v_inactive_count" },

};
static int maplen = sizeof(map) / sizeof(map[0]);
static mib_t bad_mib = { "bad.mib", "bad.mib", 0, NULL, 0, 0, NULL };

static char	*username;
static int	isDSO = 1;	/* =0 I am a daemon */
static int	cpuhz;		/* frequency for CPU time metrics */
static int	ncpu;		/* number of cpus in kern.cp_times data */
static int	pagesize;	/* vm page size */

/*
 * Fetch values from sysctl()
 *
 * Expect the result to be xpect bytes to match the PCP data size or
 * anticipated structure size, unless xpect is ==0 in which case the
 * size test is skipped.
 */
static int
do_sysctl(mib_t *mp, size_t xpect)
{
    /*
     * Note zero trip if mp->m_data and mp->datalen are already valid
     * and current
     */
    for ( ; mp->m_fetched == 0; ) {
	int	sts;
	sts = sysctl(mp->m_mib, (u_int)mp->m_miblen, mp->m_data, &mp->m_datalen, NULL, 0);
#ifdef PCP_DEBUG
	if (pmDebug & DBG_TRACE_APPL0) {
	    fprintf(stderr, "sysctl(%s%s) -> %d (datalen=%d)\n", mp->m_name, mp->m_data == NULL ? " firstcall" : "", sts, (int)mp->m_datalen);
	}
#endif
	if (sts == 0 && mp->m_data != NULL) {
	    mp->m_fetched = 1;
	    break;
	}
	if ((sts == -1 && errno == ENOMEM) || (sts == 0 && mp->m_data == NULL)) {
	    /* first call for this one, or data changed size */
	    mp->m_data = realloc(mp->m_data, mp->m_datalen);
	    if (mp->m_data == NULL) {
		fprintf(stderr, "Error: %s: buffer alloc failed for sysctl metric \"%s\"\n", mp->m_pcpname, mp->m_name);
		__pmNoMem("do_sysctl", mp->m_datalen, PM_FATAL_ERR);
		/*NOTREACHED*/
	    }
	}
	else
	    return -errno;
    }
    if (xpect > 0 && mp->m_datalen != xpect) {
	fprintf(stderr, "Error: %s: sysctl(%s) datalen=%d not %d!\n", mp->m_pcpname, mp->m_name, (int)mp->m_datalen, (int)xpect);
	return 0;
    }
    return mp->m_datalen;
}

/*
 * kernel memory reader setup
 */
struct nlist	symbols[] = {
	{ .n_name = "_ifnet" },
	{ .n_name = NULL }
};
kvm_t	*kvmp;

static void
kmemread_init(void)
{
    int		sts;
    int		i;
    char	errmsg[_POSIX2_LINE_MAX];

    /*
     * If we're running as a daemon PMDA, assume we're setgid kmem,
     * so we can open /dev/kmem, and downgrade privileges after the
     * kvm_open().
     * For a DSO PMDA, we have to assume pmcd has the required
     * privileges and don't dink with them.
     */
    kvmp = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errmsg);
    if (!isDSO)
	setgid(getgid());
    if (kvmp == NULL) {
	fprintf(stderr, "kmemread_init: kvm_openfiles failed: %s\n", errmsg);
	return;
    }

    sts = kvm_nlist(kvmp, symbols);
    if (sts < 0) {
	fprintf(stderr, "kmemread_init: kvm_nlist failed: %s\n", pmErrStr(-errno));
	for (i = 0; i < sizeof(symbols)/sizeof(symbols[0])-1; i++)
	    symbols[i].n_value = 0;
	return;
    }
#ifdef PCP_DEBUG
    if (pmDebug & DBG_TRACE_APPL0) {
	for (i = 0; i < sizeof(symbols)/sizeof(symbols[0])-1; i++) {
	    fprintf(stderr, "Info: kernel symbol %s found at 0x%08lx\n", symbols[i].n_name, symbols[i].n_value);
	}
    }
#endif

}

/*
 * Callback provided to pmdaFetch ... come here once per metric-instance
 * pair in each pmFetch().
 */
static int
freebsd_fetchCallBack(pmdaMetric *mdesc, unsigned int inst, pmAtomValue *atom)
{
    int			sts = PM_ERR_PMID;
    __pmID_int		*idp = (__pmID_int *)&(mdesc->m_desc.pmid);
    mib_t		*mp;

    mp = (mib_t *)mdesc->m_user;
    if (idp->cluster == CL_SYSCTL) {
	/* sysctl() simple cases */
	switch (idp->item) {
	    /* 32-bit integer values */
	    case 0:		/* hinv.ncpu */
	    case 18:		/* swap.pagesin */
	    case 19:		/* swap.pagesout */
	    case 20:		/* swap.in */
	    case 21:		/* swap.out */
	    case 22:		/* kernel.all.pswitch */
	    case 23:		/* kernel.all.syscall */
	    case 24:		/* kernel.all.intr */
		sts = do_sysctl(mp, sizeof(atom->ul));
		if (sts > 0) {
		    atom->ul = *((__uint32_t *)mp->m_data);
		    sts = 1;
		}
		break;

	    /* 64-bit integer values */
	    case 1:		/* hinv.physmem */
	    case 25:		/* swap.length */
	    case 26:		/* swap.used */
		sts = do_sysctl(mp, sizeof(atom->ull));
		if (sts > 0) {
		    atom->ull = *((__uint64_t *)mp->m_data);
		    sts = 1;
		}
		break;

	    /* string values */
	    case 15:		/* hinv.cpu.vendor */
	    case 16:		/* hinv.cpu.model */
	    case 17:		/* hinv.cpu.arch */
		sts = do_sysctl(mp, (size_t)0);
		if (sts > 0) {
		    atom->cp = (char *)mp->m_data;
		    sts = 1;
		}
		break;

	    /* structs and aggregates */
	    case 2:		/* kernel.all.load */
		sts = do_sysctl(mp, 3*sizeof(atom->d));
		if (sts > 0) {
		    int			i;
		    struct loadavg	*lp = (struct loadavg *)mp->m_data;
		    if (inst == 1)
			i = 0;
		    else if (inst == 5)
			i = 1;
		    else if (inst == 15)
			i = 2;
		    else
			return PM_ERR_INST;
		    atom->f = (float)((double)lp->ldavg[i] / lp->fscale);
		    sts = 1;
		}
		break;

	    case 3:		/* kernel.all.cpu.user */
	    case 4:		/* kernel.all.cpu.nice */
	    case 5:		/* kernel.all.cpu.sys */
	    case 6:		/* kernel.all.cpu.intr */
	    case 7:		/* kernel.all.cpu.idle */
		sts = do_sysctl(mp, CPUSTATES*sizeof(atom->ull));
		if (sts > 0) {
		    /*
		     * PMID assignment is important in the "-3" below so
		     * that metrics map to consecutive elements of the
		     * returned value in the order defined for CPUSTATES,
		     * i.e. CP_USER, CP_NICE, CP_SYS, CP_INTR and
		     * CP_IDLE
		     */
		    atom->ull = 1000*((__uint64_t *)mp->m_data)[idp->item-3]/cpuhz;
		    sts = 1;
		}
		break;

	    case 8:		/* kernel.percpu.cpu.user */
	    case 9:		/* kernel.percpu.cpu.nice */
	    case 10:		/* kernel.percpu.cpu.sys */
	    case 11:		/* kernel.percpu.cpu.intr */
	    case 12:		/* kernel.percpu.cpu.idle */
		sts = do_sysctl(mp, ncpu*CPUSTATES*sizeof(atom->ull));
		if (sts > 0) {
		    /*
		     * PMID assignment is important in the "-8" below so
		     * that metrics map to consecutive elements of the
		     * returned value in the order defined for CPUSTATES,
		     * i.e. CP_USER, CP_NICE, CP_SYS, CP_INTR and
		     * CP_IDLE, and then there is one such set for each
		     * CPU up to the maximum number of CPUs installed in
		     * the system.
		     */
		    atom->ull = 1000*((__uint64_t *)mp->m_data)[inst * CPUSTATES + idp->item-8]/cpuhz;
		    sts = 1;
		}
		break;

	    case 13:		/* kernel.all.hz */
		sts = do_sysctl(mp, sizeof(struct clockinfo));
		if (sts > 0) {
		    struct clockinfo	*cp = (struct clockinfo *)mp->m_data;
		    atom->ul = cp->hz;
		    sts = 1;
		}
		break;

	}
    }
    else if (idp->cluster == CL_SPECIAL) {
	/* special cases */
	switch (idp->item) {
	    case 0:	/* hinv.ndisk */
		refresh_disk_metrics();
		atom->ul = pmdaCacheOp(indomtab[DISK_INDOM].it_indom, PMDA_CACHE_SIZE_ACTIVE);
		sts = 1;
		break;

	    case 1:	/* swap.free */
		/* first vm.swap_total */
		sts = do_sysctl(mp, sizeof(atom->ull));
		if (sts > 0) {
		    atom->ull = *((__uint64_t *)mp->m_data);
		    /*
		     * now subtract vm.swap_reserved ... assumes consecutive
		     * mib[] entries
		     */
		    mp++;
		    sts = do_sysctl(mp, sizeof(atom->ull));
		    if (sts > 0) {
			atom->ull -= *((__uint64_t *)mp->m_data);
			sts = 1;
		    }
		}
		break;

	    case 3:	/* hinv.pagesize */
		atom->ul = pagesize;
		sts = 1;
		break;

	    case 4:	/* mem.util.all */
	    case 6:	/* mem.util.free */
	    case 8:	/* mem.util.cached */
	    case 9:	/* mem.util.wired */
	    case 10:	/* mem.util.active */
	    case 11:	/* mem.util.inactive */
		sts = do_sysctl(mp, sizeof(atom->ul));
		if (sts > 0) {
		    atom->ul = *((__uint32_t *)mp->m_data) * (pagesize / 1024);
		    sts = 1;
		}
		break;

	    case 7:	/* mem.util.bufmem */
		sts = do_sysctl(mp, sizeof(atom->ul));
		if (sts > 0) {
		    atom->ul = *((__uint32_t *)mp->m_data) / 1024;
		    sts = 1;
		}
		break;

	    case 5:	/* mem.util.used */
		/*
		 * mp-> v_page_count entry in mib[]
		 * assuming consecutive mib[] entries, we want
		 * v_page_count mp[0] - v_free_count mp[1] -
		 * v_cache_count mp[2] - v_inactive_count mp[5]
		 */
		sts = do_sysctl(mp, sizeof(atom->ul));
		if (sts > 0) {
		    atom->ul = *((__uint32_t *)mp->m_data);
		    sts = do_sysctl(&mp[1], sizeof(atom->ul));
		    if (sts > 0) {
			atom->ul -= *((__uint32_t *)mp[1].m_data);
			sts = do_sysctl(&mp[2], sizeof(atom->ul));
			if (sts > 0) {
			    atom->ul -= *((__uint32_t *)mp[2].m_data);
			    sts = do_sysctl(&mp[5], sizeof(atom->ul));
			    if (sts > 0) {
				atom->ul -= *((__uint32_t *)mp[5].m_data);
				atom->ul *= (pagesize / 1024);
				sts = 1;
			    }
			}
		    }
		}
		break;

	    case 12:	/* mem.util.avail */
		/*
		 * mp-> v_page_count entry in mib[]
		 * assuming consecutive mib[] entries, we want
		 * v_free_count mp[1] + v_cache_count mp[2] +
		 * v_inactive_count mp[5]
		 */
		sts = do_sysctl(&mp[1], sizeof(atom->ul));
		if (sts > 0) {
		    atom->ul = *((__uint32_t *)mp[1].m_data);
		    sts = do_sysctl(&mp[2], sizeof(atom->ul));
		    if (sts > 0) {
			atom->ul += *((__uint32_t *)mp[2].m_data);
			sts = do_sysctl(&mp[5], sizeof(atom->ul));
			if (sts > 0) {
			    atom->ul += *((__uint32_t *)mp[5].m_data);
			    atom->ul *= (pagesize / 1024);
			    sts = 1;
			}
		    }
		}
		break;

	}
    }
    else if (idp->cluster == CL_DISK) {
	/* disk metrics */
	sts = do_disk_metrics(mdesc, inst, atom);
    }
    else if (idp->cluster == CL_NETIF) {
	/* network interface metrics */
	sts = do_netif_metrics(mdesc, inst, atom);
    }

    return sts;
}

/*
 * wrapper for pmdaFetch ... force value caches to be reloaded if needed,
 * then do the fetch
 */
static int
freebsd_fetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda)
{
    int		i;
    int		done_disk = 0;
    int		done_netif = 0;

    for (i = 0; i < maplen; i++) {
	map[i].m_fetched = 0;
    }

    /*
     * pre-fetch all metrics if needed, and update instance domains if
     * they have changed
     */
    for (i = 0; !done_disk && !done_netif && i < numpmid; i++) {
	if (pmid_cluster(pmidlist[i]) == CL_DISK) {
	    refresh_disk_metrics();
	    done_disk = 1;
	}
	else if (pmid_cluster(pmidlist[i]) == CL_NETIF) {
	    refresh_netif_metrics();
	    done_netif = 1;
	}
    }

    return pmdaFetch(numpmid, pmidlist, resp, pmda);
}

/*
 * wrapper for pmdaInstance ... refresh required instance domain first
 */
static int
freebsd_instance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda)
{
    /*
     * indomtab[] instance names and ids are not used for some indoms,
     * ensure pmdaCache is current
     */
    if (indom == indomtab[DISK_INDOM].it_indom)
	refresh_disk_metrics();
    if (indom == indomtab[NETIF_INDOM].it_indom)
	refresh_netif_metrics();

    return pmdaInstance(indom, inst, name, result, pmda);
}

/*
 * PCP metric name matching for linking metrictab[] entries to mib[]
 * entries.
 *
 * Return 1 if prefix[] is equal to, or a prefix of name[]
 *
 * prefix[] of the form "a.bc" or "a.bc*" matches a name[] like "a.bc"
 * or "a.bcanything", to improve readability of the initializers in
 * mib[], and asterisk is a "match all" special case, so "a.b.*" matches
 * "a.b.anything"
 */
static int
matchname(const char *prefix, const char *name)
{
    while (*prefix != '\0' && *name != '\0' && *prefix == *name) {
	prefix++;
	name++;
    }
    if (*prefix == '\0' || *prefix == '*')
	return 1;
    else
	return 0;
}

/*
 * Initialise the agent (both daemon and DSO).
 * 
 * Do mapping from sysclt(3) names to mibs.
 * Collect some global constants.
 * Build the system-specific, but not dynamic, instance domains,
 * e.g. CPU_INDOM.
 * Initialize the kernel memory reader.
 */
void 
freebsd_init(pmdaInterface *dp)
{
    int			i;
    int			m;
    int			sts;
    struct clockinfo	clockrates;
    size_t		sz;
    int			mib[CTL_MAXNAME];	/* enough for longest mib key */
    char		iname[16];		/* enough for cpuNN.. */

    if (isDSO) {
	char	mypath[MAXPATHLEN];
	int sep = __pmPathSeparator();
	snprintf(mypath, sizeof(mypath), "%s%c" "freebsd" "%c" "help",
		pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
	pmdaDSO(dp, PMDA_INTERFACE_5, "freebsd DSO", mypath);
    } else {
	__pmSetProcessIdentity(username);
    }

    if (dp->status != 0)
	return;

    dp->version.four.fetch = freebsd_fetch;
    dp->version.four.instance = freebsd_instance;

    pmdaSetFetchCallBack(dp, freebsd_fetchCallBack);

    pmdaInit(dp, indomtab, indomtablen, metrictab, metrictablen);

    /*
     * Link metrictab[] entries via m_user to map[] entries based on
     * matching sysctl(3) name
     *
     * also translate the sysctl(3) name to a mib
     */
    for (m = 0; m < metrictablen; m++) {
	if (metrictab[m].m_user == NULL) {
	    /* not using sysctl(3) */
	    continue;
	}
	for (i = 0; i < maplen; i++) {
	    if (matchname(map[i].m_pcpname, (char *)metrictab[m].m_user)) {
		if (map[i].m_mib == NULL) {
		    /*
		     * multiple metrictab[] entries may point to the same
		     * mib[] entry, but this is the first time for this
		     * mib[] entry ...
		     */
		    map[i].m_miblen = sizeof(mib);
		    sts = sysctlnametomib(map[i].m_name, mib, &map[i].m_miblen);
		    if (sts == 0) {
			map[i].m_mib = (int *)malloc(map[i].m_miblen*sizeof(map[i].m_mib[0]));
			if (map[i].m_mib == NULL) {
			    fprintf(stderr, "Error: %s (%s): failed mib alloc for sysctl metric \"%s\"\n", map[i].m_pcpname, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name);
			    __pmNoMem("freebsd_init: mib", map[i].m_miblen*sizeof(map[i].m_mib[0]), PM_FATAL_ERR);
			    /*NOTREACHED*/
			}
			memcpy(map[i].m_mib, mib, map[i].m_miblen*sizeof(map[i].m_mib[0]));
		    }
		    else {
			fprintf(stderr, "Error: %s (%s): failed sysctlnametomib(\"%s\", ...): %s\n", map[i].m_pcpname, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name, pmErrStr(-errno));
			metrictab[m].m_user = (void *)&bad_mib;
		    }
		}
#ifdef PCP_DEBUG
		if (pmDebug & DBG_TRACE_APPL0) {
		    int	p;
		    fprintf(stderr, "Info: %s (%s): sysctl metric \"%s\" -> ", (char *)metrictab[m].m_user, pmIDStr(metrictab[m].m_desc.pmid), map[i].m_name);
		    for (p = 0; p < map[i].m_miblen; p++) {
			if (p > 0) fputc('.', stderr);
			fprintf(stderr, "%d", map[i].m_mib[p]);
		    }
		    fputc('\n', stderr);
		}
#endif
		metrictab[m].m_user = (void *)&map[i];
		break;
	    }
	}
	if (i == maplen) {
	    fprintf(stderr, "Error: %s (%s): cannot match name in sysctl map[]\n", (char *)metrictab[m].m_user, pmIDStr(metrictab[m].m_desc.pmid));
	    metrictab[m].m_user = (void *)&bad_mib;
	}
    }

    /*
     * Collect some global constants needed later ...
     */
    sz = sizeof(clockrates);
    sts = sysctlbyname("kern.clockrate", &clockrates, &sz, NULL, 0);
    if (sts < 0) {
	fprintf(stderr, "Fatal Error: sysctlbyname(\"kern.clockrate\", ...) failed: %s\n", pmErrStr(-errno));
	exit(1);
    }
    cpuhz = clockrates.stathz;
#ifdef PCP_DEBUG
    if (pmDebug & DBG_TRACE_APPL0)
	fprintf(stderr, "Info: CPU time \"hz\" = %d\n", cpuhz);
#endif

    sts = sysctlbyname("kern.cp_times", NULL, &sz, NULL, 0);
    if (sts < 0) {
	fprintf(stderr, "Fatal Error: sysctlbyname(\"kern.cp_times\", ...) failed: %s\n", pmErrStr(-errno));
	exit(1);
    }
    /*
     * see note below when fetching kernel.percpu.cpu.* metrics to
     * explain this
     */
    ncpu = sz / (CPUSTATES * sizeof(__uint64_t));
#ifdef PCP_DEBUG
    if (pmDebug & DBG_TRACE_APPL0)
	fprintf(stderr, "Info: ncpu = %d\n", ncpu);
#endif

    sz = sizeof(pagesize);
    sts = sysctlbyname("hw.pagesize", &pagesize, &sz, NULL, 0);
    if (sts < 0) {
	fprintf(stderr, "Fatal Error: sysctlbyname(\"hw.pagesize\", ...) failed: %s\n", pmErrStr(-errno));
	exit(1);
    }
#ifdef PCP_DEBUG
    if (pmDebug & DBG_TRACE_APPL0)
	fprintf(stderr, "Info: VM pagesize = %d\n", pagesize);
#endif

    /*
     * Build some instance domains ...
     */
    indomtab[CPU_INDOM].it_numinst = ncpu;
    indomtab[CPU_INDOM].it_set = (pmdaInstid *)malloc(ncpu * sizeof(pmdaInstid));
    if (indomtab[CPU_INDOM].it_set == NULL) {
	__pmNoMem("freebsd_init: CPU_INDOM it_set", ncpu * sizeof(pmdaInstid), PM_FATAL_ERR);
	/*NOTREACHED*/
    }
    for (i = 0; i < ncpu; i++) {
	indomtab[CPU_INDOM].it_set[i].i_inst = i;
	snprintf(iname, sizeof(iname), "cpu%d", i);
	indomtab[CPU_INDOM].it_set[i].i_name = strdup(iname);
	if (indomtab[CPU_INDOM].it_set[i].i_name == NULL) {
	    __pmNoMem("freebsd_init: CPU_INDOM strdup iname", strlen(iname), PM_FATAL_ERR);
	    /*NOTREACHED*/
	}
    }

    kmemread_init();
}

static void
usage(void)
{
    fprintf(stderr, "Usage: %s [options]\n\n", pmProgname);
    fputs("Options:\n"
	  "  -d domain    use domain (numeric) for metrics domain of PMDA\n"
	  "  -l logfile   write log into logfile rather than using default log name\n"
	  "  -U username  user account to run under (default \"pcp\")\n"
	  "\nExactly one of the following options may appear:\n"
	  "  -i port      expect PMCD to connect on given inet port (number or name)\n"
	  "  -p           expect PMCD to supply stdin/stdout (pipe)\n"
	  "  -u socket    expect PMCD to connect on given unix domain socket\n"
	  "  -6 port      expect PMCD to connect on given ipv6 port (number or name)\n",
	  stderr);		
    exit(1);
}

/*
 * Set up the agent if running as a daemon.
 */
int
main(int argc, char **argv)
{
    int			c, err = 0;
    int			sep = __pmPathSeparator();
    pmdaInterface	dispatch;
    char		mypath[MAXPATHLEN];

    isDSO = 0;
    __pmSetProgname(argv[0]);
    __pmGetUsername(&username);

    snprintf(mypath, sizeof(mypath), "%s%c" "freebsd" "%c" "help",
		pmGetConfig("PCP_PMDAS_DIR"), sep, sep);
    pmdaDaemon(&dispatch, PMDA_INTERFACE_5, pmProgname, FREEBSD,
		"freebsd.log", mypath);

    while ((c = pmdaGetOpt(argc, argv, "D:d:i:l:pu:U:6:?", &dispatch, &err)) != EOF) {
	switch(c) {
	case 'U':
	    username = optarg;
	    break;
	default:
	    err++;
	}
    }
    if (err)
	usage();

    pmdaOpenLog(&dispatch);
    freebsd_init(&dispatch);
    pmdaConnect(&dispatch);
    pmdaMain(&dispatch);
    exit(0);
}
