aprsc/src/counterdata.c

275 lines
5.8 KiB
C

/*
* aprsc
*
* (c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
*
* This program is licensed under the BSD license, which can be found
* in the file LICENSE.
*
*
* The counterdata module stores periodic samples of counter values
* to facilitate calculating average traffic levels and do some
* fancy bells and whistles on the status page.
*/
#include "ac-hdrs.h"
#include <pthread.h>
#include <string.h>
#include <errno.h>
#include "counterdata.h"
#include "config.h"
#include "hmalloc.h"
#include "worker.h"
#include "hlog.h"
struct cdata_t {
struct cdata_t *next;
struct cdata_t **prevp;
pthread_mutex_t mt;
char *name;
long long last_raw_value;
time_t times[CDATA_SAMPLES];
long long values[CDATA_SAMPLES];
int last_index;
int is_gauge;
};
struct cdata_t *counterdata = NULL;
pthread_mutex_t counterdata_mt = PTHREAD_MUTEX_INITIALIZER;
/*
* allocation and freeing of cdata structures
*/
struct cdata_t *cdata_alloc(const char *name)
{
int e;
struct cdata_t *cd;
cd = hmalloc(sizeof(*cd));
memset(cd, 0, sizeof(*cd));
pthread_mutex_init(&cd->mt, NULL);
cd->name = hstrdup(name);
cd->last_index = -1; // no data inserted yet
cd->is_gauge = 0;
if ((e = pthread_mutex_lock(&counterdata_mt))) {
hlog(LOG_CRIT, "cdata_allocate: failed to lock counterdata_mt: %s", strerror(e));
exit(1);
}
cd->next = counterdata;
if (counterdata)
counterdata->prevp = &cd->next;
counterdata = cd;
cd->prevp = &counterdata;
if ((e = pthread_mutex_unlock(&counterdata_mt))) {
hlog(LOG_CRIT, "cdata_allocate: could not unlock counterdata_mt: %s", strerror(e));
exit(1);
}
//hlog(LOG_DEBUG, "cdata: allocated: %s", cd->name);
return cd;
}
void cdata_free(struct cdata_t *cd)
{
if (cd->next)
cd->next->prevp = cd->prevp;
*cd->prevp = cd->next;
hfree(cd->name);
hfree(cd);
}
/*
* Find a cdata element by name and lock it for use
*/
static struct cdata_t *cdata_find_and_lock(const char *name)
{
struct cdata_t *cd = NULL;
int e;
if ((e = pthread_mutex_lock(&counterdata_mt))) {
hlog(LOG_CRIT, "cdata_find_and_lock: failed to lock counterdata_mt: %s", strerror(e));
exit(1);
}
for (cd = counterdata; (cd); cd = cd->next)
if (strcmp(name, cd->name) == 0) {
if ((e = pthread_mutex_lock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_find_and_lock: could not lock cd: %s", strerror(e));
exit(1);
}
break;
}
if ((e = pthread_mutex_unlock(&counterdata_mt))) {
hlog(LOG_CRIT, "cdata_find_and_lock: could not unlock counterdata_mt: %s", strerror(e));
exit(1);
}
return cd;
}
/*
* Add a new data sample to a counter-watching cdata
*/
void cdata_counter_sample(struct cdata_t *cd, long long value)
{
int e;
long long l;
if ((e = pthread_mutex_lock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_counter_sample %s: failed to lock mt: %s", cd->name, strerror(e));
exit(1);
}
cd->last_index++;
if (cd->last_index >= CDATA_SAMPLES)
cd->last_index = 0;
/* calculate counter's increment and insert */
if (value == -1) {
/* no data for sample */
l = -1;
} else {
/* check for wrap-around */
if (value < cd->last_raw_value)
l = -1;
else
l = value - cd->last_raw_value;
}
cd->last_raw_value = value;
cd->values[cd->last_index] = l;
cd->times[cd->last_index] = tick;
if ((e = pthread_mutex_unlock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_counter_sample %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
exit(1);
}
}
/*
* Add a new sample to a gauge-watching cdata
*/
void cdata_gauge_sample(struct cdata_t *cd, long long value)
{
int e;
if ((e = pthread_mutex_lock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_gauge_sample %s: failed to lock mt: %s", cd->name, strerror(e));
exit(1);
}
cd->last_index++;
if (cd->last_index >= CDATA_SAMPLES)
cd->last_index = 0;
/* just insert the gauge */
cd->last_raw_value = value;
cd->values[cd->last_index] = value;
cd->times[cd->last_index] = tick;
cd->is_gauge = 1;
if ((e = pthread_mutex_unlock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_gauge_sample %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
exit(1);
}
}
/*
* Get the last calculated value of a cdata
*/
long cdata_get_last_value(const char *name)
{
int e;
long v;
struct cdata_t *cd;
cd = cdata_find_and_lock(name);
if (!cd)
return -1;
if (cd->last_index < 0) {
v = -1;
} else {
v = cd->values[cd->last_index];
}
if ((e = pthread_mutex_unlock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_get_last_value %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
exit(1);
}
return v;
}
/*
* Return a JSON string containing the contents of a cdata array.
* Allocated on the heap, needs to be freed by the caller.
* This is called by the http service to provide graph data for the
* web UI.
*/
char *cdata_json_string(const char *name)
{
struct cdata_t *cd;
char *out = NULL;
int i, e;
cd = cdata_find_and_lock(name);
if (!cd)
return NULL;
cJSON *root = cJSON_CreateObject();
cJSON *values = cJSON_CreateArray();
if (cd->is_gauge)
cJSON_AddNumberToObject(root, "gauge", 1);
cJSON_AddNumberToObject(root, "interval", CDATA_INTERVAL);
cJSON_AddItemToObject(root, "values", values);
if (cd->last_index >= 0) {
i = cd->last_index + 1;
do {
if (i == CDATA_SAMPLES)
i = 0;
//hlog(LOG_DEBUG, "cdata_json_string, sample %d", i);
if (cd->times[i] > 0) {
cJSON *val = cJSON_CreateArray();
cJSON_AddItemToArray(val, cJSON_CreateNumber(cd->times[i]));
cJSON_AddItemToArray(val, cJSON_CreateNumber(cd->values[i]));
cJSON_AddItemToArray(values, val);
}
if (i == cd->last_index)
break;
i ++;
} while (1);
}
if ((e = pthread_mutex_unlock(&cd->mt))) {
hlog(LOG_CRIT, "cdata_get_last_value %s: could not unlock counterdata_mt: %s", cd->name, strerror(e));
exit(1);
}
out = cJSON_Print(root);
cJSON_Delete(root);
return out;
}