/* * This file is part of the rsyslog runtime library. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * -or- * see COPYING.ASL20 in the source distribution * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "config.h" #include #include #include #include #include #include "rsyslog.h" #include "srUtils.h" #include "errmsg.h" #include "rsconf.h" #include "unicode-helper.h" /* definitions for objects we access */ DEFobjStaticHelpers DEFobjCurrIf(statsobj) #define DYNSTATS_PARAM_NAME "name" #define DYNSTATS_PARAM_RESETTABLE "resettable" #define DYNSTATS_PARAM_MAX_CARDINALITY "maxCardinality" #define DYNSTATS_PARAM_UNUSED_METRIC_LIFE "unusedMetricLife" /* in seconds */ #define DYNSTATS_DEFAULT_RESETTABILITY 1 #define DYNSTATS_DEFAULT_MAX_CARDINALITY 2000 #define DYNSTATS_DEFAULT_UNUSED_METRIC_LIFE 3600 /* seconds */ #define DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH 100 #define DYNSTATS_METRIC_NAME_SEPARATOR '.' #define DYNSTATS_HASHTABLE_SIZE_OVERPROVISIONING 1.25 static struct cnfparamdescr modpdescr[] = { { DYNSTATS_PARAM_NAME, eCmdHdlrString, CNFPARAM_REQUIRED }, { DYNSTATS_PARAM_RESETTABLE, eCmdHdlrBinary, 0 }, { DYNSTATS_PARAM_MAX_CARDINALITY, eCmdHdlrPositiveInt, 0}, { DYNSTATS_PARAM_UNUSED_METRIC_LIFE, eCmdHdlrPositiveInt, 0} /* in minutes */ }; static struct cnfparamblk modpblk = { CNFPARAMBLK_VERSION, sizeof(modpdescr)/sizeof(struct cnfparamdescr), modpdescr }; rsRetVal dynstatsClassInit(void) { DEFiRet; CHKiRet(objGetObjInterface(&obj)); CHKiRet(objUse(statsobj, CORE_COMPONENT)); finalize_it: RETiRet; } static void dynstats_destroyCtr(dynstats_ctr_t *ctr) { statsobj.DestructUnlinkedCounter(ctr->pCtr); free(ctr->metric); free(ctr); } static void /* assumes exclusive access to bucket */ dynstats_destroyCountersIn(dynstats_bucket_t *b, htable *table, dynstats_ctr_t *ctrs) { dynstats_ctr_t *ctr; int ctrs_purged = 0; hashtable_destroy(table, 0); while (ctrs != NULL) { ctr = ctrs; ctrs = ctrs->next; dynstats_destroyCtr(ctr); ctrs_purged++; } STATSCOUNTER_ADD(b->ctrMetricsPurged, b->mutCtrMetricsPurged, ctrs_purged); ATOMIC_SUB_unsigned(&b->metricCount, ctrs_purged, &b->mutMetricCount); } static void /* assumes exclusive access to bucket */ dynstats_destroyCounters(dynstats_bucket_t *b) { statsobj.UnlinkAllCounters(b->stats); dynstats_destroyCountersIn(b, b->table, b->ctrs); } static void dynstats_destroyBucket(dynstats_buckets_t *bkts, dynstats_bucket_t* b) { pthread_rwlock_wrlock(&b->lock); dynstats_destroyCounters(b); dynstats_destroyCountersIn(b, b->survivor_table, b->survivor_ctrs); statsobj.Destruct(&b->stats); free(b->name); pthread_rwlock_unlock(&b->lock); pthread_rwlock_destroy(&b->lock); pthread_mutex_destroy(&b->mutMetricCount); statsobj.DestructCounter(bkts->global_stats, b->pOpsOverflowCtr); statsobj.DestructCounter(bkts->global_stats, b->pNewMetricAddCtr); statsobj.DestructCounter(bkts->global_stats, b->pNoMetricCtr); statsobj.DestructCounter(bkts->global_stats, b->pMetricsPurgedCtr); statsobj.DestructCounter(bkts->global_stats, b->pOpsIgnoredCtr); statsobj.DestructCounter(bkts->global_stats, b->pPurgeTriggeredCtr); free(b); } static rsRetVal dynstats_addBucketMetrics(dynstats_buckets_t *bkts, dynstats_bucket_t *b, const uchar* name) { uchar *metric_name_buff, *metric_suffix; const uchar *suffix_litteral; int name_len; DEFiRet; name_len = ustrlen(name); CHKmalloc(metric_name_buff = malloc((name_len + DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH + 1) * sizeof(uchar))); strcpy((char*)metric_name_buff, (char*)name); metric_suffix = metric_name_buff + name_len; *metric_suffix = DYNSTATS_METRIC_NAME_SEPARATOR; metric_suffix++; suffix_litteral = UCHAR_CONSTANT("ops_overflow"); ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH); STATSCOUNTER_INIT(b->ctrOpsOverflow, b->mutCtrOpsOverflow); CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(b->ctrOpsOverflow), &b->pOpsOverflowCtr, 1)); suffix_litteral = UCHAR_CONSTANT("new_metric_add"); ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH); STATSCOUNTER_INIT(b->ctrNewMetricAdd, b->mutCtrNewMetricAdd); CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(b->ctrNewMetricAdd), &b->pNewMetricAddCtr, 1)); suffix_litteral = UCHAR_CONSTANT("no_metric"); ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH); STATSCOUNTER_INIT(b->ctrNoMetric, b->mutCtrNoMetric); CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(b->ctrNoMetric), &b->pNoMetricCtr, 1)); suffix_litteral = UCHAR_CONSTANT("metrics_purged"); ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH); STATSCOUNTER_INIT(b->ctrMetricsPurged, b->mutCtrMetricsPurged); CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(b->ctrMetricsPurged), &b->pMetricsPurgedCtr, 1)); suffix_litteral = UCHAR_CONSTANT("ops_ignored"); ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH); STATSCOUNTER_INIT(b->ctrOpsIgnored, b->mutCtrOpsIgnored); CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(b->ctrOpsIgnored), &b->pOpsIgnoredCtr, 1)); suffix_litteral = UCHAR_CONSTANT("purge_triggered"); ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH); STATSCOUNTER_INIT(b->ctrPurgeTriggered, b->mutCtrPurgeTriggered); CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(b->ctrPurgeTriggered), &b->pPurgeTriggeredCtr, 1)); finalize_it: free(metric_name_buff); if (iRet != RS_RET_OK) { if (b->pOpsOverflowCtr != NULL) { statsobj.DestructCounter(bkts->global_stats, b->pOpsOverflowCtr); } if (b->pNewMetricAddCtr != NULL) { statsobj.DestructCounter(bkts->global_stats, b->pNewMetricAddCtr); } if (b->pNoMetricCtr != NULL) { statsobj.DestructCounter(bkts->global_stats, b->pNoMetricCtr); } if (b->pMetricsPurgedCtr != NULL) { statsobj.DestructCounter(bkts->global_stats, b->pMetricsPurgedCtr); } if (b->pOpsIgnoredCtr != NULL) { statsobj.DestructCounter(bkts->global_stats, b->pOpsIgnoredCtr); } if (b->pPurgeTriggeredCtr != NULL) { statsobj.DestructCounter(bkts->global_stats, b->pPurgeTriggeredCtr); } } RETiRet; } static void no_op_free(void __attribute__((unused)) *ignore) {} static rsRetVal /* assumes exclusive access to bucket */ dynstats_rebuildSurvivorTable(dynstats_bucket_t *b) { htable *survivor_table = NULL; htable *new_table = NULL; size_t htab_sz; DEFiRet; htab_sz = (size_t) (DYNSTATS_HASHTABLE_SIZE_OVERPROVISIONING * b->maxCardinality + 1); if (b->table == NULL) { CHKmalloc(survivor_table = create_hashtable(htab_sz, hash_from_string, key_equals_string, no_op_free)); } CHKmalloc(new_table = create_hashtable(htab_sz, hash_from_string, key_equals_string, no_op_free)); statsobj.UnlinkAllCounters(b->stats); if (b->survivor_table != NULL) { dynstats_destroyCountersIn(b, b->survivor_table, b->survivor_ctrs); } b->survivor_table = (b->table == NULL) ? survivor_table : b->table; b->survivor_ctrs = b->ctrs; b->table = new_table; b->ctrs = NULL; finalize_it: if (iRet != RS_RET_OK) { LogError(errno, RS_RET_INTERNAL_ERROR, "error trying to evict " "TTL-expired metrics of dyn-stats bucket named: %s", b->name); if (new_table == NULL) { LogError(errno, RS_RET_INTERNAL_ERROR, "error trying to " "initialize hash-table for dyn-stats bucket named: %s", b->name); } else { assert(0); /* "can" not happen -- triggers Coverity CID 184307: hashtable_destroy(new_table, 0); We keep this as guard should code above change in the future */ } if (b->table == NULL) { if (survivor_table == NULL) { LogError(errno, RS_RET_INTERNAL_ERROR, "error trying to initialize " "ttl-survivor hash-table for dyn-stats bucket named: %s", b->name); } else { hashtable_destroy(survivor_table, 0); } } } RETiRet; } static rsRetVal dynstats_resetBucket(dynstats_bucket_t *b) { DEFiRet; pthread_rwlock_wrlock(&b->lock); CHKiRet(dynstats_rebuildSurvivorTable(b)); STATSCOUNTER_INC(b->ctrPurgeTriggered, b->mutCtrPurgeTriggered); timeoutComp(&b->metricCleanupTimeout, b->unusedMetricLife); finalize_it: pthread_rwlock_unlock(&b->lock); RETiRet; } static void dynstats_resetIfExpired(dynstats_bucket_t *b) { long timeout; pthread_rwlock_rdlock(&b->lock); timeout = timeoutVal(&b->metricCleanupTimeout); pthread_rwlock_unlock(&b->lock); if (timeout == 0) { LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO, "dynstats: bucket '%s' is being reset", b->name); dynstats_resetBucket(b); } } static void dynstats_readCallback(statsobj_t __attribute__((unused)) *ignore, void *b) { dynstats_buckets_t *bkts; bkts = &runConf->dynstats_buckets; pthread_rwlock_rdlock(&bkts->lock); dynstats_resetIfExpired((dynstats_bucket_t *) b); pthread_rwlock_unlock(&bkts->lock); } static rsRetVal dynstats_initNewBucketStats(dynstats_bucket_t *b) { DEFiRet; CHKiRet(statsobj.Construct(&b->stats)); CHKiRet(statsobj.SetOrigin(b->stats, UCHAR_CONSTANT("dynstats.bucket"))); CHKiRet(statsobj.SetName(b->stats, b->name)); CHKiRet(statsobj.SetReportingNamespace(b->stats, UCHAR_CONSTANT("values"))); statsobj.SetReadNotifier(b->stats, dynstats_readCallback, b); CHKiRet(statsobj.ConstructFinalize(b->stats)); finalize_it: RETiRet; } static rsRetVal dynstats_newBucket(const uchar* name, uint8_t resettable, uint32_t maxCardinality, uint32_t unusedMetricLife) { dynstats_bucket_t *b; dynstats_buckets_t *bkts; uint8_t lock_initialized, metric_count_mutex_initialized; pthread_rwlockattr_t bucket_lock_attr; DEFiRet; lock_initialized = metric_count_mutex_initialized = 0; b = NULL; bkts = &loadConf->dynstats_buckets; if (bkts->initialized) { CHKmalloc(b = calloc(1, sizeof(dynstats_bucket_t))); b->resettable = resettable; b->maxCardinality = maxCardinality; b->unusedMetricLife = 1000 * unusedMetricLife; CHKmalloc(b->name = ustrdup(name)); pthread_rwlockattr_init(&bucket_lock_attr); #ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP pthread_rwlockattr_setkind_np(&bucket_lock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); #endif pthread_rwlock_init(&b->lock, &bucket_lock_attr); lock_initialized = 1; pthread_mutex_init(&b->mutMetricCount, NULL); metric_count_mutex_initialized = 1; CHKiRet(dynstats_initNewBucketStats(b)); CHKiRet(dynstats_resetBucket(b)); CHKiRet(dynstats_addBucketMetrics(bkts, b, name)); pthread_rwlock_wrlock(&bkts->lock); if (bkts->list == NULL) { bkts->list = b; } else { b->next = bkts->list; bkts->list = b; } pthread_rwlock_unlock(&bkts->lock); } else { LogError(0, RS_RET_INTERNAL_ERROR, "dynstats: bucket creation failed, as " "global-initialization of buckets was unsuccessful"); ABORT_FINALIZE(RS_RET_INTERNAL_ERROR); } finalize_it: if (iRet != RS_RET_OK) { if (metric_count_mutex_initialized) { pthread_mutex_destroy(&b->mutMetricCount); } if (lock_initialized) { pthread_rwlock_destroy(&b->lock); } if (b != NULL) { dynstats_destroyBucket(bkts, b); } } RETiRet; } rsRetVal dynstats_processCnf(struct cnfobj *o) { struct cnfparamvals *pvals; short i; uchar *name = NULL; uint8_t resettable = DYNSTATS_DEFAULT_RESETTABILITY; uint32_t maxCardinality = DYNSTATS_DEFAULT_MAX_CARDINALITY; uint32_t unusedMetricLife = DYNSTATS_DEFAULT_UNUSED_METRIC_LIFE; DEFiRet; pvals = nvlstGetParams(o->nvlst, &modpblk, NULL); if(pvals == NULL) { ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } for(i = 0 ; i < modpblk.nParams ; ++i) { if(!pvals[i].bUsed) continue; if(!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_NAME)) { CHKmalloc(name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_RESETTABLE)) { resettable = (pvals[i].val.d.n != 0); } else if (!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_MAX_CARDINALITY)) { maxCardinality = (uint32_t) pvals[i].val.d.n; } else if (!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_UNUSED_METRIC_LIFE)) { unusedMetricLife = (uint32_t) pvals[i].val.d.n; } else { dbgprintf("dyn_stats: program error, non-handled " "param '%s'\n", modpblk.descr[i].name); } } if (name != NULL) { CHKiRet(dynstats_newBucket(name, resettable, maxCardinality, unusedMetricLife)); } finalize_it: free(name); cnfparamvalsDestruct(pvals, &modpblk); RETiRet; } rsRetVal dynstats_initCnf(dynstats_buckets_t *bkts) { DEFiRet; bkts->initialized = 0; bkts->list = NULL; CHKiRet(statsobj.Construct(&bkts->global_stats)); CHKiRet(statsobj.SetOrigin(bkts->global_stats, UCHAR_CONSTANT("dynstats"))); CHKiRet(statsobj.SetName(bkts->global_stats, UCHAR_CONSTANT("global"))); CHKiRet(statsobj.SetReportingNamespace(bkts->global_stats, UCHAR_CONSTANT("values"))); CHKiRet(statsobj.ConstructFinalize(bkts->global_stats)); pthread_rwlock_init(&bkts->lock, NULL); bkts->initialized = 1; finalize_it: if (iRet != RS_RET_OK) { statsobj.Destruct(&bkts->global_stats); } RETiRet; } void dynstats_destroyAllBuckets(void) { dynstats_buckets_t *bkts; dynstats_bucket_t *b; bkts = &runConf->dynstats_buckets; if (bkts->initialized) { pthread_rwlock_wrlock(&bkts->lock); while(1) { b = bkts->list; if (b == NULL) { break; } else { bkts->list = b->next; dynstats_destroyBucket(bkts, b); } } statsobj.Destruct(&bkts->global_stats); pthread_rwlock_unlock(&bkts->lock); pthread_rwlock_destroy(&bkts->lock); } } dynstats_bucket_t * dynstats_findBucket(const uchar* name) { dynstats_buckets_t *bkts; dynstats_bucket_t *b; bkts = &loadConf->dynstats_buckets; if (bkts->initialized) { pthread_rwlock_rdlock(&bkts->lock); b = bkts->list; while(b != NULL) { if (! ustrcmp(name, b->name)) { break; } b = b->next; } pthread_rwlock_unlock(&bkts->lock); } else { b = NULL; LogError(0, RS_RET_INTERNAL_ERROR, "dynstats: bucket lookup failed, as global-initialization " "of buckets was unsuccessful"); } return b; } static rsRetVal dynstats_createCtr(dynstats_bucket_t *b, const uchar* metric, dynstats_ctr_t **ctr) { DEFiRet; CHKmalloc(*ctr = calloc(1, sizeof(dynstats_ctr_t))); CHKmalloc((*ctr)->metric = ustrdup(metric)); STATSCOUNTER_INIT((*ctr)->ctr, (*ctr)->mutCtr); CHKiRet(statsobj.AddManagedCounter(b->stats, metric, ctrType_IntCtr, b->resettable ? CTR_FLAG_MUST_RESET : CTR_FLAG_NONE, &(*ctr)->ctr, &(*ctr)->pCtr, 0)); finalize_it: if (iRet != RS_RET_OK) { if ((*ctr) != NULL) { free((*ctr)->metric); free(*ctr); *ctr = NULL; } } RETiRet; } #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" /* TODO: how can we fix these warnings? */ #endif static rsRetVal dynstats_addNewCtr(dynstats_bucket_t *b, const uchar* metric, uint8_t doInitialIncrement) { dynstats_ctr_t *ctr; dynstats_ctr_t *found_ctr, *survivor_ctr, *effective_ctr; int created; uchar *copy_of_key = NULL; DEFiRet; created = 0; ctr = NULL; if ((unsigned) ATOMIC_FETCH_32BIT_unsigned(&b->metricCount, &b->mutMetricCount) >= b->maxCardinality) { ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } CHKiRet(dynstats_createCtr(b, metric, &ctr)); pthread_rwlock_wrlock(&b->lock); found_ctr = (dynstats_ctr_t*) hashtable_search(b->table, ctr->metric); if (found_ctr != NULL) { if (doInitialIncrement) { STATSCOUNTER_INC(found_ctr->ctr, found_ctr->mutCtr); } } else { copy_of_key = ustrdup(ctr->metric); if (copy_of_key != NULL) { survivor_ctr = (dynstats_ctr_t*) hashtable_search(b->survivor_table, ctr->metric); if (survivor_ctr == NULL) { effective_ctr = ctr; } else { effective_ctr = survivor_ctr; if (survivor_ctr->prev != NULL) { survivor_ctr->prev->next = survivor_ctr->next; } if (survivor_ctr->next != NULL) { survivor_ctr->next->prev = survivor_ctr->prev; } if (survivor_ctr == b->survivor_ctrs) { b->survivor_ctrs = survivor_ctr->next; } } if ((created = hashtable_insert(b->table, copy_of_key, effective_ctr))) { statsobj.AddPreCreatedCtr(b->stats, effective_ctr->pCtr); } } if (created) { if (b->ctrs != NULL) { b->ctrs->prev = effective_ctr; } effective_ctr->prev = NULL; effective_ctr->next = b->ctrs; b->ctrs = effective_ctr; if (doInitialIncrement) { STATSCOUNTER_INC(effective_ctr->ctr, effective_ctr->mutCtr); } } } pthread_rwlock_unlock(&b->lock); if (found_ctr != NULL) { //ignore } else if (created && (effective_ctr != survivor_ctr)) { ATOMIC_INC(&b->metricCount, &b->mutMetricCount); STATSCOUNTER_INC(b->ctrNewMetricAdd, b->mutCtrNewMetricAdd); } else if (! created) { if (copy_of_key != NULL) { free(copy_of_key); } ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } finalize_it: if (((! created) || (effective_ctr != ctr)) && (ctr != NULL)) { dynstats_destroyCtr(ctr); } RETiRet; } #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif rsRetVal dynstats_inc(dynstats_bucket_t *b, uchar* metric) { dynstats_ctr_t *ctr; DEFiRet; if (! GatherStats) { FINALIZE; } if (ustrlen(metric) == 0) { STATSCOUNTER_INC(b->ctrNoMetric, b->mutCtrNoMetric); FINALIZE; } if (pthread_rwlock_tryrdlock(&b->lock) == 0) { ctr = (dynstats_ctr_t *) hashtable_search(b->table, metric); if (ctr != NULL) { STATSCOUNTER_INC(ctr->ctr, ctr->mutCtr); } pthread_rwlock_unlock(&b->lock); } else { ABORT_FINALIZE(RS_RET_NOENTRY); } if (ctr == NULL) { CHKiRet(dynstats_addNewCtr(b, metric, 1)); } finalize_it: if (iRet != RS_RET_OK) { if (iRet == RS_RET_NOENTRY) { /* NOTE: this is not tested (because it requires very strong orchestration to guarantee contended lock for testing) */ STATSCOUNTER_INC(b->ctrOpsIgnored, b->mutCtrOpsIgnored); } else { STATSCOUNTER_INC(b->ctrOpsOverflow, b->mutCtrOpsOverflow); } } RETiRet; }