usr/src/cmd/rad/mod/kstat/mod_kstat.c
author devjani.ray@oracle.com <devjani.ray@oracle.com>
Tue, 15 May 2012 15:37:20 -0400
changeset 858 97083523b759
parent 853 e2d9352738a7
permissions -rw-r--r--
parfait fixes - kstat, libjniutil

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/list.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <kstat.h>
#include <pthread.h>
#include <string.h>
#include <limits.h>

#include <rad/adr.h>
#include <rad/adr_object.h>
#include <rad/rad_modapi.h>

#include "api_kstat.h"
#include "data.h"

#define	KSTAT_DOMAIN	"com.oracle.solaris.rad.kstat"

/* Certain kstats aren't worth it. */
#define	HDRS_MODULE	"unix"
#define	HDRS_NAME	"kstat_headers"
#define	SOCK_MODULE	"sockfs"
#define	SOCK_NAME	"sock_unix_list"
#define	KSMATCH(ks, mod, name)	\
	(strcmp(ks->ks_module, (mod)) == 0 && strcmp(ks->ks_name, (name)) == 0)
#define	SKIP(ks)	(KSMATCH(ks, HDRS_MODULE, HDRS_NAME) || \
	KSMATCH(ks, SOCK_MODULE, SOCK_NAME))

static pthread_mutex_t kstat_lock = PTHREAD_MUTEX_INITIALIZER;
static list_t kstat_list;
static kstat_ctl_t *kc;

typedef struct kstat_inst {
	adr_name_t *ki_name;	/* full name */
	int ki_instance;	/* instance number */
	uchar_t ki_type;	/* last known type */
	hrtime_t ki_crtime;	/* last known creation time */

	kstat_t *ki_ks;		/* current kstat header, if any */
	rad_instance_t *ki_inst;
	list_node_t ki_node;
} kstat_inst_t;


static void
kstat_inst_invalidate_one(kstat_inst_t *kid)
{
	kid->ki_ks = NULL;
}

static void
kstat_inst_invalidate_all(void)
{
	for (kstat_inst_t *kid = list_head(&kstat_list); kid;
	    kid = list_next(&kstat_list, kid))
		kstat_inst_invalidate_one(kid);
}

static boolean_t
kstat_inst_lookup(kstat_inst_t *kid)
{
	const char *kname = adr_name_key(kid->ki_name, "name");
	const char *kmodule = adr_name_key(kid->ki_name, "module");
	if ((kid->ki_ks = kstat_lookup(kc, (char *)kmodule,
	    kid->ki_instance, (char *)kname)) == NULL)
		return (B_FALSE);

	return (B_TRUE);
}

static boolean_t
kstat_inst_read_one(kstat_inst_t *kid)
{
	if (kid->ki_ks == NULL && !kstat_inst_lookup(kid))
		return (B_FALSE);

	if (kstat_read(kc, kid->ki_ks, NULL) == -1) {
		kstat_inst_invalidate_one(kid);
		return (B_FALSE);
	}

	return (B_TRUE);
}

static void
kstat_inst_read_all(void)
{
	for (kstat_inst_t *kid = list_head(&kstat_list); kid;
	    kid = list_next(&kstat_list, kid))
		(void) kstat_inst_read_one(kid);
}

static void
kstat_inst_update(void)
{
	while (kstat_chain_update(kc))
		kstat_inst_invalidate_all();
	kstat_inst_read_all();
}

static boolean_t
kstat_inst_read(kstat_inst_t *kid, boolean_t fresh)
{
	rad_mutex_enter(&kstat_lock);

	/* Able to use last-read value */
	if (!fresh && kid->ki_ks != NULL)
		return (B_TRUE);

	if (kstat_inst_read_one(kid))
		return (B_TRUE);

	rad_mutex_exit(&kstat_lock);
	return (B_FALSE);
}

static void
kstat_inst_free(kstat_inst_t *kid)
{
	adr_name_rele(kid->ki_name);
	free(kid);
}

static adr_name_t *
kstat_name_create(kstat_t *ks)
{
	char instance[100];
	(void) snprintf(instance, sizeof (instance), "%d", ks->ks_instance);

	return (adr_name_vcreate(KSTAT_DOMAIN, 5,
	    "type", "Kstat",
	    "module", ks->ks_module,
	    "name", ks->ks_name,
	    "class", ks->ks_class,
	    "instance", instance));
}

/*
 * Dynamic namespace callbacks
 */

/*ARGSUSED*/
static conerr_t
kstat_listf(adr_name_t *pattern, data_t **names, void *arg)
{
	data_t *result = data_new_array(&t_array_string, 0);

	for (kstat_t *ks = kc->kc_chain; ks; ks = ks->ks_next) {
		if (SKIP(ks))
			continue;

		/* XXX: round-about filtering; could be much more efficient */
		adr_name_t *name = kstat_name_create(ks);
		if (adr_name_match(name, pattern))
			(void) array_add(result,
			    data_new_string(adr_name_tostr(name), lt_free));
		adr_name_rele(name);
	}
	*names = data_purify_deep(result);

	return (*names == NULL ? ce_nomem : ce_ok);
}

/*ARGSUSED*/
static conerr_t
kstat_lookupf(adr_name_t **name, rad_instance_t **inst, void *arg)
{
	adr_name_t *aname = *name;
	const char *kclass = adr_name_key(aname, "class");
	const char *kname = adr_name_key(aname, "name");
	const char *kmodule = adr_name_key(aname, "module");
	const char *kinst = adr_name_key(aname, "instance");
	char *end = NULL;
	long kinstnum;

	if (kname == NULL || kmodule == NULL || kinst == NULL)
		return (ce_notfound);

	kinstnum = strtol(kinst, &end, 10);
	if (*end != '\0' || kinstnum < 0 || kinstnum > INT_MAX)
		return (ce_notfound);

	rad_mutex_enter(&kstat_lock);
	kstat_t *ks = kstat_lookup(kc, (char *)kmodule, (int)kinstnum,
	    (char *)kname);
	if (ks == NULL ||
	    (kclass != NULL && strcmp(kclass, ks->ks_class) != 0)) {
		rad_mutex_exit(&kstat_lock);
		return (ce_notfound);
	}

	if (SKIP(ks)) {
		rad_mutex_exit(&kstat_lock);
		return (ce_notfound);
	}

	if (kclass == NULL) {
		/*
		 * We permit clients to omit the class when doing lookups.
		 * This results in a second kstat_lookup, but *shrug*.
		 */
		adr_name_t *cname = kstat_name_create(ks);
		rad_mutex_exit(&kstat_lock);
		if (cname == NULL)
			return (ce_nomem);
		adr_name_rele(aname);
		*name = cname;
		*inst = NULL;
		return (ce_ok);
	}

	if (kstat_read(kc, ks, NULL) == -1) {
		rad_mutex_exit(&kstat_lock);
		return (ce_notfound);
	}

	kstat_inst_t *kid = malloc(sizeof (kstat_inst_t));
	if (kid == NULL) {
		rad_mutex_exit(&kstat_lock);
		return (ce_nomem);
	}

	rad_instance_t *i = instance_create(adr_name_hold(aname),
	    &interface_Kstat_svr, kid, (void(*)(void *))kstat_inst_free);
	if (i == NULL) {
		rad_mutex_exit(&kstat_lock);
		free(kid);
		return (ce_nomem);
	}

	kid->ki_name = adr_name_hold(aname);
	kid->ki_instance = (int)kinstnum;
	kid->ki_type = ks->ks_type;
	kid->ki_crtime = ks->ks_crtime;
	kid->ki_ks = ks;
	kid->ki_inst = i;
	list_insert_tail(&kstat_list, kid);
	*inst = i;

	rad_mutex_exit(&kstat_lock);

	return (ce_ok);
}

/*
 * "kstat" interface entry points
 */

/*ARGSUSED*/
conerr_t
interface_Kstat_read_info(rad_instance_t *inst, adr_attribute_t *attr,
    data_t **data, data_t **error)
{
	kstat_inst_t *kid = instance_getdata(inst);

	rad_mutex_enter(&kstat_lock);
	data_t *info = data_new_struct(&t__Kstatinfo);
	struct_set(info, "module",
	    data_new_string(adr_name_key(kid->ki_name, "module"), lt_copy));
	struct_set(info, "name",
	    data_new_string(adr_name_key(kid->ki_name, "name"), lt_copy));
	struct_set(info, "klass",
	    data_new_string(adr_name_key(kid->ki_name, "class"), lt_copy));
	struct_set(info, "instance", data_new_integer(kid->ki_instance));
	struct_set(info, "type", make_type(kid->ki_type));
	struct_set(info, "crtime", data_new_ulong(kid->ki_crtime));
	rad_mutex_exit(&kstat_lock);

	if (!data_verify(info, NULL, B_TRUE)) {
		data_free(info);
		return (ce_nomem);
	}

	*data = info;
	return (ce_ok);
}

/*ARGSUSED*/
conerr_t
interface_Kstat_read_stale(rad_instance_t *inst, adr_attribute_t *attr,
    data_t **data, data_t **error)
{
	kstat_inst_t *kid = instance_getdata(inst);

	rad_mutex_enter(&kstat_lock);
	*data = data_new_boolean(kid->ki_ks == NULL);
	rad_mutex_exit(&kstat_lock);

	return (ce_ok);
}

/*
 * Constructs kstat value.  Future: consider caching.
 */
static conerr_t
read_snapshot(rad_instance_t *inst, data_t **data, boolean_t fresh)
{
	kstat_inst_t *kid = instance_getdata(inst);
	if (!kstat_inst_read(kid, fresh))
		return (ce_object);

	data_t *snap = make_snapshot(kid->ki_ks);
	rad_mutex_exit(&kstat_lock);

	if (data_verify(snap, NULL, B_TRUE)) {
		*data = snap;
	} else {
		*data = NULL;
		data_free(snap);
	}

	return (*data == NULL ? ce_nomem : ce_ok);
}

/*ARGSUSED*/
conerr_t
interface_Kstat_read_snapshot(rad_instance_t *inst, adr_attribute_t *attr,
    data_t **data, data_t **error)
{
	return (read_snapshot(inst, data, B_FALSE));
}

/*ARGSUSED*/
conerr_t
interface_Kstat_invoke_fresh_snapshot(rad_instance_t *inst, adr_method_t *meth,
    data_t **ret, data_t **args, int count, data_t **error)
{
	return (read_snapshot(inst, ret, B_TRUE));
}

/*
 * "control" interface entry point
 */

/*ARGSUSED*/
conerr_t
interface_Control_invoke_update(rad_instance_t *inst, adr_method_t *meth,
    data_t **ret, data_t **args, int count, data_t **error)
{
	rad_mutex_enter(&kstat_lock);
	kstat_inst_update();
	rad_mutex_exit(&kstat_lock);
	return (ce_ok);
}

/*
 * rad module linkage
 */

static rad_modinfo_t modinfo = { "kstat", "kernel statistics module" };

int
_rad_init(void *handle)
{
	if (rad_module_register(handle, RAD_MODVERSION, &modinfo) == -1)
		return (-1);

	if (rad_isproxy)
		return (0);

	adr_name_t *domain = adr_name_vcreate(KSTAT_DOMAIN, 0);
	if (domain == NULL)
		return (0);

	if ((kc = kstat_open()) == NULL)
		return (0);

	list_create(&kstat_list, sizeof (kstat_inst_t),
	    offsetof(kstat_inst_t, ki_node));
	(void) cont_register_dynamic(rad_container, domain,
	    kstat_listf, kstat_lookupf, NULL);

	adr_name_t *ctlname =
	    adr_name_vcreate(KSTAT_DOMAIN, 1, "type", "Control");
	(void) cont_insert_singleton(rad_container, ctlname,
	    &interface_Control_svr);

	return (0);
}