diff options
Diffstat (limited to 'src/libpcp_pmda')
-rw-r--r-- | src/libpcp_pmda/GNUlocaldefs.32 | 4 | ||||
-rw-r--r-- | src/libpcp_pmda/GNUmakefile | 31 | ||||
-rw-r--r-- | src/libpcp_pmda/src/GNUmakefile | 102 | ||||
-rw-r--r-- | src/libpcp_pmda/src/cache.c | 1543 | ||||
-rw-r--r-- | src/libpcp_pmda/src/callback.c | 753 | ||||
-rw-r--r-- | src/libpcp_pmda/src/context.c | 31 | ||||
-rw-r--r-- | src/libpcp_pmda/src/dynamic.c | 217 | ||||
-rw-r--r-- | src/libpcp_pmda/src/events.c | 391 | ||||
-rw-r--r-- | src/libpcp_pmda/src/exports | 115 | ||||
-rw-r--r-- | src/libpcp_pmda/src/help.c | 217 | ||||
-rw-r--r-- | src/libpcp_pmda/src/libdefs.h | 36 | ||||
-rw-r--r-- | src/libpcp_pmda/src/mainloop.c | 491 | ||||
-rw-r--r-- | src/libpcp_pmda/src/open.c | 987 | ||||
-rw-r--r-- | src/libpcp_pmda/src/queues.c | 610 | ||||
-rw-r--r-- | src/libpcp_pmda/src/queues.h | 172 | ||||
-rw-r--r-- | src/libpcp_pmda/src/tree.c | 313 |
16 files changed, 6013 insertions, 0 deletions
diff --git a/src/libpcp_pmda/GNUlocaldefs.32 b/src/libpcp_pmda/GNUlocaldefs.32 new file mode 100644 index 0000000..38bc43e --- /dev/null +++ b/src/libpcp_pmda/GNUlocaldefs.32 @@ -0,0 +1,4 @@ +LCFLAGS += -m32 -march=i386 +LLDFLAGS += -m32 -march=i386 +PCP_LIB_DIR=$(PCP_LIB32_DIR) +LIBPCP_ABIDIR=32 diff --git a/src/libpcp_pmda/GNUmakefile b/src/libpcp_pmda/GNUmakefile new file mode 100644 index 0000000..982d0ad --- /dev/null +++ b/src/libpcp_pmda/GNUmakefile @@ -0,0 +1,31 @@ +# +# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This library 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 Lesser General Public +# License for more details. +# + +TOPDIR = ../.. + +include $(TOPDIR)/src/include/builddefs + +LSRCFILES = GNUlocaldefs.32 + +SUBDIRS = src + +default install: $(SUBDIRS) + $(SUBDIRS_MAKERULE) + +include $(BUILDRULES) + +default_pcp : default + +install_pcp : install + diff --git a/src/libpcp_pmda/src/GNUmakefile b/src/libpcp_pmda/src/GNUmakefile new file mode 100644 index 0000000..5812b73 --- /dev/null +++ b/src/libpcp_pmda/src/GNUmakefile @@ -0,0 +1,102 @@ +# +# Copyright (c) 2013 Red Hat. +# Copyright (c) 2009,2011 Aconex. All Rights Reserved. +# Copyright (c) 2000,2004 Silicon Graphics, Inc. All Rights Reserved. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This library 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 Lesser General Public +# License for more details. +# + +TOPDIR = ../../.. +include $(TOPDIR)/src/include/builddefs + +CFILES = callback.c open.c mainloop.c help.c cache.c tree.c context.c \ + events.c queues.c dynamic.c +HFILES = libdefs.h queues.h +LLDLIBS = -lpcp + +STATICLIBTARGET = libpcp_pmda.a + +# +# libpcp_pmda.so -> libpcp_pmda.so.3 +# libpcp_pmda.so.2 -> libpcp_pmda.so.3 +# +DSOVERSION_V2 = 2 +DSOVERSION_V3 = 3 +VERSION_SCRIPT = exports + +ifneq "$(TARGET_OS)" "darwin" +LIBTARGET_V1 = libpcp_pmda.$(DSOSUFFIX) +LIBTARGET_V2 = libpcp_pmda.$(DSOSUFFIX).$(DSOVERSION_V2) +LIBTARGET_V3 = libpcp_pmda.$(DSOSUFFIX).$(DSOVERSION_V3) +else +LIBTARGET_V1 = libpcp_pmda.$(DSOSUFFIX) +LIBTARGET_V2 = +LIBTARGET_V3 = libpcp_pmda.$(DSOVERSION_V3).$(DSOSUFFIX) +endif +ifeq "$(PACKAGE_DISTRIBUTION)" "debian" +LIBTARGET_V2 = +endif +LIBTARGET = $(LIBTARGET_V3) + +ifeq "$(TARGET_OS)" "mingw" +LIBTARGET = libpcp_pmda.$(DSOSUFFIX) +STATICLIBTARGET = +LIBTARGET_V1 = +LIBTARGET_V2 = +LIBTARGET_V3 = +endif + +ifeq "$(ENABLE_SHARED)" "no" +LIBTARGET = +LIBTARGET_V1 = +LIBTARGET_V2 = +LIBTARGET_V3 = +endif + +LSRCFILES = $(VERSION_SCRIPT) +LDIRT = $(LIBTARGET_V1) $(LIBTARGET_V2) $(LIBTARGET_V3) + +default: $(LIBTARGET_V1) $(LIBTARGET_V2) $(LIBTARGET) $(STATICLIBTARGET) + +ifneq ($(LIBTARGET_V1),) +$(LIBTARGET_V1): $(LIBTARGET_V3) + $(LN_S) -f $(LIBTARGET_V3) $(LIBTARGET_V1) +endif +ifneq ($(LIBTARGET_V2),) +$(LIBTARGET_V2): $(LIBTARGET_V3) + $(LN_S) -f $(LIBTARGET_V3) $(LIBTARGET_V2) +endif + +callback.o mainloop.o open.o : libdefs.h + +include $(BUILDRULES) + +install : default +ifneq ($(LIBTARGET),) + $(INSTALL) -m 755 $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET) +endif +ifneq ($(LIBTARGET_V1),) + $(INSTALL) -S $(LIBTARGET_V3) $(PCP_LIB_DIR)/$(LIBTARGET_V1) +endif +ifneq ($(LIBTARGET_V2),) + $(INSTALL) -S $(LIBTARGET) $(PCP_LIB_DIR)/$(LIBTARGET_V2) +endif +ifneq ($(STATICLIBTARGET),) + $(INSTALL) -m 755 $(STATICLIBTARGET) $(PCP_LIB_DIR)/$(STATICLIBTARGET) +endif + +default_pcp : default + +install_pcp : install + +ifneq ($(LIBTARGET),) +$(LIBTARGET): $(VERSION_SCRIPT) +endif diff --git a/src/libpcp_pmda/src/cache.c b/src/libpcp_pmda/src/cache.c new file mode 100644 index 0000000..dd74b3f --- /dev/null +++ b/src/libpcp_pmda/src/cache.c @@ -0,0 +1,1543 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 2005 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include <fcntl.h> +#include <ctype.h> +#include <sys/stat.h> + +static __uint32_t hash(const char *, int, __uint32_t); + +/* + * simple linked list for each cache at this stage + */ +typedef struct entry { + struct entry *next; /* in inst identifier order */ + struct entry *h_inst; /* inst hash chain */ + struct entry *h_name; /* name hash chain */ + int inst; + char *name; + int hashlen; /* smaller of strlen(name) and chars to first space */ + int keylen; /* > 0 if have key from pmdaCacheStoreKey() */ + void *key; /* != NULL if have key from pmdaCacheStoreKey() */ + int state; + void *private; + time_t stamp; +} entry_t; + +#define VERSION 1 /* version of external file format */ +#define MAX_HASH_TRY 10 + +/* + * linked list of cache headers + */ +typedef struct hdr { + struct hdr *next; /* linked list of indoms */ + entry_t *first; /* in inst order */ + entry_t *last; /* in inst order */ + entry_t *save; /* used in cache_walk() */ + entry_t **ctl_inst; /* hash by inst chains */ + entry_t **ctl_name; /* hash by name chains */ + pmInDom indom; + int hsize; + int hbits; + int nentry; /* number of entries */ + int ins_mode; /* see insert_cache() */ + int hstate; /* dirty/clean/string state */ + int keyhash_cnt[MAX_HASH_TRY]; +} hdr_t; + +/* bitfields for hstate */ +#define DIRTY_INSTANCE 0x1 +#define DIRTY_STAMP 0x2 +#define CACHE_STRINGS 0x4 + +static hdr_t *base; /* start of cache headers */ +static char filename[MAXPATHLEN]; + /* for load/save ops */ +static char *vdp; /* first trip mkdir for load/save */ + +/* + * Count character to end of string or first space, whichever comes + * first. In the special case of string caches, spaces are allowed. + */ +static int +get_hashlen(hdr_t *h, const char *str) +{ + const char *q = str; + + while (*q && (*q != ' ' || (h->hstate & CACHE_STRINGS) != 0)) + q++; + return (int)(q-str); +} + +static unsigned int +hash_str(const char *str, int len) +{ + return hash(str, len, 0); +} + +static void +KeyStr(FILE *f, int keylen, const char *key) +{ + int i; + if (keylen > 0) { + fprintf(f, "[key=0x"); + for (i = 0; i < keylen; i++, key++) + fprintf(f, "%02x", (*key & 0xff)); + fputc(']', f); + } + else + fprintf(f, "[no key]"); +} + +/* + * The magic "match up to a space" instance name matching ... + * + * e->name e->hashlen name hashlen result + * foo... >3 foo 3 0 + * foo 3 foo... >3 0 + * foo 3 foo 3 1 + * foo bar 3 foo 3 1 + * foo bar 3 foo bar 3 1 + * foo 3 foo bar 3 -1 bad + * foo blah 3 foo bar 3 -1 bad + * + */ +static int +name_eq(entry_t *e, const char *name, int hashlen) +{ + if (e->hashlen != hashlen) + return 0; + if (strncmp(e->name, name, hashlen) != 0) + return 0; + if (name[hashlen] == '\0') + return 1; + if (e->name[hashlen] == '\0') + return -1; + if (strcmp(&e->name[hashlen+1], &name[hashlen+1]) == 0) + return 1; + return -1; +} + +static int +key_eq(entry_t *e, int keylen, const char *key) +{ + const char *ekp; + const char *kp; + int i; + + if (e->keylen != keylen) + return 0; + + ekp = (const char *)e->key; + kp = (const char *)key; + for (i = 0; i < keylen; i++) { + if (*ekp != *kp) + return 0; + ekp++; + kp++; + } + + return 1; +} + +static hdr_t * +find_cache(pmInDom indom, int *sts) +{ + hdr_t *h; + int i; + + for (h = base; h != NULL; h = h->next) { + if (h->indom == indom) + return h; + } + + if ((h = (hdr_t *)malloc(sizeof(hdr_t))) == NULL) { + char strbuf[20]; + __pmNotifyErr(LOG_ERR, + "find_cache: indom %s: unable to allocate memory for hdr_t", + pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + *sts = PM_ERR_GENERIC; + return NULL; + } + h->next = base; + base = h; + h->first = NULL; + h->last = NULL; + h->hsize = 16; + h->hbits = 0xf; + h->ctl_inst = (entry_t **)calloc(h->hsize, sizeof(entry_t *)); + h->ctl_name = (entry_t **)calloc(h->hsize, sizeof(entry_t *)); + h->indom = indom; + h->nentry = 0; + h->ins_mode = 0; + h->hstate = 0; + for (i = 0; i < MAX_HASH_TRY; i++) + h->keyhash_cnt[i] = 0; + return h; +} + +/* + * Traverse the cache in ascending inst order + */ +static entry_t * +walk_cache(hdr_t *h, int op) +{ + entry_t *e; + + if (op == PMDA_CACHE_WALK_REWIND) { + h->save = h->first; + return NULL; + } + e = h->save; + if (e != NULL) + h->save = e->next; + return e; +} + +/* + * inst_or_name is 0 for inst hash list, 1 for name hash list + */ +static void +dump_hash_list(FILE *fp, hdr_t *h, int inst_or_name, int i) +{ + entry_t *e; + + fprintf(fp, " [%03d]", i); + if (inst_or_name == 1) { + for (e = h->ctl_name[i]; e != NULL; e = e->h_name) { + fprintf(fp, " -> %d", e->inst); + if (e->state == PMDA_CACHE_EMPTY) + fputc('E', fp); + else if (e->state == PMDA_CACHE_INACTIVE) + fputc('I', fp); + } + fputc('\n', fp); + } + else { + for (e = h->ctl_inst[i]; e != NULL; e = e->h_inst) { + fprintf(fp, " -> %d", e->inst); + if (e->state == PMDA_CACHE_EMPTY) + fputc('E', fp); + else if (e->state == PMDA_CACHE_INACTIVE) + fputc('I', fp); + } + fputc('\n', fp); + } +} + +static void +dump(FILE *fp, hdr_t *h, int do_hash) +{ + entry_t *e; + char strbuf[20]; + int i; + + fprintf(fp, "pmdaCacheDump: indom %s: nentry=%d ins_mode=%d hstate=%d hsize=%d\n", + pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)), h->nentry, h->ins_mode, h->hstate, h->hsize); + for (e = h->first; e != NULL; e = e->next) { + if (e->state == PMDA_CACHE_EMPTY) { + fprintf(fp, "(%10d) %8s\n", e->inst, "empty"); + } + else { + fprintf(fp, " %10d %8s " PRINTF_P_PFX "%p %s", + e->inst, e->state == PMDA_CACHE_ACTIVE ? "active" : "inactive", + e->private, e->name); + if (strlen(e->name) > e->hashlen) + fprintf(fp, " [match len=%d]", e->hashlen); + if (e->keylen > 0) { + fputc(' ', fp); + KeyStr(fp, e->keylen, (const char *)e->key); + } + fputc('\n', fp); + } + } + + if (do_hash == 0) + return; + + for (i = 0; i < MAX_HASH_TRY; i++) { + if (h->keyhash_cnt[i]) + break; + } + + if (i < MAX_HASH_TRY) { + fprintf(fp, "pmdaCacheStoreKey hash stats ...\n"); + for (i = 0; i < MAX_HASH_TRY; i++) { + if (h->keyhash_cnt[i] != 0) { + if (i == 0) + fprintf(fp, "hash once: %d times\n", h->keyhash_cnt[i]); + else + fprintf(fp, "%d hash attempts: %d times\n", i+1, h->keyhash_cnt[i]); + } + } + } + + if (h->ctl_inst != NULL) { + int i; + fprintf(fp, "inst hash\n"); + for (i = 0; i < h->hsize; i++) { + dump_hash_list(fp, h, 0, i); + } + } + if (h->ctl_name != NULL) { + int i; + fprintf(fp, "name hash\n"); + for (i = 0; i < h->hsize; i++) { + dump_hash_list(fp, h, 1, i); + } + } +} + +static entry_t * +find_name(hdr_t *h, const char *name, int *sts) +{ + entry_t *e; + int hashlen = get_hashlen(h, name); + + *sts = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state != PMDA_CACHE_EMPTY) { + if ((*sts = name_eq(e, name, hashlen))) + break; + } + } + return e; +} + +static entry_t * +find_inst(hdr_t *h, int inst) +{ + entry_t *e; + + for (e = h->first; e != NULL; e = e->next) { + if (e->inst == inst && e->state != PMDA_CACHE_EMPTY) + break; + } + return e; +} + +/* + * supports find by instance identifier (name == NULL) else + * find by instance name + */ +static entry_t * +find_entry(hdr_t *h, const char *name, int inst, int *sts) +{ + entry_t *e; + + *sts = 0; + if (name == NULL) { + /* + * search by instance identifier (inst) + */ + if (h->ctl_inst == NULL) + /* no hash, use linear search */ + return find_inst(h, inst); + for (e = h->ctl_inst[inst & h->hbits]; e != NULL; e = e->h_inst) { + if (e->inst == inst && e->state != PMDA_CACHE_EMPTY) + return e; + } + } + else { + /* + * search by instance name + */ + int hashlen = get_hashlen(h, name); + + if (h->ctl_name == NULL) + /* no hash, use linear search */ + return find_name(h, name, sts); + for (e = h->ctl_name[hash_str(name, hashlen) & h->hbits]; e != NULL; e = e->h_name) { + if (e->state != PMDA_CACHE_EMPTY) { + if ((*sts = name_eq(e, name, hashlen))) + return e; + } + } + } + + return NULL; +} + +/* + * optionally resize the hash table first (if resize == 1) + * + * then re-order each hash chain so that active entires are + * before inactive entries, and culled entries dropped + * + * applies to _both_ the inst and name hashes + */ +static void +redo_hash(hdr_t *h, int resize) +{ + entry_t *e; + entry_t *last_e = NULL; + entry_t *t; + int i; + entry_t *last_active; + entry_t *inactive; + entry_t *last_inactive; + + if (resize) { + entry_t **old_inst; + entry_t **old_name; + int oldsize; + int oldi; + + old_inst = h->ctl_inst; + old_name = h->ctl_name; + oldsize = h->hsize; + h->hsize <<= 1; + h->ctl_inst = (entry_t **)calloc(h->hsize, sizeof(entry_t *)); + if (h->ctl_inst == NULL) { + h->ctl_inst = old_inst; + h->hsize = oldsize; + goto reorder; + } + h->ctl_name = (entry_t **)calloc(h->hsize, sizeof(entry_t *)); + if (h->ctl_name == NULL) { + free(h->ctl_inst); + h->ctl_inst = old_inst; + h->ctl_name = old_name; + h->hsize = oldsize; + goto reorder; + } + h->hbits = (h->hbits << 1) | 1; + for (oldi = 0; oldi < oldsize; oldi++) { + for (e = old_inst[oldi]; e != NULL; ) { + t = e; + e = e->h_inst; + i = t->inst & h->hbits; + t->h_inst = h->ctl_inst[i]; + h->ctl_inst[i] = t; + } + } + for (oldi = 0; oldi < oldsize; oldi++) { + for (e = old_name[oldi]; e != NULL; ) { + t = e; + e = e->h_name; + i = hash_str(t->name, t->hashlen) & h->hbits; + t->h_name = h->ctl_name[i]; + h->ctl_name[i] = t; + } + } + free(old_inst); + free(old_name); + } +reorder: + + /* + * first the inst hash list, moving active entries before inactive ones, + * and unlinking any empty ones + */ + for (i = 0; i < h->hsize; i++) { + last_active = NULL; + inactive = NULL; + last_inactive = NULL; + e = h->ctl_inst[i]; + h->ctl_inst[i] = NULL; + while (e != NULL) { + t = e; + e = e->h_inst; + t->h_inst = NULL; + if (t->state == PMDA_CACHE_ACTIVE) { + if (last_active == NULL) + h->ctl_inst[i] = t; + else + last_active->h_inst = t; + last_active = t; + } + else if (t->state == PMDA_CACHE_INACTIVE) { + if (last_inactive == NULL) + inactive = t; + else + last_inactive->h_inst = t; + last_inactive = t; + } + } + if (last_active == NULL) + h->ctl_inst[i] = inactive; + else + last_active->h_inst = inactive; + } + + /* + * and now the name hash list, doing the same thing + */ + for (i = 0; i < h->hsize; i++) { + last_active = NULL; + inactive = NULL; + last_inactive = NULL; + e = h->ctl_name[i]; + h->ctl_name[i] = NULL; + while (e != NULL) { + t = e; + e = e->h_name; + t->h_name = NULL; + if (t->state == PMDA_CACHE_ACTIVE) { + if (last_active == NULL) + h->ctl_name[i] = t; + else + last_active->h_name = t; + last_active = t; + } + else if (t->state == PMDA_CACHE_INACTIVE) { + if (last_inactive == NULL) + inactive = t; + else + last_inactive->h_name = t; + last_inactive = t; + } + } + if (last_active == NULL) + h->ctl_name[i] = inactive; + else + last_active->h_name = inactive; + } + + /* + * now walk the instance list, removing any culled entries and + * rebuilding the linked list + */ + e = h->first; + while (e != NULL) { + t = e; + e = e->next; + if (t->state == PMDA_CACHE_EMPTY) { + if (last_e == NULL) + h->first = e; + else + last_e->next = e; + if (t->name) + free(t->name); + free(t); + } + else + last_e = t; + } + +} + +/* + * We need to keep the instances in ascending inst order. + * If inst _is_ PM_IN_NULL, then we need to choose a value ... + * The default mode is appending to use the last value+1 (this is + * ins_mode == 0). If we wrap the instance identifier range, or + * PMDA_CACHE_REUSE has been used, then ins_mode == 1 and we walk + * the list starting from the beginning, looking for the first + * unused inst value. + * + * If inst is _not_ PM_IN_NULL, we're being called from load_cache + * or pmdaCacheStoreKey() and the inst is known ... so we need to + * check for possible duplicate entries. + */ +static entry_t * +insert_cache(hdr_t *h, const char *name, int inst, int *sts) +{ + entry_t *e; + entry_t *last_e = NULL; + char *dup; + int i; + int hashlen = get_hashlen(h, name); + + *sts = 0; + + if (inst != PM_IN_NULL) { + /* + * Check if instance id or instance name already in cache + * ... if id and name are the the same, keep the existing one + * and ignore the new one (in particular state is not reset to + * inactive). + * If one matches but the other is different, keep the + * matching entry, but return an error as a warning. + * If both fail to match, we're OK to insert the new entry, + * although we need to run down the list quickly to find the + * correct place to insert the new one. + */ + e = find_entry(h, NULL, inst, sts); + if (e != NULL) { + if (name_eq(e, name, hashlen) != 1) { + /* instance id the same, different name */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCache: store: indom %s: instance %d ", pmInDomStr(h->indom), e->inst); + fprintf(stderr, " in cache, name \"%s\" does not match new entry \"%s\"\n", e->name, name); + } +#endif + *sts = PM_ERR_INST; + } + return e; + } + e = find_entry(h, name, PM_IN_NULL, sts); + if (e != NULL) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance \"%s\"", pmInDomStr(h->indom), e->name); + fprintf(stderr, " in cache, id %d does not match new entry %d\n", e->inst, inst); + } +#endif + *sts = PM_ERR_INST; + return e; + } + for (e = h->first; e != NULL; e = e->next) { + if (e->inst < inst) + last_e = e; + else if (e->inst > inst) + break; + } + } + + if ((dup = strdup(name)) == NULL) { + char strbuf[20]; + __pmNotifyErr(LOG_ERR, + "insert_cache: indom %s: unable to allocate %d bytes for name: %s\n", + pmInDomStr_r(h->indom, strbuf, sizeof(strbuf)), (int)strlen(name), name); + *sts = PM_ERR_GENERIC; + return NULL; + } + + if (inst == PM_IN_NULL) { + if (h->ins_mode == 0) { + last_e = h->last; + if (last_e == NULL) + inst = 0; + else { + if (last_e->inst == 0x7fffffff) { + /* + * overflowed inst identifier, need to shift to + * ins_mode == 1 + */ + h->ins_mode = 1; + last_e = NULL; + goto retry; + } + inst = last_e->inst+1; + } + } + else { +retry: + inst = 0; + for (e = h->first; e != NULL; e = e->next) { + if (inst < e->inst) + break; + if (inst == 0x7fffffff) { + /* + * 2^32-1 is the maximum number of instances we can have + */ + char strbuf[20]; + __pmNotifyErr(LOG_ERR, + "insert_cache: indom %s: too many instances", + pmInDomStr_r(h->indom, strbuf, sizeof(strbuf))); + *sts = PM_ERR_GENERIC; + free(dup); + return NULL; + } + inst++; + last_e = e; + } + } + } + + if ((e = (entry_t *)malloc(sizeof(entry_t))) == NULL) { + char strbuf[20]; + __pmNotifyErr(LOG_ERR, + "insert_cache: indom %s: unable to allocate memory for entry_t", + pmInDomStr_r(h->indom, strbuf, sizeof(strbuf))); + *sts = PM_ERR_GENERIC; + free(dup); + return NULL; + } + + if (last_e == NULL) { + /* head of list */ + e->next = h->first; + h->first = e; + } + else { + /* middle of list */ + e->next = last_e->next; + last_e->next = e; + } + e->inst = inst; + e->name = dup; + e->hashlen = get_hashlen(h, dup); + e->key = NULL; + e->state = PMDA_CACHE_INACTIVE; + e->private = NULL; + e->stamp = 0; + if (h->last == NULL || h->last->inst < inst) + h->last = e; + h->nentry++; + + if (h->hsize > 0 && h->hsize < 1024 && h->nentry > 4 * h->hsize) + redo_hash(h, 1); + + /* link into the inst hash list, if any */ + if (h->ctl_inst != NULL) { + i = inst & h->hbits; + e->h_inst = h->ctl_inst[i]; + h->ctl_inst[i] = e; + } + else + e->h_inst = NULL; + + /* link into the name hash list, if any */ + if (h->ctl_name != NULL) { + i = hash_str(e->name, e->hashlen) & h->hbits; + e->h_name = h->ctl_name[i]; + h->ctl_name[i] = e; + } + else + e->h_name = NULL; + + return e; +} + +static int +load_cache(hdr_t *h) +{ + FILE *fp; + entry_t *e; + int cnt; + int x; + int inst; + int keylen = 0; + void *key = NULL; + int s; + char buf[1024]; /* input line buffer, is this big enough? */ + char *p; + int sts; + int sep = __pmPathSeparator(); + char strbuf[20]; + + if (vdp == NULL) { + vdp = pmGetConfig("PCP_VAR_DIR"); + snprintf(filename, sizeof(filename), + "%s%c" "config" "%c" "pmda", vdp, sep, sep); + mkdir2(filename, 0755); + } + + snprintf(filename, sizeof(filename), "%s%cconfig%cpmda%c%s", + vdp, sep, sep, sep, pmInDomStr_r(h->indom, strbuf, sizeof(strbuf))); + if ((fp = fopen(filename, "r")) == NULL) + return -oserror(); + if (fgets(buf, sizeof(buf), fp) == NULL) { + __pmNotifyErr(LOG_ERR, + "pmdaCacheOp: %s: empty file?", filename); + fclose(fp); + return PM_ERR_GENERIC; + } + s = sscanf(buf, "%d %d", &x, &h->ins_mode); + if (s != 2 || x != 1 || h->ins_mode < 0 || h->ins_mode > 1) { + __pmNotifyErr(LOG_ERR, + "pmdaCacheOp: %s: illegal first record: %s", + filename, buf); + fclose(fp); + return PM_ERR_GENERIC; + } + + for (cnt = 0; ; cnt++) { + if (fgets(buf, sizeof(buf), fp) == NULL) + break; + if ((p = strchr(buf, '\n')) != NULL) + *p = '\0'; + p = buf; + while (*p && isascii((int)*p) && isspace((int)*p)) + p++; + if (*p == '\0') goto bad; + inst = 0; + while (*p && isascii((int)*p) && isdigit((int)*p)) { + inst = inst*10 + (*p-'0'); + p++; + } + while (*p && isascii((int)*p) && isspace((int)*p)) + p++; + if (inst < 0 || *p == '\0') goto bad; + x = 0; + while (*p && isascii((int)*p) && isdigit((int)*p)) { + x = x*10 + (*p-'0'); + p++; + } + while (*p && isascii((int)*p) && isspace((int)*p)) + p++; + if (*p == '[') { + char *pend; + char *q; + int i; + int tmp; + p++; + pend = p; + while (*pend && *pend != ']') + pend++; + if (*pend != ']') + goto bad; + /* + * convert key in place ... + */ + keylen = (pend - p) / 2; + if ((key = malloc(keylen)) == NULL) { + __pmNotifyErr(LOG_ERR, + "load_cache: indom %s: unable to allocate memory for keylen=%d", + pmInDomStr(h->indom), keylen); + fclose(fp); + return PM_ERR_GENERIC; + } + q = key; + for (i = 0; i < keylen; i++) { + sscanf(p, "%2x", &tmp); + *q++ = (tmp & 0xff); + p += 2; + } + p += 2; + while (*p && isascii((int)*p) && isspace((int)*p)) + p++; + } + else { + keylen = 0; + key = NULL; + } + if (*p == '\0') { +bad: + __pmNotifyErr(LOG_ERR, + "pmdaCacheOp: %s: illegal record: %s", + filename, buf); + if (key) free(key); + fclose(fp); + return PM_ERR_GENERIC; + } + e = insert_cache(h, p, inst, &sts); + if (e == NULL) { + if (key) free(key); + fclose(fp); + return sts; + } + if (sts != 0) { + __pmNotifyErr(LOG_WARNING, + "pmdaCacheOp: %s: loading instance %d (\"%s\") ignored, already in cache as %d (\"%s\")", + filename, inst, p, e->inst, e->name); + } + e->keylen = keylen; + e->key = key; + e->stamp = x; + } + fclose(fp); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "After PMDA_CACHE_LOAD\n"); + dump(stderr, h, 0); + } +#endif + + return cnt; +} + +static int +save_cache(hdr_t *h, int hstate) +{ + FILE *fp; + entry_t *e; + int cnt; + time_t now; + int sep = __pmPathSeparator(); + char strbuf[20]; + + if ((h->hstate & hstate) == 0 || (h->hstate & CACHE_STRINGS) != 0) { + /* nothing to be done */ + return 0; + } + + if (vdp == NULL) { + vdp = pmGetConfig("PCP_VAR_DIR"); + snprintf(filename, sizeof(filename), + "%s%c" "config" "%c" "pmda", vdp, sep, sep); + mkdir2(filename, 0755); + } + + snprintf(filename, sizeof(filename), "%s%cconfig%cpmda%c%s", + vdp, sep, sep, sep, pmInDomStr_r(h->indom, strbuf, sizeof(strbuf))); + if ((fp = fopen(filename, "w")) == NULL) + return -oserror(); + fprintf(fp, "%d %d\n", VERSION, h->ins_mode); + + now = time(NULL); + cnt = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state == PMDA_CACHE_EMPTY) + continue; + if (e->stamp == 0) + e->stamp = now; + fprintf(fp, "%d %d", e->inst, (int)e->stamp); + if (e->keylen > 0) { + char *p = (char *)e->key; + int i; + fprintf(fp, " ["); + for (i = 0; i < e->keylen; i++, p++) + fprintf(fp, "%02x", (*p & 0xff)); + fputc(']', fp); + } + fprintf(fp, " %s\n", e->name); + cnt++; + } + fclose(fp); + h->hstate = 0; + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "After cache_save hstate={"); + if (hstate & DIRTY_INSTANCE) fprintf(stderr, "DIRTY_INSTANCE"); + if (hstate & DIRTY_STAMP) fprintf(stderr, "DIRTY_STAMP"); + fprintf(stderr, "}\n"); + dump(stderr, h, 0); + } +#endif + + return cnt; +} + +void +__pmdaCacheDumpAll(FILE *fp, int do_hash) +{ + hdr_t *h; + + for (h = base; h != NULL; h = h->next) { + dump(fp, h, do_hash); + } +} + +void +__pmdaCacheDump(FILE *fp, pmInDom indom, int do_hash) +{ + hdr_t *h; + + for (h = base; h != NULL; h = h->next) { + if (h->indom == indom) { + dump(fp, h, do_hash); + } + } +} + +static int +store(pmInDom indom, int flags, const char *name, pmInDom inst, int keylen, const char *key, void *private) +{ + hdr_t *h; + entry_t *e; + int sts; + + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + if ((e = find_entry(h, name, inst, &sts)) == NULL) { + + if (flags != PMDA_CACHE_ADD) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCache store: indom %s: instance \"%s\"", pmInDomStr(indom), name); + if (inst != PM_IN_NULL) + fprintf(stderr, " (%d)", inst); + fprintf(stderr, " not in cache: flags=%d not allowed\n", flags); + } +#endif + return PM_ERR_INST; + } + + if ((e = insert_cache(h, name, inst, &sts)) == NULL) + return sts; + h->hstate |= DIRTY_INSTANCE; /* added a new entry */ + } + else { + if (sts == -1) + /* + * name contains space, match up to space (short name) but + * mismatch after that ... cannot store name in the cache, + * the PMDA would have to cull the entry that matches on + * the short name first + */ + return -EINVAL; + } + + switch (flags) { + case PMDA_CACHE_ADD: + e->keylen = keylen; + if (keylen > 0) { + if ((e->key = malloc(keylen)) == NULL) { + __pmNotifyErr(LOG_ERR, + "store: indom %s: unable to allocate memory for keylen=%d", + pmInDomStr(indom), keylen); + return PM_ERR_GENERIC; + } + memcpy(e->key, key, keylen); + } + else + e->key = NULL; + e->state = PMDA_CACHE_ACTIVE; + e->private = private; + e->stamp = 0; /* flag, updated at next cache_save() */ + h->hstate |= DIRTY_STAMP; /* timestamp needs updating */ + break; + + case PMDA_CACHE_HIDE: + e->state = PMDA_CACHE_INACTIVE; + break; + + case PMDA_CACHE_CULL: + e->state = PMDA_CACHE_EMPTY; + /* + * we don't clean anything up, which may be a problem in the + * presence of lots of culling ... see redo_hash() for how + * the culled entries can be reclaimed + */ + h->hstate |= DIRTY_INSTANCE; /* entry will not be saved */ + break; + + default: + return -EINVAL; + } + + return e->inst; +} + +int +pmdaCacheStore(pmInDom indom, int flags, const char *name, void *private) +{ + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + return store(indom, flags, name, PM_IN_NULL, 0, NULL, private); +} + +/* + * Generate a new 31-bit (positive) instance number from a key provided + * as a ``hint'' via key[] (first keylen bytes) or name[] if keylen < 1 + * or key == NULL ... useful for compressing natural 64-bit or larger + * instance identifiers into the 31-bits required for the PCP APIs + * and PDUs. + */ +int +pmdaCacheStoreKey(pmInDom indom, int flags, const char *name, int keylen, const void *key, void *private) +{ + int inst; + int sts; + int i; + __uint32_t try = 0; + hdr_t *h; + entry_t *e; + const char *mykey; + int mykeylen; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if (flags != PMDA_CACHE_ADD) + return store(indom, flags, name, PM_IN_NULL, 0, NULL, private); + + /* + * This is the PMDA_CACHE_ADD case, so need to find an instance id + */ + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + if (keylen < 1 || key == NULL) { + /* use name[] instead of keybuf[] */ + mykey = (const char *)name; + mykeylen = strlen(name); + } + else { + mykey = key; + mykeylen = keylen; + } + + if ((e = find_entry(h, name, PM_IN_NULL, &sts)) != NULL) { + /* + * cache entry already exists for this name ... + * if keys are not equal => failure + */ + if (key_eq(e, mykeylen, mykey) == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance \"%s\" (%d) in cache ", pmInDomStr(indom), e->name, e->inst); + KeyStr(stderr, e->keylen, (const char *)e->key); + fprintf(stderr, " does not match new entry "); + KeyStr(stderr, mykeylen, mykey); + fputc('\n', stderr); + } +#endif + return PM_ERR_INST; + } + /* keys the same, use inst from existing entry */ + inst = e->inst; + } + else { + /* we're in the inst guessing game ... */ + for (i = 0; i < MAX_HASH_TRY; i++) { + try = hash(mykey, mykeylen, try); + /* strip top bit ... instance id must be positive */ + inst = try & ~(1 << (8*sizeof(__uint32_t)-1)); + e = find_entry(h, NULL, inst, &sts); + if (e == NULL) { + h->keyhash_cnt[i]++; + break; + } + /* + * Found matching entry using the guessed inst ... + * + * If the key[]s are the same and the name[]s are the same + * then the matching entry is already in the cache, so use + * this instance identifier. + * + * If the key[]s match, but the name[]s are different, this + * is an error (duplicate instance name). + * + * If the names[] match, but the key[]s are different, this + * is an error (duplicate key). + * + * Otherwise instance id is in use for another instance, so + * keep trying by rehashing. + */ + if (strcmp(e->name, name) == 0) { + if (key_eq(e, mykeylen, mykey) == 1) + break; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance \"%s\" (%d) in cache, ", pmInDomStr(indom), e->name, e->inst); + KeyStr(stderr, e->keylen, (const char *)e->key); + fprintf(stderr, " does not match new entry "); + KeyStr(stderr, mykeylen, mykey); + fputc('\n', stderr); + } +#endif + return PM_ERR_INST; + } + else if (key_eq(e, mykeylen, mykey) == 1) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCacheStoreKey: indom %s: instance %d ", pmInDomStr(indom), e->inst); + KeyStr(stderr, e->keylen, (const char *)e->key); + fprintf(stderr, " in cache, name \"%s\" does not match new entry \"%s\"\n", e->name, name); + } +#endif + return PM_ERR_INST; + } + } + if (i == MAX_HASH_TRY) { + /* failed after MAX_HASH_TRY rehash attempts ... */ + __pmNotifyErr(LOG_ERR, + "pmdaCacheStoreKey: indom %s: unable allocate a new id for instance \"%s\" based on a key of %d bytes\n", + pmInDomStr(h->indom), name, keylen); + return PM_ERR_GENERIC; + } + + /* + * when using key[] or name[] as a hint, we permanently change + * to PMDA_CACHE_REUSE mode + */ + h->ins_mode = 1; + } + + return store(indom, flags, name, inst, mykeylen, mykey, private); +} + +int pmdaCacheOp(pmInDom indom, int op) +{ + hdr_t *h; + entry_t *e; + int sts; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if (op == PMDA_CACHE_CHECK) { + /* is there a cache for this one? */ + for (h = base; h != NULL; h = h->next) { + if (h->indom == indom) + return 1; + } + return 0; + } + + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + switch (op) { + case PMDA_CACHE_LOAD: + return load_cache(h); + + case PMDA_CACHE_SAVE: + return save_cache(h, DIRTY_INSTANCE); + + case PMDA_CACHE_SYNC: + return save_cache(h, DIRTY_INSTANCE|DIRTY_STAMP); + + case PMDA_CACHE_STRINGS: + /* must be set before any cache entries are added */ + if (h->nentry > 0) + return -E2BIG; + h->hstate |= CACHE_STRINGS; + return 0; + + case PMDA_CACHE_ACTIVE: + sts = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state == PMDA_CACHE_INACTIVE) { + e->state = PMDA_CACHE_ACTIVE; + sts++; + } + } + /* no instances added or deleted, so no need to save */ + return sts; + + case PMDA_CACHE_INACTIVE: + sts = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state == PMDA_CACHE_ACTIVE) { + e->state = PMDA_CACHE_INACTIVE; + sts++; + } + } + /* no instances added or deleted, so no need to save */ + return sts; + + case PMDA_CACHE_CULL: + sts = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state != PMDA_CACHE_EMPTY) { + e->state = PMDA_CACHE_EMPTY; + sts++; + } + } + if (sts > 0) + h->hstate |= DIRTY_INSTANCE; /* entries culled */ + return sts; + + case PMDA_CACHE_SIZE: + return h->nentry; + + case PMDA_CACHE_SIZE_ACTIVE: + sts = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state == PMDA_CACHE_ACTIVE) + sts++; + } + return sts; + + case PMDA_CACHE_SIZE_INACTIVE: + sts = 0; + for (e = h->first; e != NULL; e = e->next) { + if (e->state == PMDA_CACHE_INACTIVE) + sts++; + } + return sts; + + case PMDA_CACHE_REUSE: + h->ins_mode = 1; + return 0; + + case PMDA_CACHE_REORG: + redo_hash(h, 0); + return 0; + + case PMDA_CACHE_WALK_REWIND: + walk_cache(h, PMDA_CACHE_WALK_REWIND); + return 0; + + case PMDA_CACHE_WALK_NEXT: + while ((e = walk_cache(h, PMDA_CACHE_WALK_NEXT)) != NULL) { + if (e->state == PMDA_CACHE_ACTIVE) + return e->inst; + } + return -1; + + case PMDA_CACHE_DUMP: + dump(stderr, h, 0); + return 0; + + case PMDA_CACHE_DUMP_ALL: + dump(stderr, h, 1); + return 0; + + default: + return -EINVAL; + } +} + +int pmdaCacheLookupName(pmInDom indom, const char *name, int *inst, void **private) +{ + hdr_t *h; + entry_t *e; + int sts; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + if ((e = find_entry(h, name, PM_IN_NULL, &sts)) == NULL) { + if (sts == 0) sts = PM_ERR_INST; + return sts; + } + + if (private != NULL) + *private = e->private; + + if (inst != NULL) + *inst = e->inst; + + return e->state; +} + +int pmdaCacheLookup(pmInDom indom, int inst, char **name, void **private) +{ + hdr_t *h; + entry_t *e; + int sts; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + if ((e = find_entry(h, NULL, inst, &sts)) == NULL) { + if (sts == 0) sts = PM_ERR_INST; + return sts; + } + + if (name != NULL) + *name = e->name; + if (private != NULL) + *private = e->private; + + return e->state; +} + +int pmdaCacheLookupKey(pmInDom indom, const char *name, int keylen, const void *key, char **oname, int *inst, void **private) +{ + hdr_t *h; + entry_t *e; + int sts; + const char *mykey; + int mykeylen; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + if (keylen < 1 || key == NULL) { + /* use name[] instead of keybuf[] */ + mykey = (const char *)name; + mykeylen = strlen(name); + } + else { + mykey = key; + mykeylen = keylen; + } + + /* + * No hash list for key[]s ... have to walk the cache. + * pmdaCacheStoreKey() ensures the key[]s are unique, so first match + * wins. + */ + walk_cache(h, PMDA_CACHE_WALK_REWIND); + while ((e = walk_cache(h, PMDA_CACHE_WALK_NEXT)) != NULL) { + if (e->state == PMDA_CACHE_EMPTY) + continue; + if (key_eq(e, mykeylen, mykey) == 1) { + if (oname != NULL) + *oname = e->name; + if (inst != NULL) + *inst = e->inst; + if (private != NULL) + *private = e->private; + return e->state; + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + fprintf(stderr, "pmdaCacheLookupKey: indom %s: ", pmInDomStr(h->indom)); + KeyStr(stderr, mykeylen, mykey); + fprintf(stderr, ": no matching key in cache\n"); + } +#endif + return PM_ERR_INST; +} + +int pmdaCachePurge(pmInDom indom, time_t recent) +{ + hdr_t *h; + entry_t *e; + time_t epoch = time(NULL) - recent; + int cnt; + int sts; + + if (indom == PM_INDOM_NULL) + return PM_ERR_INDOM; + + if ((h = find_cache(indom, &sts)) == NULL) + return sts; + + cnt = 0; + for (e = h->first; e != NULL; e = e->next) { + /* + * e->stamp == 0 => recently ACTIVE and no subsequent SAVE ... + * keep these ones + */ + if (e->stamp != 0 && e->stamp < epoch) { + e->state = PMDA_CACHE_EMPTY; + cnt++; + } + } + if (cnt > 0) + h->hstate |= DIRTY_INSTANCE; /* entries marked empty */ + + return cnt; +} + +/* +-------------------------------------------------------------------- +lookup2.c, by Bob Jenkins, December 1996, Public Domain. +hash(), hash2(), hash3, and mix() are externally useful functions. +Routines to test the hash are included if SELF_TEST is defined. +You can use this free for any purpose. It has no warranty. +-------------------------------------------------------------------- +*/ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +typedef unsigned long int ub4; /* unsigned 4-byte quantities */ +typedef unsigned char ub1; + +#define hashsize(n) ((ub4)1<<(n)) +#define hashmask(n) (hashsize(n)-1) + +/* +-------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. +For every delta with one or two bit set, and the deltas of all three + high bits or all three low bits, whether the original value of a,b,c + is almost all zero or is uniformly distributed, +* If mix() is run forward or backward, at least 32 bits in a,b,c + have at least 1/4 probability of changing. +* If mix() is run forward, every bit of c will change between 1/3 and + 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) +mix() was built out of 36 single-cycle latency instructions in a + structure that could supported 2x parallelism, like so: + a -= b; + a -= c; x = (c>>13); + b -= c; a ^= x; + b -= a; x = (a<<8); + c -= a; b ^= x; + c -= b; x = (b>>13); + ... + Unfortunately, superscalar Pentiums and Sparcs can't take advantage + of that parallelism. They've also turned some of those single-cycle + latency instructions into multi-cycle latency instructions. Still, + this is the fastest good hash I could find. There were about 2^^68 + to choose from. I only looked at a billion or so. +-------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= b; a -= c; a ^= (c>>13); \ + b -= c; b -= a; b ^= (a<<8); \ + c -= a; c -= b; c ^= (b>>13); \ + a -= b; a -= c; a ^= (c>>12); \ + b -= c; b -= a; b ^= (a<<16); \ + c -= a; c -= b; c ^= (b>>5); \ + a -= b; a -= c; a ^= (c>>3); \ + b -= c; b -= a; b ^= (a<<10); \ + c -= a; c -= b; c ^= (b>>15); \ +} + +/* +-------------------------------------------------------------------- +hash() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + len : the length of the key, counting by bytes + level : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Every 1-bit and 2-bit delta achieves avalanche. +About 36+6len instructions. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (ub1 **)k, do it like this: + for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h); + +By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net. You may use this +code any way you wish, private, educational, or commercial. It's free. + +See http://burlteburtle.net/bob/hash/evahash.html +Use for hash table lookup, or anything where one collision in 2^32 is +acceptable. Do NOT use for cryptographic purposes. +-------------------------------------------------------------------- +*/ + +static __uint32_t +hash(const char *k, int length, __uint32_t initval) +{ + __uint32_t a,b,c,len; + + /* Set up the internal state */ + len = length; + a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ + c = initval; /* the previous hash value */ + + /*---------------------------------------- handle most of the key */ + while (len >= 12) + { + a += (k[0] +((__uint32_t)k[1]<<8) +((__uint32_t)k[2]<<16) + +((__uint32_t)k[3]<<24)); + b += (k[4] +((__uint32_t)k[5]<<8) +((__uint32_t)k[6]<<16) + +((__uint32_t)k[7]<<24)); + c += (k[8] +((__uint32_t)k[9]<<8) + +((__uint32_t)k[10]<<16)+((__uint32_t)k[11]<<24)); + mix(a,b,c); + k += 12; len -= 12; + } + + /*------------------------------------- handle the last 11 bytes */ + c += length; + switch(len) /* all the case statements fall through */ + { + case 11: c+=((__uint32_t)k[10]<<24); + case 10: c+=((__uint32_t)k[9]<<16); + case 9 : c+=((__uint32_t)k[8]<<8); + /* the first byte of c is reserved for the length */ + case 8 : b+=((__uint32_t)k[7]<<24); + case 7 : b+=((__uint32_t)k[6]<<16); + case 6 : b+=((__uint32_t)k[5]<<8); + case 5 : b+=k[4]; + case 4 : a+=((__uint32_t)k[3]<<24); + case 3 : a+=((__uint32_t)k[2]<<16); + case 2 : a+=((__uint32_t)k[1]<<8); + case 1 : a+=k[0]; + /* case 0: nothing left to add */ + } + mix(a,b,c); + /*-------------------------------------------- report the result */ + return c; +} diff --git a/src/libpcp_pmda/src/callback.c b/src/libpcp_pmda/src/callback.c new file mode 100644 index 0000000..c1a1d86 --- /dev/null +++ b/src/libpcp_pmda/src/callback.c @@ -0,0 +1,753 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "libdefs.h" + +/* + * count the number of instances in an instance domain + */ + +int +__pmdaCntInst(pmInDom indom, pmdaExt *pmda) +{ + int i; + int sts = 0; + + if (indom == PM_INDOM_NULL) + return 1; + if (pmdaCacheOp(indom, PMDA_CACHE_CHECK)) { + sts = pmdaCacheOp(indom, PMDA_CACHE_SIZE_ACTIVE); + } + else { + for (i = 0; i < pmda->e_nindoms; i++) { + if (pmda->e_indoms[i].it_indom == indom) { + sts = pmda->e_indoms[i].it_numinst; + break; + } + } + if (i == pmda->e_nindoms) { + char strbuf[20]; + __pmNotifyErr(LOG_WARNING, "cntinst: unknown indom %s", pmInDomStr_r(indom, strbuf, sizeof(strbuf))); + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + char strbuf[20]; + fprintf(stderr, "__pmdaCntInst(indom=%s) -> %d\n", pmInDomStr_r(indom, strbuf, sizeof(strbuf)), sts); + } +#endif + + return sts; +} + +/* + * commence a new round of instance selection + */ + +static pmdaIndom last; + +/* + * State between here and __pmdaNextInst is a little strange + * + * for the classical method, + * - pmda->e_idp is set here (points into indomtab[]) and + * pmda->e_idp->it_indom is used in __pmdaNextInst + * + * for the cache method + * - pmda->e_idp is set here (points into last) which is also set + * up with the it_indom field (other fields in last are not used), + * and pmda->e_idp->it_indom in __pmdaNextInst + * + * In both cases, pmda->e_ordinal and pmda->e_singular are set here + * and updated in __pmdaNextInst. + * + * As in most other places, this is not thread-safe and we assume we + * call __pmdaStartInst and then repeatedly call __pmdaNextInst all + * for the same indom, before calling __pmdaStartInst again. + * + * If we could do this again, adding an indom argument to __pmdaNextInst + * would be a better design, but the API to __pmdaNextInst has escaped. + */ +void +__pmdaStartInst(pmInDom indom, pmdaExt *pmda) +{ + int i; + + pmda->e_ordinal = pmda->e_singular = -1; + if (indom == PM_INDOM_NULL) { + /* singular value */ + pmda->e_singular = 0; + } + else { + if (pmdaCacheOp(indom, PMDA_CACHE_CHECK)) { + pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND); + last.it_indom = indom; + pmda->e_idp = &last; + pmda->e_ordinal = 0; + } + else { + for (i = 0; i < pmda->e_nindoms; i++) { + if (pmda->e_indoms[i].it_indom == indom) { + /* multiple values are possible */ + pmda->e_idp = &pmda->e_indoms[i]; + pmda->e_ordinal = 0; + break; + } + } + } + } + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + char strbuf[20]; + fprintf(stderr, "__pmdaStartInst(indom=%s) e_ordinal=%d\n", + pmInDomStr_r(indom, strbuf, sizeof(strbuf)), pmda->e_ordinal); + } +#endif + return; +} + +/* + * select the next instance + */ + +int +__pmdaNextInst(int *inst, pmdaExt *pmda) +{ + int j; + int myinst; + + if (pmda->e_singular == 0) { + /* PM_INDOM_NULL ... just the one value */ + *inst = 0; + pmda->e_singular = -1; + return 1; + } + if (pmda->e_ordinal >= 0) { + /* scan for next value in the profile */ + if (pmda->e_idp == &last) { + /* cache-driven */ + while ((myinst = pmdaCacheOp(pmda->e_idp->it_indom, PMDA_CACHE_WALK_NEXT)) != -1) { + pmda->e_ordinal++; + if (__pmInProfile(pmda->e_idp->it_indom, pmda->e_prof, myinst)) { + *inst = myinst; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + char strbuf[20]; + fprintf(stderr, "__pmdaNextInst(indom=%s) -> %d e_ordinal=%d (cache)\n", + pmInDomStr_r(pmda->e_idp->it_indom, strbuf, sizeof(strbuf)), myinst, pmda->e_ordinal); + } +#endif + return 1; + } + } + } + else { + /* indomtab[]-driven */ + for (j = pmda->e_ordinal; j < pmda->e_idp->it_numinst; j++) { + if (__pmInProfile(pmda->e_idp->it_indom, + pmda->e_prof, + pmda->e_idp->it_set[j].i_inst)) { + *inst = pmda->e_idp->it_set[j].i_inst; + pmda->e_ordinal = j+1; +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_INDOM) { + char strbuf[20]; + fprintf(stderr, "__pmdaNextInst(indom=%s) -> %d e_ordinal=%d\n", + pmInDomStr_r(pmda->e_idp->it_indom, strbuf, sizeof(strbuf)), *inst, pmda->e_ordinal); + } +#endif + return 1; + } + } + } + pmda->e_ordinal = -1; + } + return 0; +} + + +/* + * Helper routines for performing metric table searches. + * + * There are currently three ways - using the PMID hash that hangs off + * of the e_ext structure, using the direct mapping mechanism (require + * PMIDs be allocated one after-the-other, all in one cluster), or via + * a linear search of the metric table array. + */ + +static pmdaMetric * +__pmdaHashedSearch(pmID pmid, __pmHashCtl *hash) +{ + __pmHashNode *node; + + if ((node = __pmHashSearch(pmid, hash)) == NULL) + return NULL; + return (pmdaMetric *)node->data; +} + +static pmdaMetric * +__pmdaDirectSearch(pmID pmid, pmdaExt *pmda) +{ + __pmID_int *pmidp = (__pmID_int *)&pmid; + + /* + * pmidp->domain is correct ... PMCD guarantees this, but + * pmda->e_direct only works for a single cluster + */ + if (pmidp->item < pmda->e_nmetrics && + pmidp->cluster == + ((__pmID_int *)&pmda->e_metrics[pmidp->item].m_desc.pmid)->cluster) { + /* pmidp->item is unsigned, so must be >= 0 */ + return &pmda->e_metrics[pmidp->item]; + } + return NULL; +} + +static pmdaMetric * +__pmdaLinearSearch(pmID pmid, pmdaExt *pmda) +{ + int i; + + for (i = 0; i < pmda->e_nmetrics; i++) { + if (pmda->e_metrics[i].m_desc.pmid == pmid) { + /* found the hard way */ + return &pmda->e_metrics[i]; + } + } + return NULL; +} + +/* + * Save the profile away for use in __pmdaNextInst() during subsequent + * fetches ... it is the _caller_ of pmdaProfile()'s responsibility to + * ensure that the profile is not freed while it is being used here. + * + * For DSO pmdas, the profiles are managed per client in DoProfile() and + * DeleteClient() but sent to the pmda in SendFetch() + * + * For daemon pmdas, the profile is received from pmcd in __pmdaMainPDU + * and the last received profile is held there + */ + +int +pmdaProfile(__pmProfile *prof, pmdaExt *pmda) +{ + pmda->e_prof = prof; + return 0; +} + +/* + * return desciption of an instance or instance domain + */ + +int +pmdaInstance(pmInDom indom, int inst, char *name, __pmInResult **result, pmdaExt *pmda) +{ + int i; + int namelen; + int err = 0; + __pmInResult *res; + pmdaIndom *idp = NULL; /* initialize to pander to gcc */ + int have_cache = 0; + int myinst; + char *np; + + if (pmdaCacheOp(indom, PMDA_CACHE_CHECK)) { + have_cache = 1; + } + else { + /* + * check this is an instance domain we know about -- code below + * assumes this test is complete + */ + for (i = 0; i < pmda->e_nindoms; i++) { + if (pmda->e_indoms[i].it_indom == indom) + break; + } + if (i >= pmda->e_nindoms) + return PM_ERR_INDOM; + idp = &pmda->e_indoms[i]; + } + + if ((res = (__pmInResult *)malloc(sizeof(*res))) == NULL) + return -oserror(); + res->indom = indom; + + if (name == NULL && inst == PM_IN_NULL) + res->numinst = __pmdaCntInst(indom, pmda); + else + res->numinst = 1; + + if (inst == PM_IN_NULL) { + if ((res->instlist = (int *)malloc(res->numinst * sizeof(res->instlist[0]))) == NULL) { + free(res); + return -oserror(); + } + } + else + res->instlist = NULL; + + if (name == NULL) { + if ((res->namelist = (char **)malloc(res->numinst * sizeof(res->namelist[0]))) == NULL) { + __pmFreeInResult(res); + return -oserror(); + } + for (i = 0; i < res->numinst; i++) + res->namelist[0] = NULL; + } + else + res->namelist = NULL; + + if (name == NULL && inst == PM_IN_NULL) { + /* return inst and name for everything */ + if (have_cache) { + pmdaCacheOp(indom, PMDA_CACHE_WALK_REWIND); + i = 0; + while (i < res->numinst && (myinst = pmdaCacheOp(indom, PMDA_CACHE_WALK_NEXT)) != -1) { + if (pmdaCacheLookup(indom, myinst, &np, NULL) != PMDA_CACHE_ACTIVE) + continue; + + res->instlist[i] = myinst; + if ((res->namelist[i++] = strdup(np)) == NULL) { + __pmFreeInResult(res); + return -oserror(); + } + } + } + else { + for (i = 0; i < res->numinst; i++) { + res->instlist[i] = idp->it_set[i].i_inst; + if ((res->namelist[i] = strdup(idp->it_set[i].i_name)) == NULL) { + __pmFreeInResult(res); + return -oserror(); + } + } + } + } + else if (name == NULL) { + /* given an inst, return the name */ + if (have_cache) { + if (pmdaCacheLookup(indom, inst, &np, NULL) == PMDA_CACHE_ACTIVE) { + if ((res->namelist[0] = strdup(np)) == NULL) { + __pmFreeInResult(res); + return -oserror(); + } + } + else + err = 1; + } + else { + for (i = 0; i < idp->it_numinst; i++) { + if (inst == idp->it_set[i].i_inst) { + if ((res->namelist[0] = strdup(idp->it_set[i].i_name)) == NULL) { + __pmFreeInResult(res); + return -oserror(); + } + break; + } + } + if (i == idp->it_numinst) + err = 1; + } + } + else if (inst == PM_IN_NULL && (namelen = (int)strlen(name)) > 0) { + if (have_cache) { + if (pmdaCacheLookupName(indom, name, &myinst, NULL) == PMDA_CACHE_ACTIVE) + res->instlist[0] = myinst; + else + err = 1; + } + else { + /* given a name, return an inst. If the name contains spaces, + * only exact matches are good enough for us, otherwise, we're + * prepared to accept a match upto the first space in the + * instance name on the assumption that pmdas will play by the + * rules and guarantee the first "word" in the instance name + * is unique. That allows for the things like "1 5 15" to match + * instances for kernel.all.load["1 minute","5 minute","15 minutes"] + */ + char * nspace = strchr (name, ' '); + + for (i = 0; i < idp->it_numinst; i++) { + char *instname = idp->it_set[i].i_name; + if (strcmp(name, instname) == 0) { + /* accept an exact match */ +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + fprintf(stderr, + "pmdaInstance: exact match name=%s id=%d\n", + name, idp->it_set[i].i_inst); + } +#endif + res->instlist[0] = idp->it_set[i].i_inst; + break; + } + else if (nspace == NULL) { + /* all of name must match instname up to the the first + * space in instname. */ + char *p = strchr(instname, ' '); + if (p != NULL) { + int len = (int)(p - instname); + if (namelen == len && strncmp(name, instname, len) == 0) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + fprintf(stderr, "pmdaInstance: matched argument name=\"%s\" with indom id=%d name=\"%s\" len=%d\n", + name, idp->it_set[i].i_inst, instname, len); + } +#endif + res->instlist[0] = idp->it_set[i].i_inst; + break; + } + } + } + } + if (i == idp->it_numinst) + err = 1; + } + } + else + err = 1; + if (err == 1) { + /* bogus arguments or instance id/name */ + __pmFreeInResult(res); + return PM_ERR_INST; + } + + *result = res; + return 0; +} + +/* + * resize the pmResult and call the e_callback for each metric instance + * required in the profile. + */ + +int +pmdaFetch(int numpmid, pmID pmidlist[], pmResult **resp, pmdaExt *pmda) +{ + int i; /* over pmidlist[] */ + int j; /* over metatab and vset->vlist[] */ + int sts; + int need; + int inst; + int numval; + pmValueSet *vset; + pmDesc *dp; + pmdaMetric *metap; + pmAtomValue atom; + int type; + e_ext_t *extp = (e_ext_t *)pmda->e_ext; + + if (extp->pmda_interface >= PMDA_INTERFACE_5) + __pmdaSetContext(pmda->e_context); + + if (numpmid > extp->maxnpmids) { + if (extp->res != NULL) + free(extp->res); + /* (numpmid - 1) because there's room for one valueSet in a pmResult */ + need = (int)sizeof(pmResult) + (numpmid - 1) * (int)sizeof(pmValueSet *); + if ((extp->res = (pmResult *) malloc(need)) == NULL) + return -oserror(); + extp->maxnpmids = numpmid; + } + + extp->res->timestamp.tv_sec = 0; + extp->res->timestamp.tv_usec = 0; + extp->res->numpmid = numpmid; + + for (i = 0; i < numpmid; i++) { + if (pmda->e_flags & PMDA_EXT_FLAG_HASHED) + metap = __pmdaHashedSearch(pmidlist[i], &extp->hashpmids); + else if (pmda->e_direct) + metap = __pmdaDirectSearch(pmidlist[i], pmda); + else + metap = __pmdaLinearSearch(pmidlist[i], pmda); + + if (metap) { + dp = &(metap->m_desc); + if (dp->indom != PM_INDOM_NULL) { + /* count instances in the profile */ + numval = 0; + /* count instances in indom */ + __pmdaStartInst(dp->indom, pmda); + while (__pmdaNextInst(&inst, pmda)) { + numval++; + } + } + else { + /* singular instance domains */ + numval = 1; + } + } + else { + dp = NULL; + /* dynamic name metrics may often vanish, avoid log spam */ + if (extp->pmda_interface < PMDA_INTERFACE_4) { + char strbuf[20]; + __pmNotifyErr(LOG_ERR, + "pmdaFetch: Requested metric %s is not defined", + pmIDStr_r(pmidlist[i], strbuf, sizeof(strbuf))); + } + numval = PM_ERR_PMID; + } + + + /* Must use individual malloc()s because of pmFreeResult() */ + if (numval >= 1) + extp->res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) + + (numval - 1)*sizeof(pmValue)); + else + extp->res->vset[i] = vset = (pmValueSet *)malloc(sizeof(pmValueSet) - + sizeof(pmValue)); + if (vset == NULL) { + sts = -oserror(); + goto error; + } + vset->pmid = pmidlist[i]; + vset->numval = numval; + vset->valfmt = PM_VAL_INSITU; + if (vset->numval <= 0) + continue; + + if (dp->indom == PM_INDOM_NULL) + inst = PM_IN_NULL; + else { + __pmdaStartInst(dp->indom, pmda); + __pmdaNextInst(&inst, pmda); + } + type = dp->type; + j = 0; + do { + if (j == numval) { + /* more instances than expected! */ + numval++; + extp->res->vset[i] = vset = (pmValueSet *)realloc(vset, + sizeof(pmValueSet) + (numval - 1)*sizeof(pmValue)); + if (vset == NULL) { + sts = -oserror(); + goto error; + } + } + vset->vlist[j].inst = inst; + + if ((sts = (*(pmda->e_fetchCallBack))(metap, inst, &atom)) < 0) { + char strbuf[20]; + + pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)); + if (sts == PM_ERR_PMID) { + __pmNotifyErr(LOG_ERR, + "pmdaFetch: PMID %s not handled by fetch callback\n", + strbuf); + } + else if (sts == PM_ERR_INST) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_ERR, + "pmdaFetch: Instance %d of PMID %s not handled by fetch callback\n", + inst, strbuf); + } +#endif + } + else if (sts == PM_ERR_APPVERSION || + sts == PM_ERR_PERMISSION || + sts == PM_ERR_AGAIN || + sts == PM_ERR_NYI) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_ERR, + "pmdaFetch: Unavailable metric PMID %s[%d]\n", + strbuf, inst); + } +#endif + } + else { + __pmNotifyErr(LOG_ERR, + "pmdaFetch: Fetch callback error from metric PMID %s[%d]: %s\n", + strbuf, inst, pmErrStr(sts)); + } + } + else { + /* + * PMDA_INTERFACE_2 + * >= 0 => OK + * PMDA_INTERFACE_3 or PMDA_INTERFACE_4 + * == 0 => no values + * > 0 => OK + * PMDA_INTERFACE_5 or later + * == 0 (PMDA_FETCH_NOVALUES) => no values + * == 1 (PMDA_FETCH_STATIC) or > 2 => OK + * == 2 (PMDA_FETCH_DYNAMIC) => OK and free(atom.vp) + * after __pmStuffValue() called + */ + if (extp->pmda_interface == PMDA_INTERFACE_2 || + (extp->pmda_interface >= PMDA_INTERFACE_3 && sts > 0)) { + int lsts; + + if ((lsts = __pmStuffValue(&atom, &vset->vlist[j], type)) == PM_ERR_TYPE) { + char strbuf[20]; + char st2buf[20]; + __pmNotifyErr(LOG_ERR, + "pmdaFetch: Descriptor type (%s) for metric %s is bad", + pmTypeStr_r(type, strbuf, sizeof(strbuf)), + pmIDStr_r(dp->pmid, st2buf, sizeof(st2buf))); + } + else if (lsts >= 0) { + vset->valfmt = lsts; + j++; + } + if (extp->pmda_interface >= PMDA_INTERFACE_5 && sts == PMDA_FETCH_DYNAMIC) { + if (type == PM_TYPE_STRING) + free(atom.cp); + else if (type == PM_TYPE_AGGREGATE) + free(atom.vbp); + else { + char strbuf[20]; + char st2buf[20]; + __pmNotifyErr(LOG_WARNING, + "pmdaFetch: Attempt to free value for metric %s of wrong type %s\n", + pmIDStr_r(dp->pmid, strbuf, sizeof(strbuf)), + pmTypeStr_r(type, st2buf, sizeof(st2buf))); + } + } + if (lsts < 0) + sts = lsts; + } + } + } while (dp->indom != PM_INDOM_NULL && __pmdaNextInst(&inst, pmda)); + + if (j == 0) + vset->numval = sts; + else + vset->numval = j; + + } + *resp = extp->res; + return 0; + + error: + + if (i) { + extp->res->numpmid = i; + __pmFreeResultValues(extp->res); + } + return sts; +} + +/* + * Return the metric description + */ + +int +pmdaDesc(pmID pmid, pmDesc *desc, pmdaExt *pmda) +{ + e_ext_t *extp = (e_ext_t *)pmda->e_ext; + pmdaMetric *metric; + char strbuf[32]; + + if (extp->pmda_interface >= PMDA_INTERFACE_5) + __pmdaSetContext(pmda->e_context); + + if (pmda->e_flags & PMDA_EXT_FLAG_HASHED) + metric = __pmdaHashedSearch(pmid, &extp->hashpmids); + else if (pmda->e_direct) + metric = __pmdaDirectSearch(pmid, pmda); + else + metric = __pmdaLinearSearch(pmid, pmda); + + if (metric) { + *desc = metric->m_desc; + return 0; + } + + __pmNotifyErr(LOG_ERR, "Requested metric %s is not defined", + pmIDStr_r(pmid, strbuf, sizeof(strbuf))); + return PM_ERR_PMID; +} + +/* + * Return the help text for a metric + */ + +int +pmdaText(int ident, int type, char **buffer, pmdaExt *pmda) +{ + e_ext_t *extp = (e_ext_t *)pmda->e_ext; + + if (extp->pmda_interface >= PMDA_INTERFACE_5) + __pmdaSetContext(pmda->e_context); + + if (pmda->e_help >= 0) { + if ((type & PM_TEXT_PMID) == PM_TEXT_PMID) + *buffer = pmdaGetHelp(pmda->e_help, (pmID)ident, type); + else + *buffer = pmdaGetInDomHelp(pmda->e_help, (pmInDom)ident, type); + } + else + *buffer = NULL; + + return (*buffer == NULL) ? PM_ERR_TEXT : 0; +} + +/* + * Tell PMCD there is nothing to store + */ + +int +pmdaStore(pmResult *result, pmdaExt *pmda) +{ + return PM_ERR_PERMISSION; +} + +/* + * Expect routines pmdaPMID(), pmdaName() and pmdaChildren() below + * to be overridden with real routines for any PMDA that is + * using PMDA_INTERFACE_4 or later and supporting dynamic metrics. + * + * Expect the pmdaAttribute() routine to be overridden with a real + * routine for a PMDA using PMDA_INTERFACE_6 or later supporting + * metrics whose behaviour depends on clients being authenticated. + * + * These implementations are stubs that return appropriate errors + * if they are ever called. + */ + +int +pmdaPMID(const char *name, pmID *pmid, pmdaExt *pmda) +{ + return PM_ERR_NAME; +} + +int +pmdaName(pmID pmid, char ***nameset, pmdaExt *pmda) +{ + return PM_ERR_PMID; +} + +int +pmdaChildren(const char *name, int traverse, char ***offspring, int **status, pmdaExt *pmda) +{ + return PM_ERR_NAME; +} + +int +pmdaAttribute(int context, int attribute, const char *value, int size, pmdaExt *pmda) +{ + return 0; /* simply ignore everything by default */ +} diff --git a/src/libpcp_pmda/src/context.c b/src/libpcp_pmda/src/context.c new file mode 100644 index 0000000..fb0f0e2 --- /dev/null +++ b/src/libpcp_pmda/src/context.c @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" + +static int last_ctx = -1; /* not thread safe! */ + +void +__pmdaSetContext(int ctx) +{ + last_ctx = ctx; +} + +int +pmdaGetContext(void) +{ + return last_ctx; +} diff --git a/src/libpcp_pmda/src/dynamic.c b/src/libpcp_pmda/src/dynamic.c new file mode 100644 index 0000000..3d6e73d --- /dev/null +++ b/src/libpcp_pmda/src/dynamic.c @@ -0,0 +1,217 @@ +/* + * Dynamic namespace metrics, PMDA helper routines. + * + * Copyright (c) 2013-2014 Red Hat. + * Copyright (c) 2010 Aconex. 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" + +static struct dynamic { + const char *prefix; + int prefixlen; + int mtabcount; /* internal use only */ + int extratrees; /* internal use only */ + int nclusters; + int *clusters; + pmdaUpdatePMNS pmnsupdate; + pmdaUpdateText textupdate; + pmdaUpdateMetric mtabupdate; + pmdaCountMetrics mtabcounts; + pmdaNameSpace *pmns; + pmdaMetric *metrics; /* original fixed table */ + int nmetrics; /* fixed metrics number */ + unsigned int clustermask; /* mask out parts of PMID cluster */ +} *dynamic; + +static int dynamic_count; + +static inline unsigned int +dynamic_pmid_cluster(int index, pmID pmid) +{ + if (dynamic[index].clustermask) + return pmid_cluster(pmid) & dynamic[index].clustermask; + return pmid_cluster(pmid); +} + +int +pmdaDynamicSetClusterMask(const char *prefix, unsigned int mask) +{ + int i; + + for (i = 0; i < dynamic_count; i++) { + if (strcmp(prefix, dynamic[i].prefix) == 0) + continue; + dynamic[i].clustermask = mask; + return 0; + } + return -EINVAL; +} + +void +pmdaDynamicPMNS(const char *prefix, + int *clusters, int nclusters, + pmdaUpdatePMNS pmnsupdate, pmdaUpdateText textupdate, + pmdaUpdateMetric mtabupdate, pmdaCountMetrics mtabcounts, + pmdaMetric *metrics, int nmetrics) +{ + int size = (dynamic_count+1) * sizeof(struct dynamic); + int *ctab; + size_t ctabsz; + + if ((dynamic = (struct dynamic *)realloc(dynamic, size)) == NULL) { + __pmNotifyErr(LOG_ERR, "out-of-memory registering dynamic metrics"); + return; + } + ctabsz = sizeof(int) * nclusters; + if ((ctab = (int *)malloc(ctabsz)) == NULL) { + __pmNotifyErr(LOG_ERR, "out-of-memory registering dynamic clusters"); + free(dynamic); + return; + } + dynamic[dynamic_count].prefix = prefix; + dynamic[dynamic_count].prefixlen = strlen(prefix); + dynamic[dynamic_count].nclusters = nclusters; + dynamic[dynamic_count].clusters = ctab; + memcpy(dynamic[dynamic_count].clusters, clusters, ctabsz); + dynamic[dynamic_count].pmnsupdate = pmnsupdate; + dynamic[dynamic_count].textupdate = textupdate; + dynamic[dynamic_count].mtabupdate = mtabupdate; + dynamic[dynamic_count].mtabcounts = mtabcounts; + dynamic[dynamic_count].pmns = NULL; + dynamic[dynamic_count].metrics = metrics; + dynamic[dynamic_count].nmetrics = nmetrics; + dynamic[dynamic_count].clustermask = 0; + dynamic_count++; +} + +pmdaNameSpace * +pmdaDynamicLookupName(pmdaExt *pmda, const char *name) +{ + int i; + + for (i = 0; i < dynamic_count; i++) { + if (strncmp(name, dynamic[i].prefix, dynamic[i].prefixlen) == 0) { + if (dynamic[i].pmnsupdate(pmda, &dynamic[i].pmns)) + pmdaDynamicMetricTable(pmda); + return dynamic[i].pmns; + } + } + return NULL; +} + +pmdaNameSpace * +pmdaDynamicLookupPMID(pmdaExt *pmda, pmID pmid) +{ + int i, j, cluster; + + for (i = 0; i < dynamic_count; i++) { + cluster = dynamic_pmid_cluster(i, pmid); + for (j = 0; j < dynamic[i].nclusters; j++) { + if (cluster == dynamic[i].clusters[j]) { + if (dynamic[i].pmnsupdate(pmda, &dynamic[i].pmns)) + pmdaDynamicMetricTable(pmda); + return dynamic[i].pmns; + } + } + } + return NULL; +} + +int +pmdaDynamicLookupText(pmID pmid, int type, char **buf, pmdaExt *pmda) +{ + int i, j; + + for (i = 0; i < dynamic_count; i++) { + int cluster = dynamic_pmid_cluster(i, pmid); + for (j = 0; j < dynamic[i].nclusters; j++) + if (cluster == dynamic[i].clusters[j]) + return dynamic[i].textupdate(pmda, pmid, type, buf); + } + return -ENOENT; +} + +/* + * Call the update function for each new metric we're adding. + * We pass in the original metric, and the new (uninit'd) slot + * which needs to be filled in. All a bit obscure, really. + */ +static pmdaMetric * +dynamic_metric_table(int index, pmdaMetric *offset) +{ + struct dynamic *dp = &dynamic[index]; + int m, tree_count = dp->extratrees; + + for (m = 0; m < dp->nmetrics; m++) { + pmdaMetric *mp = &dp->metrics[m]; + int cluster = dynamic_pmid_cluster(index, mp->m_desc.pmid); + int c, gid; + + for (c = 0; c < dp->nclusters; c++) + if (dp->clusters[c] == cluster) + break; + if (c < dp->nclusters) + for (gid = 0; gid < tree_count; gid++) + dp->mtabupdate(mp, offset++, gid + 1); + } + return offset; +} + +/* + * Iterate through the dynamic table working out how many additional metric + * table entries are needed. Then allocate a new metric table, if needed, + * and run through the dynamic table once again to fill in the additional + * entries. Finally, we update the metric table pointer within the pmdaExt + * for libpcp_pmda callback routines subsequent use. + */ +void +pmdaDynamicMetricTable(pmdaExt *pmda) +{ + int i, trees, total, resize = 0; + pmdaMetric *mtab, *fixed, *offset; + + for (i = 0; i < dynamic_count; i++) + dynamic[i].mtabcount = dynamic[i].extratrees = 0; + + for (i = 0; i < dynamic_count; i++) { + dynamic[i].mtabcounts(&total, &trees); + dynamic[i].mtabcount += total; + dynamic[i].extratrees += trees; + resize += (total * trees); + } + + fixed = dynamic[0].metrics; /* fixed metrics */ + total = dynamic[0].nmetrics; /* and the count */ + + if (resize == 0) { + /* Fits into the default metric table - reset it to original values */ +fallback: + if (pmda->e_metrics != fixed) + free(pmda->e_metrics); + pmdaRehash(pmda, fixed, total); + } else { + resize += total; + if ((mtab = calloc(resize, sizeof(pmdaMetric))) == NULL) + goto fallback; + memcpy(mtab, fixed, total * sizeof(pmdaMetric)); + offset = mtab + total; + for (i = 0; i < dynamic_count; i++) + offset = dynamic_metric_table(i, offset); + if (pmda->e_metrics != fixed) + free(pmda->e_metrics); + pmdaRehash(pmda, mtab, resize); + } +} diff --git a/src/libpcp_pmda/src/events.c b/src/libpcp_pmda/src/events.c new file mode 100644 index 0000000..09abb8d --- /dev/null +++ b/src/libpcp_pmda/src/events.c @@ -0,0 +1,391 @@ +/* + * Service routines for managing a packed array of event records + * + * Copyright (c) 2010 Ken McDonell. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" + +typedef struct { + char *baddr; /* base address of the buffer */ + char *bptr; /* next location to be filled in the buffer */ + void *berp; /* current pmEvent[HighRes]Record in buffer */ + int blen; /* buffer size */ + int bstate; +} bufctl_t; + +#define B_FREE 0 +#define B_INUSE 1 + +static int nbuf; +static bufctl_t *bufs; + +static int +check_buf(bufctl_t *bp, int need) +{ + int offset = bp->bptr - bp->baddr; + int er_offset = (char *)bp->berp - bp->baddr; + + while (bp->blen == 0 || &bp->bptr[need] >= &bp->baddr[bp->blen-1]) { + if (bp->blen == 0) + /* first time, punt on a 512 byte buffer */ + bp->blen = 512; + else + bp->blen *= 2; + if ((bp->baddr = (char *)realloc(bp->baddr, bp->blen)) == NULL) + return -oserror(); + bp->bptr = &bp->baddr[offset]; + bp->berp = (void *)&bp->baddr[er_offset]; + } + return 0; +} + +static int +event_array(void) +{ + int i; + + for (i = 0; i < nbuf; i++) { + if (bufs[i].bstate == B_FREE) + break; + } + + if (i == nbuf) { + nbuf++; + bufs = (bufctl_t *)realloc(bufs, nbuf*sizeof(bufs[0])); + if (bufs == NULL) { + nbuf = 0; + return -oserror(); + } + } + + bufs[i].bptr = bufs[i].baddr = NULL; + bufs[i].blen = 0; + bufs[i].bstate = B_INUSE; + return i; +} + +int +pmdaEventNewArray(void) +{ + int sts = event_array(); + + if (sts >= 0) + pmdaEventResetArray(sts); + return sts; +} + +int +pmdaEventNewHighResArray(void) +{ + int sts = event_array(); + + if (sts >= 0) + pmdaEventResetHighResArray(sts); + return sts; +} + +/* prepare to reuse an array */ +int +pmdaEventResetArray(int idx) +{ + bufctl_t *bp; + pmEventArray *eap; + int sts; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + if ((sts = check_buf(bp, sizeof(pmEventArray) - sizeof(pmEventRecord))) < 0) + return sts; + + eap = (pmEventArray *)bp->baddr; + eap->ea_nrecords = 0; + bp->bptr = bp->baddr + sizeof(pmEventArray) - sizeof(pmEventRecord); + return 0; +} + +int +pmdaEventResetHighResArray(int idx) +{ + bufctl_t *bp; + pmHighResEventArray *hreap; + int sts; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + if ((sts = check_buf(bp, sizeof(*hreap) - sizeof(pmHighResEventRecord))) < 0) + return sts; + + hreap = (pmHighResEventArray *)bp->baddr; + hreap->ea_nrecords = 0; + bp->bptr = bp->baddr + sizeof(*hreap) - sizeof(pmHighResEventRecord); + return 0; +} + +/* release buffer space associated with a packed event array */ +int +pmdaEventReleaseArray(int idx) +{ + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + + free(bufs[idx].baddr); + bufs[idx].bstate = B_FREE; + return 0; +} + +int +pmdaEventReleaseHighResArray(int idx) +{ + return pmdaEventReleaseArray(idx); +} + +int +pmdaEventAddRecord(int idx, struct timeval *tp, int flags) +{ + int sts; + bufctl_t *bp; + pmEventArray *eap; + pmEventRecord *erp; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + + /* use pmdaEventAddMissedRecord for missed records ... */ + if (flags & PM_EVENT_FLAG_MISSED) + return PM_ERR_CONV; + + if ((sts = check_buf(bp, sizeof(*erp) - sizeof(pmEventParameter))) < 0) + return sts; + eap = (pmEventArray *)bp->baddr; + eap->ea_nrecords++; + erp = (pmEventRecord *)bp->bptr; + erp->er_timestamp.tv_sec = (__int32_t)tp->tv_sec; + erp->er_timestamp.tv_usec = (__int32_t)tp->tv_usec; + erp->er_nparams = 0; + erp->er_flags = flags; + bp->berp = (void *)erp; + bp->bptr += sizeof(pmEventRecord) - sizeof(pmEventParameter); + return 0; +} + +int +pmdaEventAddHighResRecord(int idx, struct timespec *ts, int flags) +{ + int sts; + bufctl_t *bp; + pmHighResEventArray *hreap; + pmHighResEventRecord *hrerp; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + + /* use pmdaEventAddMissedRecord for missed records ... */ + if (flags & PM_EVENT_FLAG_MISSED) + return PM_ERR_CONV; + + if ((sts = check_buf(bp, sizeof(*hrerp) - sizeof(pmEventParameter))) < 0) + return sts; + hreap = (pmHighResEventArray *)bp->baddr; + hreap->ea_nrecords++; + hrerp = (pmHighResEventRecord *)bp->bptr; + hrerp->er_timestamp.tv_sec = (__int64_t)ts->tv_sec; + hrerp->er_timestamp.tv_nsec = (__int64_t)ts->tv_nsec; + hrerp->er_nparams = 0; + hrerp->er_flags = flags; + bp->berp = (void *)hrerp; + bp->bptr += sizeof(pmHighResEventRecord) - sizeof(pmEventParameter); + return 0; +} + +int +pmdaEventAddMissedRecord(int idx, struct timeval *tp, int missed) +{ + int sts; + bufctl_t *bp; + pmEventArray *eap; + pmEventRecord *erp; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + + if ((sts = check_buf(bp, sizeof(*erp) - sizeof(pmEventParameter))) < 0) + return sts; + eap = (pmEventArray *)bp->baddr; + eap->ea_nrecords++; + erp = (pmEventRecord *)bp->bptr; + erp->er_timestamp.tv_sec = (__int32_t)tp->tv_sec; + erp->er_timestamp.tv_usec = (__int32_t)tp->tv_usec; + erp->er_nparams = missed; + erp->er_flags = PM_EVENT_FLAG_MISSED; + bp->berp = (void *)erp; + bp->bptr += sizeof(pmEventRecord) - sizeof(pmEventParameter); + return 0; +} + +int +pmdaEventAddHighResMissedRecord(int idx, struct timespec *ts, int missed) +{ + int sts; + bufctl_t *bp; + pmHighResEventArray *hreap; + pmHighResEventRecord *hrerp; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + + if ((sts = check_buf(bp, sizeof(*hrerp) - sizeof(pmEventParameter))) < 0) + return sts; + hreap = (pmHighResEventArray *)bp->baddr; + hreap->ea_nrecords++; + hrerp = (pmHighResEventRecord *)bp->bptr; + hrerp->er_timestamp.tv_sec = (__int32_t)ts->tv_sec; + hrerp->er_timestamp.tv_nsec = (__int32_t)ts->tv_nsec; + hrerp->er_nparams = missed; + hrerp->er_flags = PM_EVENT_FLAG_MISSED; + bp->berp = (void *)hrerp; + bp->bptr += sizeof(pmHighResEventRecord) - sizeof(pmEventParameter); + return 0; +} + +int +add_param(int idx, pmID pmid, int type, pmAtomValue *avp, bufctl_t **bpp) +{ + int sts; + int need; /* bytes in the buffer */ + int vlen; /* value only length */ + void *src; + pmEventParameter *epp; + bufctl_t *bp; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return PM_ERR_NOCONTEXT; + bp = &bufs[idx]; + + need = sizeof(pmEventParameter); + switch (type) { + case PM_TYPE_32: + case PM_TYPE_U32: + vlen = sizeof(avp->l); + need += vlen; + src = &avp->l; + break; + case PM_TYPE_64: + case PM_TYPE_U64: + vlen = sizeof(avp->ll); + need += vlen; + src = &avp->ll; + break; + case PM_TYPE_FLOAT: + vlen = sizeof(avp->f); + need += vlen; + src = &avp->f; + break; + case PM_TYPE_DOUBLE: + vlen = sizeof(avp->d); + need += vlen; + src = &avp->d; + break; + case PM_TYPE_STRING: + vlen = strlen(avp->cp); + need += PM_PDU_SIZE_BYTES(vlen); + src = avp->cp; + break; + case PM_TYPE_AGGREGATE: + /* PM_VAL_HDR_SIZE is added back in below */ + vlen = avp->vbp->vlen - PM_VAL_HDR_SIZE; + need += PM_PDU_SIZE_BYTES(vlen); + src = avp->vbp->vbuf; + break; + default: + return PM_ERR_TYPE; + } + if ((sts = check_buf(bp, need)) < 0) + return sts; + epp = (pmEventParameter *)bp->bptr; + epp->ep_pmid = pmid; + epp->ep_len = PM_VAL_HDR_SIZE + vlen; + epp->ep_type = type; + memcpy((void *)(bp->bptr + sizeof(pmEventParameter)), src, vlen); + bp->bptr += need; + *bpp = bp; + return 0; +} + +int +pmdaEventAddParam(int idx, pmID pmid, int type, pmAtomValue *avp) +{ + int sts; + bufctl_t *bp; + pmEventRecord *erp; + + if ((sts = add_param(idx, pmid, type, avp, &bp)) >= 0) { + erp = (pmEventRecord *)bp->berp; + erp->er_nparams++; + } + return sts; +} + +int +pmdaEventHighResAddParam(int idx, pmID pmid, int type, pmAtomValue *avp) +{ + int sts; + bufctl_t *bp; + pmHighResEventRecord *hrerp; + + if ((sts = add_param(idx, pmid, type, avp, &bp)) >= 0) { + hrerp = (pmHighResEventRecord *)bp->berp; + hrerp->er_nparams++; + } + return sts; +} + +/* + * fill in the vlen/vtype header and return the address of the whole + * structure + */ +pmEventArray * +pmdaEventGetAddr(int idx) +{ + pmEventArray *eap; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return NULL; + + eap = (pmEventArray *)bufs[idx].baddr; + eap->ea_type = PM_TYPE_EVENT; + eap->ea_len = bufs[idx].bptr - bufs[idx].baddr; + return eap; +} + +pmHighResEventArray * +pmdaEventHighResGetAddr(int idx) +{ + pmHighResEventArray *hreap; + + if (idx < 0 || idx >= nbuf || bufs[idx].bstate == B_FREE) + return NULL; + + hreap = (pmHighResEventArray *)bufs[idx].baddr; + hreap->ea_type = PM_TYPE_HIGHRES_EVENT; + hreap->ea_len = bufs[idx].bptr - bufs[idx].baddr; + return hreap; +} diff --git a/src/libpcp_pmda/src/exports b/src/libpcp_pmda/src/exports new file mode 100644 index 0000000..ed8c313 --- /dev/null +++ b/src/libpcp_pmda/src/exports @@ -0,0 +1,115 @@ +PCP_PMDA_3.0 { + global: + pmdaInit; + pmdaMain; + pmdaGetOpt; + pmdaConnect; + pmdaOpenLog; + pmdaOpenHelp; + pmdaSetFlags; + pmdaGetContext; + pmdaGetInDomHelp; + + pmdaDaemon; + pmdaDSO; + + __pmdaInFd; + __pmdaMainPDU; + __pmdaSetContext; + + pmdaPMID; + pmdaRehash; + + pmdaSetCheckCallBack; + pmdaSetDoneCallBack; + pmdaSetEndContextCallBack; + pmdaSetFetchCallBack; + pmdaSetResultCallBack; + + pmdaAttribute; + pmdaDesc; + pmdaFetch; + pmdaInstance; + pmdaProfile; + pmdaStore; + pmdaText; + pmdaName; + pmdaChildren; + + pmdaCacheLookup; + pmdaCacheLookupKey; + pmdaCacheLookupName; + pmdaCacheOp; + pmdaCachePurge; + pmdaCacheStore; + pmdaCacheStoreKey; + + __pmdaCacheDump; + __pmdaCacheDumpAll; + __pmdaStartInst; + __pmdaNextInst; + __pmdaCntInst; + + pmdaCloseHelp; + pmdaGetHelp; + __pmdaHelpTab; + + pmdaTreeChildren; + pmdaTreeName; + pmdaTreePMID; + pmdaTreeRebuildHash; + pmdaTreeSize; + + pmdaEventAddMissedRecord; + pmdaEventAddParam; + pmdaEventAddRecord; + pmdaEventClients; + pmdaEventEndClient; + pmdaEventGetAddr; + pmdaEventNewActiveQueue; + pmdaEventNewArray; + pmdaEventNewClient; + pmdaEventNewQueue; + pmdaEventQueueAppend; + pmdaEventQueueBytes; + pmdaEventQueueClients; + pmdaEventQueueCounter; + pmdaEventQueueHandle; + pmdaEventQueueMemory; + pmdaEventQueueRecords; + pmdaEventReleaseArray; + pmdaEventResetArray; + pmdaEventSetAccess; + pmdaEventSetFilter; + __pmdaEventPrint; + + pmdaDynamicLookupName; + pmdaDynamicLookupPMID; + pmdaDynamicLookupText; + pmdaDynamicMetricTable; + pmdaDynamicPMNS; + + local: *; +}; + +PCP_PMDA_3.1 { + global: + pmdaGetOptions; + pmdaUsageMessage; +} PCP_PMDA_3.0; + +PCP_PMDA_3.2 { + global: + pmdaDynamicSetClusterMask; +} PCP_PMDA_3.1; + +PCP_PMDA_3.3 { + global: + pmdaEventNewHighResArray; + pmdaEventResetHighResArray; + pmdaEventReleaseHighResArray; + pmdaEventAddHighResRecord; + pmdaEventAddHighResMissedRecord; + pmdaEventHighResAddParam; + pmdaEventHighResGetAddr; +} PCP_PMDA_3.2; diff --git a/src/libpcp_pmda/src/help.c b/src/libpcp_pmda/src/help.c new file mode 100644 index 0000000..d9d17b8 --- /dev/null +++ b/src/libpcp_pmda/src/help.c @@ -0,0 +1,217 @@ +/* + * Copyright (c) 1995-2003 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +/* + * Get help text from files built using newhelp + */ +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include <sys/stat.h> + +typedef struct { /* beware: this data structure mirrored in chkhelp */ + pmID pmid; + __uint32_t off_oneline; + __uint32_t off_text; +} help_idx_t; + +typedef struct { /* beware: this data structure mirrored in chkhelp */ + int dir_fd; + int pag_fd; + int numidx; + help_idx_t *index; + char *text; + int textlen; +} help_t; + +static help_t *tab = NULL; +static int numhelp = 0; + +/* + * open the help text files and return a handle on success + */ +int +pmdaOpenHelp(char *fname) +{ + char pathname[MAXPATHLEN]; + int sts, size; + help_idx_t hdr; + help_t *hp; + struct stat sbuf; + + for (sts = 0; sts < numhelp; sts++) { + if (tab[sts].dir_fd == -1) + break; + } + if (sts == numhelp) { + sts = numhelp++; + tab = (help_t *)realloc(tab, numhelp * sizeof(tab[0])); + if (tab == NULL) { + __pmNoMem("pmdaOpenHelp", numhelp * sizeof(tab[0]), PM_RECOV_ERR); + numhelp = 0; + return -oserror(); + } + } + hp = &tab[sts]; + memset(hp, 0, sizeof(*hp)); + hp->dir_fd = -1; + hp->pag_fd = -1; + + snprintf(pathname, sizeof(pathname), "%s.dir", fname); + hp->dir_fd = open(pathname, O_RDONLY); + if (hp->dir_fd < 0) { + sts = -oserror(); + goto failed; + } + + if (read(hp->dir_fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + sts = -EINVAL; + goto failed; + } + + if (hdr.pmid != 0x50635068 || + (hdr.off_oneline & 0xffff0000) != 0x31320000) { + sts = -EINVAL; + goto failed; + } + + hp->numidx = hdr.off_text; + size = (hp->numidx + 1) * sizeof(help_idx_t); + + hp->index = (help_idx_t *)__pmMemoryMap(hp->dir_fd, size, 0); + if (hp->index == NULL) { + sts = -oserror(); + goto failed; + } + + snprintf(pathname, sizeof(pathname), "%s.pag", fname); + hp->pag_fd = open(pathname, O_RDONLY); + if (hp->pag_fd < 0) { + sts = -oserror(); + goto failed; + } + if (fstat(hp->pag_fd, &sbuf) < 0) { + sts = -oserror(); + goto failed; + } + hp->textlen = (int)sbuf.st_size; + hp->text = (char *)__pmMemoryMap(hp->pag_fd, hp->textlen, 0); + if (hp->text == NULL) { + sts = -oserror(); + goto failed; + } + return numhelp - 1; + +failed: + pmdaCloseHelp(numhelp-1); + return sts; +} + +/* + * retrieve pmID help text, ... + * + */ +char * +pmdaGetHelp(int handle, pmID pmid, int type) +{ + int i; + help_t *hp; + + if (handle < 0 || handle >= numhelp) + return NULL; + hp = &tab[handle]; + + /* search forwards -- could use binary chop */ + for (i = 1; i <= hp->numidx; i++) { + if (hp->index[i].pmid == pmid) { + if (type & PM_TEXT_ONELINE) + return &hp->text[hp->index[i].off_oneline]; + else + return &hp->text[hp->index[i].off_text]; + } + } + + return NULL; +} + +/* + * retrieve pmInDom help text, ... + * + */ +char * +pmdaGetInDomHelp(int handle, pmInDom indom, int type) +{ + int i; + help_t *hp; + pmID pmid; + __pmID_int *pip = (__pmID_int *)&pmid; + + if (handle < 0 || handle >= numhelp) + return NULL; + hp = &tab[handle]; + + *pip = *((__pmID_int *)&indom); + /* + * set a bit here to disambiguate pmInDom from pmID + * -- this "hack" is shared between here and newhelp/newhelp.c + */ + pip->flag = 1; + + /* search backwards ... pmInDom entries are at the end */ + for (i = hp->numidx; i >= 1; i--) { + if (hp->index[i].pmid == pmid) { + if (type & PM_TEXT_ONELINE) + return &hp->text[hp->index[i].off_oneline]; + else + return &hp->text[hp->index[i].off_text]; + } + } + + return NULL; +} + +/* + * this is only here for chkhelp(1) ... export the control data strcuture + */ +void * +__pmdaHelpTab(void) +{ + return (void *)tab; +} + +void +pmdaCloseHelp(int handle) +{ + help_t *hp; + + if (handle < 0 || handle >= numhelp) + return; + hp = &tab[handle]; + + if (hp->dir_fd != -1) + close(hp->dir_fd); + if (hp->pag_fd != -1) + close(hp->pag_fd); + if (hp->index != NULL) + __pmMemoryUnmap((void *)hp->index, (hp->numidx+1) * sizeof(help_idx_t)); + if (hp->text != NULL) + __pmMemoryUnmap(hp->text, hp->textlen); + + hp->textlen = 0; + hp->dir_fd = -1; + hp->pag_fd = -1; + hp->numidx = 0; + hp->index = NULL; + hp->text = NULL; +} diff --git a/src/libpcp_pmda/src/libdefs.h b/src/libpcp_pmda/src/libdefs.h new file mode 100644 index 0000000..1d8b1c6 --- /dev/null +++ b/src/libpcp_pmda/src/libdefs.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1999-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ +#ifndef LIBDEFS_H +#define LIBDEFS_H + +#define HAVE_V_TWO(interface) ((interface) >= PMDA_INTERFACE_2) +#define HAVE_V_FOUR(interface) ((interface) >= PMDA_INTERFACE_4) +#define HAVE_V_FIVE(interface) ((interface) >= PMDA_INTERFACE_5) +#define HAVE_V_SIX(interface) ((interface) >= PMDA_INTERFACE_6) +#define HAVE_ANY(interface) ((interface) <= PMDA_INTERFACE_6 && HAVE_V_TWO(interface)) + +/* + * Auxilliary structure used to save data from pmdaDSO or pmdaDaemon and + * make it available to the other methods, also as private per PMDA data + * when multiple DSO PMDAs are in use + */ +typedef struct { + int pmda_interface; + pmResult *res; /* high-water allocation for */ + int maxnpmids; /* pmResult for each PMDA */ + __pmHashCtl hashpmids; /* hashed metrictab lookups */ +} e_ext_t; + +#endif /* LIBDEFS_H */ diff --git a/src/libpcp_pmda/src/mainloop.c b/src/libpcp_pmda/src/mainloop.c new file mode 100644 index 0000000..7b6b776 --- /dev/null +++ b/src/libpcp_pmda/src/mainloop.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2013 Red Hat. + * Copyright (c) 1995-2000 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "libdefs.h" + +int +__pmdaInFd(pmdaInterface *dispatch) +{ + if (HAVE_ANY(dispatch->comm.pmda_interface)) + return dispatch->version.any.ext->e_infd; + + __pmNotifyErr(LOG_CRIT, "PMDA interface version %d not supported", + dispatch->comm.pmda_interface); + return -1; +} + +int +__pmdaMainPDU(pmdaInterface *dispatch) +{ + __pmPDU *pb; + int sts; + int op_sts; + pmID pmid; + pmDesc desc; + int npmids; + pmID *pmidlist; + char **namelist = NULL; + char *name; + char **offspring = NULL; + int *statuslist = NULL; + int subtype; + pmResult *result; + int ctxnum; + int length; + __pmTimeval when; + int ident; + int type; + pmInDom indom; + int inst; + char *iname; + __pmInResult *inres; + char *buffer; + __pmProfile *new_profile; + static __pmProfile *profile = NULL; + static int first_time = 1; + static pmdaExt *pmda = NULL; + int pinpdu; + + /* Initial version checks */ + if (first_time) { + if (dispatch->status != 0) { + __pmNotifyErr(LOG_ERR, "PMDA Initialisation Failed"); + return -1; + } + if (!HAVE_ANY(dispatch->comm.pmda_interface)) { + __pmNotifyErr(LOG_CRIT, "PMDA interface version %d not supported", + dispatch->comm.pmda_interface); + return -1; + } + pmda = dispatch->version.any.ext; + dispatch->comm.pmapi_version = PMAPI_VERSION; + first_time = 0; + } + + pinpdu = sts = __pmGetPDU(pmda->e_infd, ANY_SIZE, TIMEOUT_NEVER, &pb); + if (sts == 0) + return PM_ERR_EOF; + if (sts < 0) { + __pmNotifyErr(LOG_ERR, "IPC Error: %s\n", pmErrStr(sts)); + return sts; + } + + if (HAVE_V_FIVE(dispatch->comm.pmda_interface)) { + /* set up sender context */ + __pmPDUHdr *php = (__pmPDUHdr *)pb; + /* ntohl() converted already in __pmGetPDU() */ + dispatch->version.four.ext->e_context = php->from; + } + + /* + * if defined, callback once per PDU to check availability, etc. + */ + if (pmda->e_checkCallBack) { + op_sts = (*(pmda->e_checkCallBack))(); + if (op_sts < 0) { + if (sts != PDU_PROFILE) + /* all other PDUs expect an ACK */ + __pmSendError(pmda->e_outfd, FROM_ANON, op_sts); + __pmUnpinPDUBuf(pb); + return 0; + } + } + + switch (sts) { + case PDU_ERROR: + /* + * If __pmDecodeError() fails, just ignore it as no response PDU + * is required nor expected. + * Expect PM_ERR_NOTCONN to mark client context being closed. + */ + if (__pmDecodeError(pb, &op_sts) >= 0) { + if (op_sts == PM_ERR_NOTCONN) { + if (HAVE_V_FIVE(dispatch->comm.pmda_interface)) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_CONTEXT) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_ERROR (end context %d)\n", dispatch->version.four.ext->e_context); + } +#endif + if (pmda->e_endCallBack != NULL) { + (*(pmda->e_endCallBack))(dispatch->version.four.ext->e_context); + } + } + } + else { + __pmNotifyErr(LOG_ERR, + "%s: unexpected error pdu from pmcd: %s?\n", + pmda->e_name, pmErrStr(op_sts)); + } + } + break; + + case PDU_PROFILE: + /* + * can ignore ctxnum, since pmcd has already used this to send + * the correct profile, if required + */ + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_PROFILE\n"); + } +#endif + + /* + * free last profile received (if any) + * Note error responses are not sent for PDU_PROFILE + */ + if (__pmDecodeProfile(pb, &ctxnum, &new_profile) < 0) + break; + + sts = dispatch->version.any.profile(new_profile, pmda); + if (sts < 0) { + __pmFreeProfile(new_profile); + } else { + __pmFreeProfile(profile); + profile = new_profile; + } + break; + + case PDU_FETCH: + /* + * can ignore ctxnum, since pmcd has already used this to send + * the correct profile, if required + */ + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_FETCH\n"); + } +#endif + + sts = __pmDecodeFetch(pb, &ctxnum, &when, &npmids, &pmidlist); + if (sts >= 0) { + sts = dispatch->version.any.fetch(npmids, pmidlist, &result, pmda); + __pmUnpinPDUBuf(pmidlist); + } + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else { + /* this is for PURIFY to prevent a UMR in __pmXmitPDU */ + result->timestamp.tv_sec = 0; + result->timestamp.tv_usec = 0; + __pmSendResult(pmda->e_outfd, FROM_ANON, result); + (pmda->e_resultCallBack)(result); + } + break; + + case PDU_PMNS_NAMES: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_NAMES\n"); + } +#endif + + if ((sts = __pmDecodeNameList(pb, &npmids, &namelist, NULL)) >= 0) { + if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) { + if (npmids != 1) + /* + * expect only one name at a time to be sent to the + * pmda from pmcd + */ + sts = PM_ERR_IPC; + else + sts = dispatch->version.four.pmid(namelist[0], &pmid, pmda); + } + else { + /* Not INTERFACE_4 or later */ + sts = PM_ERR_NAME; + } + __pmUnpinPDUBuf(namelist); + } + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else + __pmSendIDList(pmda->e_outfd, FROM_ANON, 1, &pmid, sts); + break; + + case PDU_PMNS_CHILD: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_CHILD\n"); + } +#endif + + if ((sts = __pmDecodeChildReq(pb, &name, &subtype)) >= 0) { + if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) { + sts = dispatch->version.four.children(name, 0, &offspring, &statuslist, pmda); + if (sts >= 0) { + if (subtype == 0) { + if (statuslist) free(statuslist); + statuslist = NULL; + } + } + } + else { + /* Not INTERFACE_4 */ + sts = PM_ERR_NAME; + } + } + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else + __pmSendNameList(pmda->e_outfd, FROM_ANON, sts, offspring, statuslist); + if (offspring) free(offspring); + if (statuslist) free(statuslist); + break; + + case PDU_PMNS_TRAVERSE: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_TRAVERSE\n"); + } +#endif + + if ((sts = __pmDecodeTraversePMNSReq(pb, &name)) >= 0) { + if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) { + sts = dispatch->version.four.children(name, 1, &offspring, &statuslist, pmda); + if (sts >= 0) { + if (statuslist) free(statuslist); + statuslist = NULL; + } + } + else { + /* Not INTERFACE_4 */ + sts = PM_ERR_NAME; + } + free(name); + } + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else + __pmSendNameList(pmda->e_outfd, FROM_ANON, sts, offspring, NULL); + if (offspring) free(offspring); + break; + + case PDU_PMNS_IDS: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_PMNS_IDS\n"); + } +#endif + + sts = __pmDecodeIDList(pb, 1, &pmid, &op_sts); + if (sts >= 0) + sts = op_sts; + if (sts >= 0) { + if (HAVE_V_FOUR(dispatch->comm.pmda_interface)) { + sts = dispatch->version.four.name(pmid, &namelist, pmda); + } + else { + /* Not INTERFACE_4 */ + sts = PM_ERR_PMID; + } + } + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else + __pmSendNameList(pmda->e_outfd, FROM_ANON, sts, namelist, NULL); + if (namelist) free(namelist); + break; + + case PDU_DESC_REQ: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_DESC_REQ\n"); + } +#endif + + if ((sts = __pmDecodeDescReq(pb, &pmid)) >= 0) + sts = dispatch->version.any.desc(pmid, &desc, pmda); + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else + __pmSendDesc(pmda->e_outfd, FROM_ANON, &desc); + break; + + case PDU_INSTANCE_REQ: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_INSTANCE_REQ\n"); + } +#endif + + if ((sts = __pmDecodeInstanceReq(pb, &when, &indom, &inst, &iname)) >= 0) + sts = dispatch->version.any.instance(indom, inst, iname, &inres, pmda); + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else { + __pmSendInstance(pmda->e_outfd, FROM_ANON, inres); + __pmFreeInResult(inres); + } + if (iname) + free(iname); + break; + + case PDU_TEXT_REQ: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_TEXT_REQ\n"); + } +#endif + + if ((sts = __pmDecodeTextReq(pb, &ident, &type)) >= 0) + sts = dispatch->version.any.text(ident, type, &buffer, pmda); + if (sts < 0) + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + else { + __pmSendText(pmda->e_outfd, FROM_ANON, ident, buffer); + } + break; + + case PDU_RESULT: + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_RESULT\n"); + } +#endif + + if ((sts = __pmDecodeResult(pb, &result)) >= 0) + sts = dispatch->version.any.store(result, pmda); + __pmSendError(pmda->e_outfd, FROM_ANON, sts); + pmFreeResult(result); + break; + + case PDU_CONTROL_REQ: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "Received PDU_CONTROL_REQ\n"); + } +#endif + break; + + case PDU_AUTH: +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_DEBUG, "Received PDU_AUTH\n"); +#endif + if (__pmDecodeAuth(pb, &subtype, &buffer, &length) < 0) + break; + if (HAVE_V_SIX(dispatch->comm.pmda_interface)) { + ctxnum = dispatch->version.six.ext->e_context; + sts = dispatch->version.six.attribute(ctxnum, subtype, buffer, length, pmda); + } else { + sts = PM_ERR_GENERIC; + } + break; + + default: { + char strbuf[20]; + __pmNotifyErr(LOG_ERR, + "%s: Unrecognised pdu type: %s?\n", + pmda->e_name, __pmPDUTypeStr_r(sts, strbuf, sizeof(strbuf))); + } + break; + } + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + /* + * if defined, callback once per PDU to do termination checks, + * stats, etc + */ + if (pmda->e_doneCallBack) + (*(pmda->e_doneCallBack))(); + + return 0; +} + + +void +pmdaMain(pmdaInterface *dispatch) +{ + for ( ; ; ) { + if (__pmdaMainPDU(dispatch) < 0) + break; + } +} + +void +pmdaSetResultCallBack(pmdaInterface *dispatch, pmdaResultCallBack callback) +{ + if (HAVE_ANY(dispatch->comm.pmda_interface)) + dispatch->version.any.ext->e_resultCallBack = callback; + else { + __pmNotifyErr(LOG_CRIT, "Unable to set result callback for PMDA interface version %d.", + dispatch->comm.pmda_interface); + dispatch->status = PM_ERR_GENERIC; + } +} + +void +pmdaSetEndContextCallBack(pmdaInterface *dispatch, pmdaEndContextCallBack callback) +{ + if (HAVE_V_FIVE(dispatch->comm.pmda_interface) || callback == NULL) + dispatch->version.four.ext->e_endCallBack = callback; + else { + __pmNotifyErr(LOG_CRIT, "Unable to set end context callback for PMDA interface version %d.", + dispatch->comm.pmda_interface); + dispatch->status = PM_ERR_GENERIC; + } +} + +void +pmdaSetFetchCallBack(pmdaInterface *dispatch, pmdaFetchCallBack callback) +{ + if (HAVE_ANY(dispatch->comm.pmda_interface)) + dispatch->version.any.ext->e_fetchCallBack = callback; + else { + __pmNotifyErr(LOG_CRIT, "Unable to set fetch callback for PMDA interface version %d.", + dispatch->comm.pmda_interface); + dispatch->status = PM_ERR_GENERIC; + } +} + +void +pmdaSetCheckCallBack(pmdaInterface *dispatch, pmdaCheckCallBack callback) +{ + if (HAVE_ANY(dispatch->comm.pmda_interface)) + dispatch->version.any.ext->e_checkCallBack = callback; + else { + __pmNotifyErr(LOG_CRIT, "Unable to set check callback for PMDA interface version %d.", + dispatch->comm.pmda_interface); + dispatch->status = PM_ERR_GENERIC; + } +} + +void +pmdaSetDoneCallBack(pmdaInterface *dispatch, pmdaDoneCallBack callback) +{ + if (HAVE_ANY(dispatch->comm.pmda_interface)) + dispatch->version.any.ext->e_doneCallBack = callback; + else { + __pmNotifyErr(LOG_CRIT, "Unable to set done callback for PMDA interface version %d.", + dispatch->comm.pmda_interface); + dispatch->status = PM_ERR_GENERIC; + } +} diff --git a/src/libpcp_pmda/src/open.c b/src/libpcp_pmda/src/open.c new file mode 100644 index 0000000..3150c20 --- /dev/null +++ b/src/libpcp_pmda/src/open.c @@ -0,0 +1,987 @@ +/* + * Copyright (c) 2012-2014 Red Hat. + * Copyright (c) 1995-2000,2003,2004 Silicon Graphics, Inc. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" +#include "libdefs.h" +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_TCP_H +#include <netinet/tcp.h> +#endif + +/* + * Open an inet port to PMCD + */ + +static void +__pmdaOpenSocket(char *sockname, int port, int family, int *infd, int *outfd) +{ + int sts; + int sfd; + struct servent *service; + __pmSockAddr *myaddr; + __pmSockLen addrlen; + __pmFdSet rfds; + int one = 1; + + if (sockname != NULL) { /* Translate port name to port num */ + service = getservbyname(sockname, NULL); + if (service == NULL) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: getservbyname(%s): %s\n", + sockname, netstrerror()); + exit(1); + } + port = service->s_port; + } + + if (family != AF_INET6) { + sfd = __pmCreateSocket(); + if (sfd < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: inet socket: %s\n", + netstrerror()); + exit(1); + } + } + else { + sfd = __pmCreateIPv6Socket(); + if (sfd < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: ipv6 socket: %s\n", + netstrerror()); + exit(1); + } + } +#ifndef IS_MINGW + /* + * allow port to be quickly re-used, e.g. when Install and PMDA already + * installed, this becomes terminate and restart in a hurry ... + */ + if (__pmSetSockOpt(sfd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, + (__pmSockLen)sizeof(one)) < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: __pmSetSockOpt(reuseaddr): %s\n", + netstrerror()); + exit(1); + } +#else + /* see MSDN tech note: "Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE" */ + if (__pmSetSockOpt(sfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&one, + (__pmSockLen)sizeof(one)) < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: __pmSetSockOpt(excladdruse): %s\n", + netstrerror()); + exit(1); + } +#endif + + if ((myaddr =__pmSockAddrAlloc()) == NULL) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: sock addr alloc failed\n"); + exit(1); + } + __pmSockAddrInit(myaddr, family, INADDR_LOOPBACK, port); + sts = __pmBind(sfd, (void *)myaddr, __pmSockAddrSize()); + if (sts < 0) { + __pmSockAddrFree(myaddr); + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: bind: %s\n", + netstrerror()); + exit(1); + } + + sts = __pmListen(sfd, 1); /* Max. 1 pending connection request (pmcd) */ + if (sts == -1) { + __pmSockAddrFree(myaddr); + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: listen: %s\n", + netstrerror()); + exit(1); + } + + /* block here indefinitely, waiting for a connection */ + __pmFD_ZERO(&rfds); + __pmFD_SET(sfd, &rfds); + if ((sts = __pmSelectRead(sfd+1, &rfds, NULL)) != 1) { + sts = (sts < 0) ? -neterror() : -EINVAL; + } + if (sts < 0) { + __pmSockAddrFree(myaddr); + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: select: %s\n", + pmErrStr(sts)); + exit(1); + } + + addrlen = __pmSockAddrSize(); + if ((*infd = __pmAccept(sfd, myaddr, &addrlen)) < 0) { + __pmSockAddrFree(myaddr); + __pmNotifyErr(LOG_CRIT, "__pmdaOpenSocket: accept: %s\n", + netstrerror()); + exit(1); + } + __pmCloseSocket(sfd); + __pmSetSocketIPC(*infd); + __pmSockAddrFree(myaddr); + + *outfd = *infd; +} + +static void +__pmdaOpenInet(char *sockname, int port, int *infd, int *outfd) +{ + __pmdaOpenSocket(sockname, port, AF_INET, infd, outfd); +} + +static void +__pmdaOpenIPv6(char *sockname, int port, int *infd, int *outfd) +{ + __pmdaOpenSocket(sockname, port, AF_INET6, infd, outfd); +} + +#ifdef HAVE_STRUCT_SOCKADDR_UN +/* + * Open a unix port to PMCD + */ +static void +__pmdaOpenUnix(char *sockname, int *infd, int *outfd) +{ + int sts; + int sfd; + int len; + __pmSockLen addrlen; + struct sockaddr_un myaddr; + struct sockaddr_un from; + + sfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sfd < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: Unix domain socket: %s", + netstrerror()); + exit(1); + } + /* Sockets in the Unix domain are named pipes in the file system. + * In case the socket is still hanging around, try to remove it. If it + * belonged to someone and is still open, they will still keep the + * connection because the reference count is non-zero. + */ + if ((sts = unlink(sockname)) == 0) + __pmNotifyErr(LOG_WARNING, "__pmdaOpenUnix: Unix domain socket '%s' existed, unlinked it\n", + sockname); + else if (sts < 0 && oserror() != ENOENT) { + /* If can't unlink socket, give up. We might end up with an + * unwanted connection to some other socket (from outer space) + */ + __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: Unlinking Unix domain socket '%s': %s\n", + sockname, osstrerror()); + exit(1); + } + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sun_family = AF_UNIX; + strcpy(myaddr.sun_path, sockname); + len = (int)offsetof(struct sockaddr_un, sun_path) + (int)strlen(myaddr.sun_path); + sts = bind(sfd, (struct sockaddr*) &myaddr, len); + if (sts < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: unix bind: %s\n", + netstrerror()); + exit(1); + } + + sts = listen(sfd, 5); /* Max. of 5 pending connection requests */ + if (sts == -1) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: unix listen: %s\n", + netstrerror()); + exit(1); + } + addrlen = sizeof(from); + /* block here, waiting for a connection */ + if ((*infd = accept(sfd, (struct sockaddr *)&from, &addrlen)) < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: unix accept: %s\n", + netstrerror()); + exit(1); + } + close(sfd); + *outfd = *infd; +} +#else +static void +__pmdaOpenUnix(char *sockname, int *infd, int *outfd) +{ + __pmNotifyErr(LOG_CRIT, "__pmdaOpenUnix: UNIX domain sockets unsupported\n"); + exit(1); +} +#endif + +/* + * Capture PMDA args using pmgetopts_r + */ + +static char +pmdaIoTypeToOption(pmdaIoType io) +{ + switch(io) { + case pmdaPipe: + return 'p'; + case pmdaInet: + return 'i'; + case pmdaIPv6: + return '6'; + case pmdaUnix: + return 'u'; + case pmdaUnknown: + default: + break; + } + return '?'; +} + +/* + * Backwards compatibility interface, short option support only. + * We override username (-U) to preserve backward-compatibility + * with the original pmdaGetOpt interface, which does not know + * about that option (and uses of -U have been observed in the + * wild). Happily, pmdaGetOptions gives us a much more flexible + * route forward. + */ + +static int +username_override(int opt, pmdaOptions *opts) +{ + (void)opts; + return opt == 'U'; +} + +int +pmdaGetOpt(int argc, char *const *argv, const char *optstring, pmdaInterface *dispatch, int *err) +{ + int sts; + static pmdaOptions opts; + + opts.flags |= PM_OPTFLAG_POSIX; + opts.short_options = optstring; + opts.override = username_override; + opts.errors = 0; + + sts = pmdaGetOptions(argc, argv, &opts, dispatch); + + optind = opts.optind; + opterr = opts.opterr; + optopt = opts.optopt; + optarg = opts.optarg; + *err += opts.errors; + return sts; +} + +/* + * New, prefered interface - supports long and short options, allows + * caller to select whether POSIX style options are required. Also, + * handles the common -U,--username option setting automatically. + */ +int +pmdaGetOptions(int argc, char *const *argv, pmdaOptions *opts, pmdaInterface *dispatch) +{ + int c = EOF; + int flag = 0; + char *endnum = NULL; + pmdaExt *pmda = NULL; + + if (dispatch->status != 0) { + opts->errors++; + return EOF; + } + + if (!HAVE_ANY(dispatch->comm.pmda_interface)) { + __pmNotifyErr(LOG_CRIT, "pmdaGetOptions: " + "PMDA interface version %d not supported (domain=%d)", + dispatch->comm.pmda_interface, dispatch->domain); + opts->errors++; + return EOF; + } + + pmda = dispatch->version.any.ext; + + /* + * We only support one version of pmdaOptions structure so far - tell + * the caller the struct size/fields we will be using (version zero), + * in case they are of later version (newer PMDA, older library). + * + * The pmOptions and pmdaOptions structures share initial pmgetopts_r + * fields, hence the cast below (and sharing of pmgetopt_r itself). + * + * So far, the only PMDA-specific field is from --username although, + * of course, more may be added in the future (bumping version as we + * go of course, and observing the version number the PMDA passes in). + */ + opts->version = 0; + + while (!flag && ((c = pmgetopt_r(argc, argv, (pmOptions *)opts)) != EOF)) { + int sts; + + /* provide opportunity for overriding the general set of options */ + if (opts->override && opts->override(c, opts)) + break; + + switch (c) { + case 'd': + dispatch->domain = (int)strtol(opts->optarg, &endnum, 10); + if (*endnum != '\0') { + pmprintf("%s: -d requires numeric domain number\n", + pmda->e_name); + opts->errors++; + } + pmda->e_domain = dispatch->domain; + break; + + case 'D': + if ((sts = __pmParseDebug(opts->optarg)) < 0) { + pmprintf("%s: unrecognized debug flag specification (%s)\n", + pmda->e_name, opts->optarg); + opts->errors++; + } else { + pmDebug |= sts; + } + break; + + case 'h': /* over-ride default help file */ + pmda->e_helptext = opts->optarg; + break; + + case 'i': + if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaInet) { + pmprintf("%s: -i option clashes with -%c option\n", + pmda->e_name, pmdaIoTypeToOption(pmda->e_io)); + opts->errors++; + } else { + pmda->e_io = pmdaInet; + pmda->e_port = (int)strtol(opts->optarg, &endnum, 10); + if (*endnum != '\0') + pmda->e_sockname = opts->optarg; + } + break; + + case 'l': /* over-ride default log file */ + pmda->e_logfile = opts->optarg; + break; + + case 'p': + if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaPipe) { + pmprintf("%s: -p option clashes with -%c option\n", + pmda->e_name, pmdaIoTypeToOption(pmda->e_io)); + opts->errors++; + } else { + pmda->e_io = pmdaPipe; + } + break; + + case 'u': + if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaUnix) { + pmprintf("%s: -u option clashes with -%c option\n", + pmda->e_name, pmdaIoTypeToOption(pmda->e_io)); + opts->errors++; + } else { + pmda->e_io = pmdaUnix; + pmda->e_sockname = opts->optarg; + } + break; + + case '6': + if (pmda->e_io != pmdaUnknown && pmda->e_io != pmdaIPv6) { + pmprintf("%s: -6 option clashes with -%c option\n", + pmda->e_name, pmdaIoTypeToOption(pmda->e_io)); + opts->errors++; + } else { + pmda->e_io = pmdaIPv6; + pmda->e_port = (int)strtol(opts->optarg, &endnum, 10); + if (*endnum != '\0') + pmda->e_sockname = opts->optarg; + } + break; + + case 'U': + opts->username = opts->optarg; + break; + + case '?': + opts->errors++; + break; + + default: + flag = 1; + } + } + + return c; +} + +void +pmdaUsageMessage(pmdaOptions *opts) +{ + pmUsageMessage((pmOptions *)opts); +} + +static __pmHashWalkState +pmdaHashNodeDelete(const __pmHashNode *tp, void *cp) +{ + (void)tp; + (void)cp; + return PM_HASH_WALK_DELETE_NEXT; +} + +static void +pmdaHashDelete(__pmHashCtl *hashp) +{ + __pmHashWalkCB(pmdaHashNodeDelete, NULL, hashp); + __pmHashClear(hashp); +} + +/* + * Recompute the hash table which maps metric PMIDs to metric table + * offsets. Provides an optimised lookup alternative when a direct + * mapping is inappropriate or impossible. + */ +void +pmdaRehash(pmdaExt *pmda, pmdaMetric *metrics, int nmetrics) +{ + e_ext_t *extp = (e_ext_t *)pmda->e_ext; + __pmHashCtl *hashp = &extp->hashpmids; + pmdaMetric *metric; + char buf[32]; + int m; + + pmda->e_direct = 0; + pmda->e_metrics = metrics; + pmda->e_nmetrics = nmetrics; + + pmdaHashDelete(hashp); + for (m = 0; m < pmda->e_nmetrics; m++) { + metric = &pmda->e_metrics[m]; + + if (__pmHashAdd(metric->m_desc.pmid, metric, hashp) < 0) { + __pmNotifyErr(LOG_WARNING, "pmdaRehash: PMDA %s: " + "Hashed mapping for metrics disabled @ metric[%d] %s\n", + pmda->e_name, m, + pmIDStr_r(metric->m_desc.pmid, buf, sizeof(buf))); + break; + } + } + if (m == pmda->e_nmetrics) + pmda->e_flags |= PMDA_EXT_FLAG_HASHED; + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_DEBUG, "pmdaRehash: PMDA %s: successful rebuild\n", + pmda->e_name); + else { + pmda->e_flags &= ~PMDA_EXT_FLAG_HASHED; + pmdaHashDelete(hashp); + } +} + +static void +pmdaDirect(pmdaExt *pmda, pmdaMetric *metrics, int nmetrics) +{ + __pmID_int *pmidp; + char buf[20]; + int m; + + pmda->e_direct = 1; + for (m = 0; m < pmda->e_nmetrics; m++) { + pmidp = (__pmID_int *)&pmda->e_metrics[m].m_desc.pmid; + + if (pmidp->item == m) + continue; + + pmda->e_direct = 0; + if ((pmda->e_flags & PMDA_EXT_FLAG_DIRECT) || + (pmDebug & DBG_TRACE_LIBPMDA)) + __pmNotifyErr(LOG_WARNING, "pmdaDirect: PMDA %s: " + "Direct mapping for metrics disabled @ metrics[%d] %s\n", + pmda->e_name, m, + pmIDStr_r(pmda->e_metrics[m].m_desc.pmid, buf, sizeof(buf))); + break; + } +} + +void +pmdaSetFlags(pmdaInterface *dispatch, int flags) +{ + pmdaExt *pmda; + + if (!HAVE_ANY(dispatch->comm.pmda_interface)) { + __pmNotifyErr(LOG_CRIT, "pmdaSetFlags: PMDA interface version %d not supported (domain=%d)", + dispatch->comm.pmda_interface, dispatch->domain); + dispatch->status = PM_ERR_GENERIC; + return; + } + pmda = dispatch->version.any.ext; + pmda->e_flags |= flags; +} + +/* + * Open the help text file, check for direct mapping into the metric table + * and whether a hash mapping has been requested. + */ + +void +pmdaInit(pmdaInterface *dispatch, pmdaIndom *indoms, int nindoms, + pmdaMetric *metrics, int nmetrics) +{ + int m = 0; + int i = 0; + __pmInDom_int *indomp = NULL; + __pmInDom_int *mindomp = NULL; + __pmID_int *pmidp = NULL; + pmdaExt *pmda = NULL; + + if (!HAVE_ANY(dispatch->comm.pmda_interface)) { + __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA interface version %d not supported (domain=%d)", + dispatch->comm.pmda_interface, dispatch->domain); + dispatch->status = PM_ERR_GENERIC; + return; + } + pmda = dispatch->version.any.ext; + + if (dispatch->version.any.fetch == pmdaFetch && + pmda->e_fetchCallBack == (pmdaFetchCallBack)0) { + __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: using pmdaFetch() but fetch call back not set", pmda->e_name); + dispatch->status = PM_ERR_GENERIC; + return; + } + + /* parameter sanity checks */ + if (nmetrics < 0) { + __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: nmetrics (%d) should be non-negative", pmda->e_name, nmetrics); + dispatch->status = PM_ERR_GENERIC; + return; + } + if (nindoms < 0) { + __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: nindoms (%d) should be non-negative", pmda->e_name, nindoms); + dispatch->status = PM_ERR_GENERIC; + return; + } + if ((nmetrics == 0 && metrics != NULL) || + (nmetrics != 0 && metrics == NULL)){ + __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: metrics not consistent with nmetrics", pmda->e_name); + dispatch->status = PM_ERR_GENERIC; + return; + } + if ((nindoms == 0 && indoms != NULL) || + (nindoms != 0 && indoms == NULL)){ + __pmNotifyErr(LOG_CRIT, "pmdaInit: PMDA %s: indoms not consistent with nindoms", pmda->e_name); + dispatch->status = PM_ERR_GENERIC; + return; + } + + pmda->e_indoms = indoms; + pmda->e_nindoms = nindoms; + pmda->e_metrics = metrics; + pmda->e_nmetrics = nmetrics; + + /* fix bit fields in indom for all instance domains */ + for (i = 0; i < pmda->e_nindoms; i++) { + unsigned int domain = dispatch->domain; + unsigned int serial = pmda->e_indoms[i].it_indom; + pmda->e_indoms[i].it_indom = pmInDom_build(domain, serial); + } + + /* fix bit fields in indom for all metrics */ + for (i = 0; i < pmda->e_nmetrics; i++) { + if (pmda->e_metrics[i].m_desc.indom != PM_INDOM_NULL) { + unsigned int domain = dispatch->domain; + unsigned int serial = pmda->e_metrics[i].m_desc.indom; + pmda->e_metrics[i].m_desc.indom = pmInDom_build(domain, serial); + } + } + + /* + * For each metric, check the instance domain serial number is valid + */ + for (m = 0; m < pmda->e_nmetrics; m++) { + if (pmda->e_metrics[m].m_desc.indom != PM_INDOM_NULL) { + mindomp = (__pmInDom_int *)&(pmda->e_metrics[m].m_desc.indom); + if (pmda->e_nindoms > 0) { + for (i = 0; i < pmda->e_nindoms; i++) { + indomp = (__pmInDom_int *)&(pmda->e_indoms[i].it_indom); + if (indomp->serial == mindomp->serial) { +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + char strbuf[20]; + char st2buf[20]; + __pmNotifyErr(LOG_DEBUG, + "pmdaInit: PMDA %s: Metric %s(%d) matched to indom %s(%d)\n", + pmda->e_name, + pmIDStr_r(pmda->e_metrics[m].m_desc.pmid, strbuf, sizeof(strbuf)), m, + pmInDomStr_r(pmda->e_indoms[i].it_indom, st2buf, sizeof(st2buf)), i); + } +#endif + break; + } + } + if (i == pmda->e_nindoms) { + char strbuf[20]; + __pmNotifyErr(LOG_CRIT, + "pmdaInit: PMDA %s: Undefined instance domain serial (%d) specified in metric %s(%d)\n", + pmda->e_name, mindomp->serial, + pmIDStr_r(pmda->e_metrics[m].m_desc.pmid, strbuf, sizeof(strbuf)), m); + dispatch->status = PM_ERR_GENERIC; + return; + } + + } + } + } + + if (pmda->e_helptext != NULL) { + pmda->e_help = pmdaOpenHelp(pmda->e_helptext); + if (pmda->e_help < 0) { + __pmNotifyErr(LOG_WARNING, "pmdaInit: PMDA %s: Unable to open help text file(s) from \"%s\": %s\n", + pmda->e_name, pmda->e_helptext, pmErrStr(pmda->e_help)); + } +#ifdef PCP_DEBUG + else if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "pmdaInit: PMDA %s: help file %s opened\n", pmda->e_name, pmda->e_helptext); + } +#endif + } + else { + if (dispatch->version.two.text == pmdaText) + __pmNotifyErr(LOG_WARNING, "pmdaInit: PMDA %s: No help text file specified", pmda->e_name); +#ifdef PCP_DEBUG + else + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_DEBUG, "pmdaInit: PMDA %s: No help text path specified", pmda->e_name); +#endif + } + + /* + * Stamp the correct domain number in each of the PMIDs + */ + for (m = 0; m < pmda->e_nmetrics; m++) { + pmidp = (__pmID_int *)&pmda->e_metrics[m].m_desc.pmid; + pmidp->domain = dispatch->domain; + } + + if (pmda->e_flags & PMDA_EXT_FLAG_HASHED) + pmdaRehash(pmda, metrics, nmetrics); + else + pmdaDirect(pmda, metrics, nmetrics); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_INFO, "name = %s\n", pmda->e_name); + __pmNotifyErr(LOG_INFO, "domain = %d\n", dispatch->domain); + if (dispatch->comm.flags) + __pmNotifyErr(LOG_INFO, "comm flags = %x\n", dispatch->comm.flags); + __pmNotifyErr(LOG_INFO, "ext flags = %x\n", pmda->e_flags); + __pmNotifyErr(LOG_INFO, "num metrics = %d\n", pmda->e_nmetrics); + __pmNotifyErr(LOG_INFO, "num indom = %d\n", pmda->e_nindoms); + __pmNotifyErr(LOG_INFO, "metric map = %s\n", + (pmda->e_flags & PMDA_EXT_FLAG_HASHED) ? "hashed" : + (pmda->e_direct ? "direct" : "linear")); + } +#endif + + dispatch->status = pmda->e_status; +} + +/* + * version exchange with pmcd via credentials PDU + */ + +static int +__pmdaSetupPDU(int infd, int outfd, int flags, char *agentname) +{ + __pmVersionCred handshake; + __pmCred *credlist = NULL; + __pmPDU *pb; + int i, sts, pinpdu, vflag = 0; + int version = UNKNOWN_VERSION, credcount = 0, sender = 0; + + handshake.c_type = CVERSION; + handshake.c_version = PDU_VERSION; + handshake.c_flags = flags; + if ((sts = __pmSendCreds(outfd, (int)getpid(), 1, (__pmCred *)&handshake)) < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s send creds: %s\n", agentname, pmErrStr(sts)); + return -1; + } + + if ((pinpdu = sts = __pmGetPDU(infd, ANY_SIZE, TIMEOUT_DEFAULT, &pb)) < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s getting creds: %s\n", agentname, pmErrStr(sts)); + return -1; + } + + if (sts == PDU_CREDS) { + if ((sts = __pmDecodeCreds(pb, &sender, &credcount, &credlist)) < 0) { + __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s decode creds: %s\n", agentname, pmErrStr(sts)); + __pmUnpinPDUBuf(pb); + return -1; + } + + for (i = 0; i < credcount; i++) { + switch (credlist[i].c_type) { + case CVERSION: + version = credlist[i].c_vala; + vflag = 1; + break; + default: + __pmNotifyErr(LOG_WARNING, "__pmdaSetupPDU: PMDA %s: unexpected creds PDU\n", agentname); + } + } + if (vflag) { + __pmSetVersionIPC(infd, version); + __pmSetVersionIPC(outfd, version); + } + if (credlist != NULL) + free(credlist); + } + else + __pmNotifyErr(LOG_CRIT, "__pmdaSetupPDU: PMDA %s: version exchange failure\n", agentname); + + if (pinpdu > 0) + __pmUnpinPDUBuf(pb); + + return version; +} + +/* + * set up connection to PMCD + */ + +void +pmdaConnect(pmdaInterface *dispatch) +{ + pmdaExt *pmda = NULL; + int sts, flags = dispatch->comm.flags; + + if (dispatch->version.any.ext == NULL || + (dispatch->version.any.ext->e_flags & PMDA_EXT_SETUPDONE) != PMDA_EXT_SETUPDONE) { + __pmNotifyErr(LOG_CRIT, "pmdaConnect: need to call pmdaDaemon() or pmdaDSO() first"); + dispatch->status = PM_ERR_GENERIC; + return; + } + + if ((dispatch->version.any.ext->e_flags & PMDA_EXT_CONNECTED) == PMDA_EXT_CONNECTED) { + __pmNotifyErr(LOG_CRIT, "pmdaConnect: called more than once"); + dispatch->status = PM_ERR_GENERIC; + return; + } + + if (!HAVE_ANY(dispatch->comm.pmda_interface)) { + __pmNotifyErr(LOG_CRIT, "pmdaConnect: PMDA interface version %d not supported (domain=%d)", + dispatch->comm.pmda_interface, dispatch->domain); + dispatch->status = PM_ERR_GENERIC; + return; + } + pmda = dispatch->version.any.ext; + + switch (pmda->e_io) { + case pmdaPipe: + case pmdaUnknown: /* Default */ + + pmda->e_infd = fileno(stdin); + pmda->e_outfd = fileno(stdout); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: opened pipe to pmcd, infd = %d, outfd = %d\n", + pmda->e_name, pmda->e_infd, pmda->e_outfd); + } +#endif + break; + + case pmdaInet: + + __pmdaOpenInet(pmda->e_sockname, pmda->e_port, &(pmda->e_infd), + &(pmda->e_outfd)); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: opened inet connection, infd = %d, outfd = %d\n", + pmda->e_name, pmda->e_infd, pmda->e_outfd); + } +#endif + + break; + + case pmdaIPv6: + + __pmdaOpenIPv6(pmda->e_sockname, pmda->e_port, &(pmda->e_infd), + &(pmda->e_outfd)); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: opened ipv6 connection, infd = %d, outfd = %d\n", + pmda->e_name, pmda->e_infd, pmda->e_outfd); + } +#endif + + break; + + case pmdaUnix: + + __pmdaOpenUnix(pmda->e_sockname, &(pmda->e_infd), &(pmda->e_outfd)); + +#ifdef PCP_DEBUG + if (pmDebug & DBG_TRACE_LIBPMDA) { + __pmNotifyErr(LOG_DEBUG, "pmdaConnect: PMDA %s: Opened unix connection, infd = %d, outfd = %d\n", + pmda->e_name, pmda->e_infd, pmda->e_outfd); + } +#endif + + break; + default: + __pmNotifyErr(LOG_CRIT, "pmdaConnect: PMDA %s: Illegal iotype: %d\n", pmda->e_name, pmda->e_io); + exit(1); + } + + sts = __pmdaSetupPDU(pmda->e_infd, pmda->e_outfd, flags, pmda->e_name); + if (sts < 0) + dispatch->status = sts; + else { + dispatch->comm.pmapi_version = (unsigned int)sts; + pmda->e_flags |= PMDA_EXT_CONNECTED; + } +} + +/* + * initialise the pmdaExt and pmdaInterface structures for a daemon or DSO PMDA. + */ + +static void +__pmdaSetup(pmdaInterface *dispatch, int version, char *name) +{ + pmdaExt *pmda = NULL; + e_ext_t *extp; + + if (!HAVE_ANY(version)) { + __pmNotifyErr(LOG_CRIT, "__pmdaSetup: %s PMDA: interface version %d not supported (domain=%d)", + name, version, dispatch->domain); + dispatch->status = PM_ERR_GENERIC; + return; + } + + pmda = (pmdaExt *)calloc(1, sizeof(pmdaExt)); + if (pmda == NULL) { + __pmNotifyErr(LOG_ERR, + "%s: Unable to allocate memory for pmdaExt structure (%d bytes)", + name, (int)sizeof(pmdaExt)); + dispatch->status = PM_ERR_GENERIC; + return; + } + + dispatch->status = 0; + + dispatch->comm.pmda_interface = version; + dispatch->comm.pmapi_version = PMAPI_VERSION; + dispatch->comm.flags = 0; + + if (HAVE_V_SIX(version)) { + dispatch->version.six.attribute = pmdaAttribute; + } + if (HAVE_V_FOUR(version)) { + dispatch->version.four.pmid = pmdaPMID; + dispatch->version.four.name = pmdaName; + dispatch->version.four.children = pmdaChildren; + } + dispatch->version.any.profile = pmdaProfile; + dispatch->version.any.fetch = pmdaFetch; + dispatch->version.any.desc = pmdaDesc; + dispatch->version.any.instance = pmdaInstance; + dispatch->version.any.text = pmdaText; + dispatch->version.any.store = pmdaStore; + dispatch->version.any.ext = pmda; + + pmda->e_name = name; + pmda->e_infd = -1; + pmda->e_outfd = -1; + pmda->e_port = -1; + pmda->e_singular = -1; + pmda->e_ordinal = -1; + pmda->e_domain = dispatch->domain; + pmda->e_help = -1; + pmda->e_io = pmdaUnknown; + pmda->e_ext = (void *)dispatch; + pmda->e_flags |= PMDA_EXT_SETUPDONE; + + extp = (e_ext_t *)calloc(1, sizeof(*extp)); + if (extp == NULL) { + __pmNotifyErr(LOG_ERR, + "%s: Unable to allocate memory for e_ext_t structure (%d bytes)", + name, (int)sizeof(*extp)); + free(pmda); + dispatch->version.any.ext = NULL; + dispatch->status = PM_ERR_GENERIC; + return; + } + extp->pmda_interface = version; + pmda->e_ext = (void *)extp; + + pmdaSetResultCallBack(dispatch, __pmFreeResultValues); + pmdaSetFetchCallBack(dispatch, (pmdaFetchCallBack)0); + pmdaSetCheckCallBack(dispatch, (pmdaCheckCallBack)0); + pmdaSetDoneCallBack(dispatch, (pmdaDoneCallBack)0); + pmdaSetEndContextCallBack(dispatch, (pmdaEndContextCallBack)0); +} + +/* + * initialise the pmdaExt and pmdaInterface structures for a daemon + * also set some globals + */ + +void +pmdaDaemon(pmdaInterface *dispatch, int version, char *name, int domain, + char *logfile, char *helptext) +{ + pmdaExt *pmda; + + dispatch->domain = domain; + __pmdaSetup(dispatch, version, name); + + if (dispatch->status < 0) + return; + + pmda = dispatch->version.any.ext; + pmda->e_logfile = logfile; + pmda->e_helptext = helptext; + + __pmSetInternalState(PM_STATE_PMCS); +} + +/* + * initialise the pmdaExt structure for a DSO + * also set some globals + */ + +void +pmdaDSO(pmdaInterface *dispatch, int version, char *name, char *helptext) +{ + __pmdaSetup(dispatch, version, name); + + if (dispatch->status < 0) + return; + + dispatch->version.any.ext->e_helptext = helptext; +} + +/* + * Redirect stderr to the log file + */ + +void +pmdaOpenLog(pmdaInterface *dispatch) +{ + int c; + + if (dispatch->status < 0) + return; + + __pmOpenLog(dispatch->version.any.ext->e_name, + dispatch->version.any.ext->e_logfile, stderr, &c); +} diff --git a/src/libpcp_pmda/src/queues.c b/src/libpcp_pmda/src/queues.c new file mode 100644 index 0000000..ef01b59 --- /dev/null +++ b/src/libpcp_pmda/src/queues.c @@ -0,0 +1,610 @@ +/* + * Generic event queue support for PMDAs + * + * Copyright (c) 2011 Red Hat Inc. + * Copyright (c) 2011 Nathan Scott. 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 "queues.h" +#include <ctype.h> + +static event_queue_t *queues; +static int numqueues; + +static event_client_t *clients; +static int numclients; +static event_client_t *client_lookup(int context); + +typedef void (*clientVisitCallBack)(event_clientq_t *, event_queue_t *, void *); +static void client_iterate(clientVisitCallBack, int, event_queue_t *, void *); + +static event_queue_t * +queue_lookup(int handle) +{ + if (handle >= numqueues || handle < 0) + return NULL; + if (queues[handle].inuse) + return &queues[handle]; + return NULL; +} + +/* + * Drop an event after it has been queued (i.e. client was too slow) + */ +static void +queue_drop(event_clientq_t *clientq, event_queue_t *queue, void *data) +{ + event_t *event = (event_t *)data; + + if (clientq->last != NULL && clientq->last == event) { + clientq->last = TAILQ_NEXT(event, events); + clientq->missed++; + + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Client missed queue %s event %p", + queue->name, event); + } +} + +static void +queue_drop_bytes(int handle, event_queue_t *queue, size_t bytes) +{ + event_t *event, *next; + + event = TAILQ_FIRST(&queue->tailq); + while (event) { + if (bytes <= queue->maxmemory - queue->qsize) + break; + next = TAILQ_NEXT(event, events); + + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Dropping %s: e=%p sz=%d max=%d qsz=%d", + queue->name, event, (int)event->size, + (int)queue->maxmemory, (int)queue->qsize); + + /* Walk clients - if event last seen, drop it and bump missed count */ + client_iterate(queue_drop, handle, queue, event); + + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Removing %s event %p (%d bytes)", + queue->name, event, (int)event->size); + + TAILQ_REMOVE(&queue->tailq, event, events); + queue->qsize -= event->size; + free(event); + event = next; + } +} + +int +pmdaEventNewActiveQueue(const char *name, size_t maxmemory, unsigned int numclients) +{ + event_queue_t *queue; + size_t size; + int i; + + if (name == NULL || maxmemory <= 0) + return -EINVAL; + + for (i = 0; i < numqueues; i++) + if (queues[i].inuse && strcmp(queues[i].name, name) == 0) + return -EEXIST; + + for (i = 0; i < numqueues; i++) + if (queues[i].inuse == 0) + break; + if (i == numqueues) { + /* + * No free slots - extend the available set. + * realloc() potential moves "queues" address, fix up + * must tear down existing queues which may have back + * references and then re-initialise them afterward. + */ + for (i = 0; i < numqueues; i++) + queue_drop_bytes(i, &queues[i], INT_MAX); + size = (numqueues + 1) * sizeof(event_queue_t); + queues = realloc(queues, size); + if (!queues) + __pmNoMem("pmdaEventNewQueue", size, PM_FATAL_ERR); + /* realloc moves tailq tqh_last pointer - reset 'em */ + for (i = 0; i < numqueues; i++) + TAILQ_INIT(&queues[i].tailq); + numqueues++; + } + + /* "i" now indexes into a free slot */ + queue = &queues[i]; + memset(queue, 0, sizeof(*queue)); + TAILQ_INIT(&queue->tailq); + queue->eventarray = pmdaEventNewArray(); + queue->numclients = numclients; + queue->maxmemory = maxmemory; + queue->inuse = 1; + queue->name = name; + return i; +} + +int +pmdaEventNewQueue(const char *name, size_t maxmemory) +{ + return pmdaEventNewActiveQueue(name, maxmemory, 0); +} + +int +pmdaEventQueueHandle(const char *name) +{ + int i; + + for (i = 0; i < numqueues; i++) + if (queues[i].inuse && strcmp(queues[i].name, name) == 0) + return i; + return -ESRCH; +} + +int +pmdaEventQueueCounter(int handle, pmAtomValue *atom) +{ + event_queue_t *queue = queue_lookup(handle); + + if (!queue) + return -EINVAL; + atom->ul = queue->count; + return PMDA_FETCH_STATIC; +} + +int +pmdaEventQueueClients(int handle, pmAtomValue *atom) +{ + event_queue_t *queue = queue_lookup(handle); + + if (!queue) + return -EINVAL; + atom->ul = queue->numclients; + return PMDA_FETCH_STATIC; +} + +int +pmdaEventQueueMemory(int handle, pmAtomValue *atom) +{ + event_queue_t *queue = queue_lookup(handle); + + if (!queue) + return -EINVAL; + atom->ull = queue->qsize; + return PMDA_FETCH_STATIC; +} + +int +pmdaEventQueueBytes(int handle, pmAtomValue *atom) +{ + event_queue_t *queue = queue_lookup(handle); + + if (!queue) + return -EINVAL; + atom->ull = queue->bytes; + return PMDA_FETCH_STATIC; +} + +int +pmdaEventQueueAppend(int handle, void *data, size_t bytes, struct timeval *tv) +{ + event_queue_t *queue = queue_lookup(handle); + event_t *event; + + if (!queue) + return -EINVAL; + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Appending event: queue#%d \"%s\" (%ld bytes)", + handle, queue->name, (long)bytes); + if (bytes > queue->maxmemory) { + __pmNotifyErr(LOG_WARNING, "Event too large for queue %s (%ld > %ld)", + queue->name, (long)bytes, (long)queue->maxmemory); + goto done; + } + + /* + * We may need to make room in the event queue. If so, start at the head + * and madly drop events until sufficient space exists or all are freed. + * Bump the missed counter for each client who missed an event we had to + * throw away. + */ + queue_drop_bytes(handle, queue, bytes); + if (queue->numclients == 0) + goto done; + + if ((event = malloc(sizeof(event_t) + bytes + 1)) == NULL) { + __pmNotifyErr(LOG_ERR, "event allocation failure: %ld bytes", + (long)(bytes + 1)); + return -ENOMEM; + } + + /* Track the actual event data */ + event->count = queue->numclients; + memcpy(event->buffer, data, bytes); + memcpy(&event->time, tv, sizeof(*tv)); + event->size = bytes; + + /* Finally, store the event in the queue */ + TAILQ_INSERT_TAIL(&queue->tailq, event, events); + queue->qsize += bytes; + + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, + "Inserted %s event %p (%ld bytes) clients = %d.", + queue->name, event, (long)event->size, event->count); + +done: + /* Update event queue tracking stats (even for no-clients case) */ + queue->bytes += bytes; + queue->count++; + return 0; +} + +static int +queue_filter(event_clientq_t *clientq, void *data, size_t size) +{ + /* Note: having a filter implies access (optionally) checked there */ + if (clientq->filter) { + int sts = clientq->apply(clientq->filter, data, size); + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Clientq filter applied (%d)\n", sts); + return sts; + } + else if (!clientq->access) { + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Clientq access denied\n"); + return -PM_ERR_PERMISSION; + } + return 0; +} + +static int +queue_fetch(event_queue_t *queue, event_clientq_t *clientq, pmAtomValue *atom, + pmdaEventDecodeCallBack queue_decoder, void *data) +{ + event_t *event, *next; + int records, key, sts; + + /* + * Ensure the way we keep track of which clients are interested + * in which queues is up to date. + */ + if (clientq->active == 0) { + clientq->active = 1; + queue->numclients++; + } + if (clientq->last == NULL) + clientq->last = TAILQ_FIRST(&queue->tailq); + event = clientq->last; + + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "queue_fetch start, last event=%p\n", event); + + sts = records = 0; + key = queue->eventarray; + pmdaEventResetArray(key); + + while (event != NULL) { + char message[64]; + + if (queue_filter(clientq, event->buffer, event->size)) { + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Culling event (sz=%ld): \"%s\"", + (long)event->size, + __pmdaEventPrint(event->buffer, event->size, + message, sizeof(message))); + } else { + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Adding event (sz=%ld): \"%s\"", + (long)event->size, + __pmdaEventPrint(event->buffer, event->size, + message, sizeof(message))); + if ((sts = queue_decoder(key, + event->buffer, event->size, &event->time, data)) < 0) + break; + records += sts; + sts = 0; + } + + next = TAILQ_NEXT(event, events); + + /* Remove the current one (if its use count hits zero) */ + if (--event->count <= 0) { + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Removing %s event %p in fetch", + queue->name, event); + TAILQ_REMOVE(&queue->tailq, event, events); + queue->qsize -= event->size; + free(event); + } + + /* Go on to the next event. */ + event = next; + } + + /* + * Did this client miss any events? The "extra" one is the last previously + * observed event (pointed at by per-context last pointer) - so, event only + * missed once we move past *more* than just that last observed event. + */ + if (sts == 0) { + sts = clientq->missed - 1; + clientq->missed = 0; + if (sts > 0) { + struct timeval timestamp; + gettimeofday(×tamp, NULL); + sts = pmdaEventAddMissedRecord(key, ×tamp, sts); + records++; + } else { + sts = 0; + } + } + + /* Update queue tail pointer for this client. */ + clientq->last = NULL; + + atom->vbp = records ? (pmValueBlock *)pmdaEventGetAddr(key) : NULL; + return sts; +} + +static event_clientq_t * +client_queue_lookup(int context, int handle, int accessq) +{ + event_client_t *client = client_lookup(context); + event_queue_t *queue = queue_lookup(handle); + size_t size; + + /* + * If context doesn't exist, bail out. But, if per-client + * queue information doesn't exist for that context yet, it + * is create iff an indication of interest was shown by the + * caller (i.e. "accessq" was set). + */ + if (!client || !queue) + return NULL; + if (handle < client->nclientq) + return &client->clientq[handle]; + if (!accessq) + return NULL; + + /* allocate (possibly multiple) queue slots for this client */ + size = (handle + 1) * sizeof(struct event_clientq); + client->clientq = realloc(client->clientq, size); + if (!client->clientq) + __pmNoMem("client_queue_lookup", size, PM_FATAL_ERR); + + /* ensure any new clientq's up to this one are initialised */ + size -= client->nclientq * sizeof(struct event_clientq); + memset(client->clientq + client->nclientq, 0, size); + client->nclientq = handle + 1; + return &client->clientq[handle]; +} + +int +pmdaEventQueueRecords(int handle, pmAtomValue *atom, int context, + pmdaEventDecodeCallBack queue_decoder, void *data) +{ + event_clientq_t *clientq = client_queue_lookup(context, handle, 1); + event_queue_t *queue = queue_lookup(handle); + int sts; + + if (!queue || !clientq) + return -EINVAL; + + sts = queue_fetch(queue, clientq, atom, queue_decoder, data); + if (sts != 0) + return sts; + return (atom->vbp == NULL) ? PMDA_FETCH_NOVALUES : PMDA_FETCH_STATIC; +} + +/* + * We've lost a client (disconnected). + * Cleanup any filter and any back references across the queues. + */ +static void +queue_cleanup(int handle, event_clientq_t *clientq) +{ + event_queue_t *queue = queue_lookup(handle); + event_t *event, *next; + + if (clientq->release) + clientq->release(clientq->filter); + + if (!queue || !clientq->active) + return; + + event = clientq->last; + while (event) { + next = TAILQ_NEXT(event, events); + + /* Remove the current event (if use count hits zero) */ + if (--event->count <= 0) { + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "Removing %s event %p", + queue->name, event); + TAILQ_REMOVE(&queue->tailq, event, events); + queue->qsize -= event->size; + free(event); + } + event = next; + } + + queue->numclients--; +} + +char * +__pmdaEventPrint(const char *buffer, int bufsize, char *msg, int msgsize) +{ + int minsize = msgsize < bufsize ? msgsize : bufsize; + int i; + + if (msgsize < 4) + return NULL; + memcpy(msg, buffer, minsize); + memset(msg + minsize, '.', msgsize - minsize); + msg[minsize - 1] = '\0'; + for (i = 0; i < minsize - 1; i++) { + if (isspace((int)msg[i])) + msg[i] = ' '; + else if (!isprint((int)msg[i])) + msg[i] = '.'; + } + return msg; +} + +int +pmdaEventNewClient(int context) +{ + event_client_t *client; + int size, i; + + for (i = 0; i < numclients; i++) { + if (clients[i].context == context && clients[i].inuse) + return i; + } + for (i = 0; i < numclients; i++) { + if (clients[i].inuse == 0) + break; + } + if (i == numclients) { + /* no free slots, extend the available set */ + size = (numclients + 1) * sizeof(event_client_t); + clients = realloc(clients, size); + if (!clients) + __pmNoMem("pmdaEventNewClient", size, PM_FATAL_ERR); + numclients++; + + if (pmDebug & DBG_TRACE_LIBPMDA) + __pmNotifyErr(LOG_INFO, "%s: new client, slot=%d (total=%d)\n", + __FUNCTION__, i, numclients); + } + + /* "i" now indexes into a free slot */ + client = &clients[i]; + memset(client, 0, sizeof(*client)); + client->context = context; + client->inuse = 1; + return i; +} + +static event_client_t * +client_lookup(int context) +{ + int i; + + for (i = 0; i < numclients; i++) + if (clients[i].context == context && clients[i].inuse) + return &clients[i]; + return NULL; +} + +/* + * Visit each active context and run a supplied callback routine + */ +static void +client_iterate(clientVisitCallBack visit, + int handle, event_queue_t *queue, void *data) +{ + event_clientq_t *clientq; + int i; + + for (i = 0; i < numclients; i++) { + if (!clients[i].inuse) + continue; + clientq = client_queue_lookup(clients[i].context, handle, 0); + if (clientq && clientq->active) + visit(clientq, queue, data); + } +} + +int +pmdaEventEndClient(int context) +{ + event_client_t *client = client_lookup(context); + int i; + + if (pmDebug & DBG_TRACE_LIBPMDA) + fprintf(stderr, "pmdaEventEndClient: ctx=%d slot=%d\n", + context, client ? (int)(client - clients) : 0); + + if (!client) { + /* + * This is expected ... when a context is closed in pmcd + * (or for a local context or for dbpmda or ...) all the + * PMDAs with a registered pmdaEndContextCallBack will be + * called and some of the PMDAs may not have not serviced + * any previous requests for that context. + */ + return 0; + } + + for (i = 0; i < client->nclientq; i++) + queue_cleanup(i, &client->clientq[i]); + if (client->clientq) + free(client->clientq); + + memset(client, 0, sizeof(*client)); /* sets !inuse */ + return 0; +} + +int +pmdaEventClients(pmAtomValue *atom) +{ + __uint32_t i, c = 0; + + for (i = 0; i < numclients; i++) + if (clients[i].inuse) + c++; + atom->ul = c; + return PMDA_FETCH_STATIC; +} + +/* + * Marks context as having been pmStore'd into (access allowed), + * adds optional filtering data for the current client context. + */ +int +pmdaEventSetFilter(int context, int handle, void *filter, + pmdaEventApplyFilterCallBack apply, + pmdaEventReleaseFilterCallBack release) +{ + event_clientq_t *clientq = client_queue_lookup(context, handle, 1); + + if (!clientq) + return -EINVAL; + + /* first, free up any existing filter */ + if (clientq->filter) + clientq->release(clientq->filter); + + clientq->apply = apply; + clientq->filter = filter; + clientq->release = release; + clientq->access = 1; + return 0; +} + +int +pmdaEventSetAccess(int context, int handle, int allow) +{ + event_clientq_t *clientq = client_queue_lookup(context, handle, 1); + + if (!clientq) + return -EINVAL; + + clientq->access = allow; + return 0; +} diff --git a/src/libpcp_pmda/src/queues.h b/src/libpcp_pmda/src/queues.h new file mode 100644 index 0000000..607655e --- /dev/null +++ b/src/libpcp_pmda/src/queues.h @@ -0,0 +1,172 @@ +/* + * Event queue support for PMDAs + * + * Copyright (c) 2011 Red Hat Inc. + * Copyright (c) 2011 Nathan Scott. 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. + * + * Portions Copyright (c) 1991, 1993 + * + * The Regents of the University of California. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _QUEUES_H +#define _QUEUES_H + +/* + * Extracts of TAILQ implementation from <sys/queue.h> included directly + * for platforms without support (Win32, older Linux variants). + * + * A tail queue is headed by a pair of pointers, one to the head of the + * list and the other to the tail of the list. The elements are doubly + * linked so that an arbitrary element can be removed without a need to + * traverse the list. New elements can be added to the list before or + * after an existing element, at the head of the list, or at the end of + * the list. A tail queue may be traversed in either direction. + * + * For details on the use of these macros, see the queue(3) manual page. + */ + +#define _TAILQ_HEAD(name, type, qual) \ +struct name { \ + qual type *tqh_first; /* first element */ \ + qual type *qual *tqh_last; /* addr of last next element */ \ +} +#define TAILQ_HEAD(name, type) _TAILQ_HEAD(name, struct type,) + +#define _TAILQ_ENTRY(type, qual) \ +struct { \ + qual type *tqe_next; /* next element */ \ + qual type *qual *tqe_prev; /* address of previous next element */\ +} +#define TAILQ_ENTRY(type) _TAILQ_ENTRY(struct type,) + +#define TAILQ_INIT(head) do { \ + (head)->tqh_first = NULL; \ + (head)->tqh_last = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_HEAD(head, elm, field) do { \ + if (((elm)->field.tqe_next = (head)->tqh_first) != NULL) \ + (head)->tqh_first->field.tqe_prev = \ + &(elm)->field.tqe_next; \ + else \ + (head)->tqh_last = &(elm)->field.tqe_next; \ + (head)->tqh_first = (elm); \ + (elm)->field.tqe_prev = &(head)->tqh_first; \ +} while (0) + +#define TAILQ_INSERT_TAIL(head, elm, field) do { \ + (elm)->field.tqe_next = NULL; \ + (elm)->field.tqe_prev = (head)->tqh_last; \ + *(head)->tqh_last = (elm); \ + (head)->tqh_last = &(elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_REMOVE(head, elm, field) do { \ + if (((elm)->field.tqe_next) != NULL) \ + (elm)->field.tqe_next->field.tqe_prev = \ + (elm)->field.tqe_prev; \ + else \ + (head)->tqh_last = (elm)->field.tqe_prev; \ + *(elm)->field.tqe_prev = (elm)->field.tqe_next; \ +} while (0) + +#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) + +#define TAILQ_LAST(head, headname) \ + (*(((struct headname *)((head)->tqh_last))->tqh_last)) +#define TAILQ_PREV(elm, headname, field) \ + (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) + + +/* + * Data structures used in the PMDA event queue implementation + * Every event is timestamped and linked into one (tail) queue. + * Events know nothing about the clients accessing them. + */ + +typedef struct event { + TAILQ_ENTRY(event) events; /* link into queue of events */ + struct timeval time; /* timestamp for this event */ + int count; /* events reference count */ + size_t size; /* buffer size in bytes */ + char buffer[]; +} event_t; + +TAILQ_HEAD(tailqueue, event); + +typedef struct event_queue { + const char *name; /* callers identifier for this queue */ + size_t maxmemory; /* max data bytes that can be queued */ + int inuse; /* is this queue in use or free */ + int eventarray; /* event records for this queue */ + __uint32_t numclients; /* export: number of active clients */ + __uint32_t count; /* exported: event counter */ + __uint64_t bytes; /* exported: data throughput */ + __uint64_t qsize; /* data in the queue (<= maxmem) */ + struct tailqueue tailq; /* queue of events for clients */ +} event_queue_t; + +/* + * Data structures used in the PMDA event client implementation + * Each client is one PCP tool invocation (e.g. pmevent) and has + * a link back to those queues which it has fetched/stored into + * at some point in the past. The "last" event pointer, gives a + * pointer to the last observed event for that client, which is + * used as the starting point for a subsequent fetch request (or + * when dropping events, should the client not be keeping up). + */ + +typedef struct event_clientq { + int active; /* client interest in this queue */ + int missed; /* count of events missed on queue */ + int access; /* is access restricted/permitted */ + event_t *last; /* last event seen on this queue */ + void *filter; /* filter data for the event queue */ + pmdaEventApplyFilterCallBack apply; /* actual filter callback */ + pmdaEventReleaseFilterCallBack release; /* remove filter callback */ +} event_clientq_t; + +typedef struct event_client { + int context; /* client context identifier */ + int inuse; /* is this table slot in use */ + int nclientq; /* allocated size of clientq */ + event_clientq_t *clientq; /* per-queue client state */ +} event_client_t; + +#endif /* _QUEUES_H */ diff --git a/src/libpcp_pmda/src/tree.c b/src/libpcp_pmda/src/tree.c new file mode 100644 index 0000000..ab24504 --- /dev/null +++ b/src/libpcp_pmda/src/tree.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2014 Red Hat. + * Copyright (c) 2009-2010 Aconex. All Rights Reserved. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This library 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 Lesser General Public + * License for more details. + */ + +#include "pmapi.h" +#include "impl.h" +#include "pmda.h" + +#define NONLEAF(node) ((node)->pmid == PM_ID_NULL) + +/* + * Fixup the parent pointers of the tree. + * Fill in the hash table with nodes from the tree. + * Hashing is done on pmid. + */ +static void +__pmdaTreeReindexHash(__pmnsTree *tree, __pmnsNode *root) +{ + __pmnsNode *np; + + for (np = root->first; np != NULL; np = np->next) { + np->parent = root; + if (np->pmid != PM_ID_NULL) { + int i = np->pmid % tree->htabsize; + np->hash = tree->htab[i]; + tree->htab[i] = np; + } + __pmdaTreeReindexHash(tree, np); + } +} + +/* + * "Make the average hash list no longer than 5, and the number + * of hash table entries not a multiple of 2, 3 or 5." + * [From __pmFixPMNSHashTab; without mark_all, dinks with pmids] + */ +void +pmdaTreeRebuildHash(__pmnsTree *tree, int numpmid) +{ + if (tree) { + int htabsize = numpmid / 5; + + if (htabsize % 2 == 0) htabsize++; + if (htabsize % 3 == 0) htabsize += 2; + if (htabsize % 5 == 0) htabsize += 2; + tree->htabsize = htabsize; + tree->htab = (__pmnsNode **)calloc(htabsize, sizeof(__pmnsNode *)); + if (tree->htab) { + __pmdaTreeReindexHash(tree, tree->root); + } else { + __pmNoMem("pmdaTreeRebuildHash", + htabsize * sizeof(__pmnsNode *), PM_RECOV_ERR); + tree->htabsize = 0; + } + } +} + +static int +__pmdaNodeCount(__pmnsNode *parent) +{ + __pmnsNode *np; + int count; + + count = 0; + for (np = parent->first; np != NULL; np = np->next) { + if (np->pmid != PM_ID_NULL) + count++; + else + count += __pmdaNodeCount(np); + } + return count; +} + +int +pmdaTreeSize(__pmnsTree *pmns) +{ + if (pmns && pmns->root) + return __pmdaNodeCount(pmns->root); + return 0; +} + +static __pmnsNode * +__pmdaNodeLookup(__pmnsNode *node, const char *name) +{ + while (node != NULL) { + size_t length = strlen(node->name); + if (strncmp(name, node->name, length) == 0) { + if (name[length] == '\0') + return node; + if (name[length] == '.' && NONLEAF(node)) + return __pmdaNodeLookup(node->first, name + length + 1); + } + node = node->next; + } + return NULL; +} + +int +pmdaTreePMID(__pmnsTree *pmns, const char *name, pmID *pmid) +{ + if (pmns && pmns->root) { + __pmnsNode *node; + + if ((node = __pmdaNodeLookup(pmns->root->first, name)) == NULL) + return PM_ERR_NAME; + if (NONLEAF(node)) + return PM_ERR_NAME; + *pmid = node->pmid; + return 0; + } + return PM_ERR_NAME; +} + +static char * +__pmdaNodeAbsoluteName(__pmnsNode *node, char *buffer) +{ + if (node && node->parent) { + buffer = __pmdaNodeAbsoluteName(node->parent, buffer); + strcpy(buffer, node->name); + buffer += strlen(node->name); + *buffer++ = '.'; + } + return buffer; +} + +int +pmdaTreeName(__pmnsTree *pmns, pmID pmid, char ***nameset) +{ + __pmnsNode *hashchain, *node, *parent; + int nmatch = 0, length = 0; + char *p, **list; + + if (!pmns) + return PM_ERR_PMID; + + hashchain = pmns->htab[pmid % pmns->htabsize]; + for (node = hashchain; node != NULL; node = node->hash) { + if (node->pmid == pmid) { + for (parent = node; parent->parent; parent = parent->parent) + length += strlen(parent->name) + 1; + nmatch++; + } + } + + if (nmatch == 0) + return PM_ERR_PMID; + + length += nmatch * sizeof(char *); /* pointers to names */ + + if ((list = (char **)malloc(length)) == NULL) + return -oserror(); + + p = (char *)&list[nmatch]; + nmatch = 0; + for (node = hashchain; node != NULL; node = node->hash) { + if (node->pmid == pmid) { + list[nmatch++] = p; + p = __pmdaNodeAbsoluteName(node, p); + *(p-1) = '\0'; /* overwrite final '.' */ + } + } + + *nameset = list; + return nmatch; +} + +static int +__pmdaNodeRelativeChildren(__pmnsNode *base, char ***offspring, int **status) +{ + __pmnsNode *node; + char **list, *p; + int *leaf, length = 0, nmatch = 0; + + for (node = base; node != NULL; node = node->next, nmatch++) + length += strlen(node->name) + 1; + if (nmatch == 0) { + /* + * no need to allocate zero sized arrays for offspring[] + * and status[] + */ + return 0; + } + length += nmatch * sizeof(char *); /* pointers to names */ + if ((list = (char **)malloc(length)) == NULL) + return -oserror(); + if ((leaf = (int *)malloc(nmatch * sizeof(int))) == NULL) { + free(list); + return -oserror(); + } + p = (char *)&list[nmatch]; + nmatch = 0; + for (node = base; node != NULL; node = node->next, nmatch++) { + leaf[nmatch] = NONLEAF(node) ? PMNS_NONLEAF_STATUS : PMNS_LEAF_STATUS; + list[nmatch] = p; + strcpy(p, node->name); + p += strlen(node->name); + *p++ = '\0'; + } + + *offspring = list; + *status = leaf; + return nmatch; +} + +static void +__pmdaNodeChildrenGetSize(__pmnsNode *base, int kids, int *length, int *nmetrics) +{ + __pmnsNode *node, *parent; + + /* walk to every leaf & then add its (absolute name) length */ + for (node = base; node != NULL; node = node->next) { + if (NONLEAF(node)) { + __pmdaNodeChildrenGetSize(node->first, 1, length, nmetrics); + continue; + } + for (parent = node; parent->parent; parent = parent->parent) + *length += strlen(parent->name) + 1; + (*nmetrics)++; + if (!kids) + break; + } +} + +/* + * Fill the pmdaChildren buffers - names and leaf status. Called recursively + * to descend down to all leaf nodes. Offset parameter is the current offset + * into the name list buffer, and its also returned at the end of each call - + * it keeps track of where the next name is to start in (list) output buffer. + */ +static char * +__pmdaNodeChildrenGetList(__pmnsNode *base, int kids, int *nmetrics, char *p, char **list, int *leaf) +{ + __pmnsNode *node; + int count = *nmetrics; + char *start = p; + + for (node = base; node != NULL; node = node->next) { + if (NONLEAF(node)) { + p = __pmdaNodeChildrenGetList(node->first, 1, &count, p, list, leaf); + start = p; + continue; + } + leaf[count] = PMNS_LEAF_STATUS; + list[count] = start; + p = __pmdaNodeAbsoluteName(node, p); + *(p-1) = '\0'; /* overwrite final '.' */ + start = p; + count++; + if (!kids) + break; + } + *nmetrics = count; + return p; +} + +static int +__pmdaNodeAbsoluteChildren(__pmnsNode *node, char ***offspring, int **status) +{ + char *p, **list; + int *leaf, descend = 0, length = 0, nmetrics = 0; + + if (NONLEAF(node)) { + node = node->first; + descend = 1; + } + __pmdaNodeChildrenGetSize(node, descend, &length, &nmetrics); + + length += nmetrics * sizeof(char *); /* pointers to names */ + if ((list = (char **)malloc(length)) == NULL) + return -oserror(); + if ((leaf = (int *)malloc(nmetrics * sizeof(int))) == NULL) { + free(list); + return -oserror(); + } + + p = (char *)&list[nmetrics]; + nmetrics = 0; /* start at the start */ + __pmdaNodeChildrenGetList(node, descend, &nmetrics, p, list, leaf); + + *offspring = list; + *status = leaf; + return nmetrics; +} + +int +pmdaTreeChildren(__pmnsTree *pmns, const char *name, int traverse, char ***offspring, int **status) +{ + __pmnsNode *node; + int sts; + + if (!pmns) + return PM_ERR_NAME; + + if ((node = __pmdaNodeLookup(pmns->root->first, name)) == NULL) + return PM_ERR_NAME; + + if (traverse == 0) + sts = __pmdaNodeRelativeChildren(node->first, offspring, status); + else + sts = __pmdaNodeAbsoluteChildren(node, offspring, status); + return sts; +} |