/*
* 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);
}