350 lines
9.4 KiB
C
350 lines
9.4 KiB
C
#include "metrics.h"
|
|
#include "exceptions.h"
|
|
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define INITIAL_REGISTRY_CAPACITY 16
|
|
#define MAX_LOAD_FACTOR 0.75
|
|
|
|
METRICS_Registry *METRICS_DEFAULT_REGISTRY = NULL;
|
|
|
|
typedef enum MetricType { COUNTER, GAUGE, TIMER, HISTOGRAM } MetricType;
|
|
|
|
typedef struct METRICS_Counter {
|
|
long value;
|
|
} METRICS_Counter;
|
|
|
|
typedef struct METRICS_Gauge {
|
|
METRICS_Gauge_measure measure;
|
|
void *state;
|
|
} METRICS_Gauge;
|
|
|
|
typedef struct Registry_Entry {
|
|
char *name;
|
|
uint64_t hash;
|
|
MetricType type;
|
|
union {
|
|
METRICS_Counter counter;
|
|
METRICS_Gauge gauge;
|
|
// METRICS_Timer timer;
|
|
METRICS_Histogram histogram;
|
|
};
|
|
} Registry_Entry;
|
|
|
|
struct METRICS_Registry {
|
|
Registry_Entry *metric;
|
|
size_t capacity;
|
|
size_t count;
|
|
};
|
|
|
|
/* Forward declarations */
|
|
static Registry_Entry *find(METRICS_Registry *registry, const char *name, uint64_t hash);
|
|
static void init_counter_entry(METRICS_Registry *registry, Registry_Entry **entry, const char *name);
|
|
static void initialize_name(Registry_Entry *entry, const char *name);
|
|
static uint64_t hash_string(const char *name);
|
|
static bool check_load_factor(const METRICS_Registry *registry);
|
|
static void for_each(METRICS_Registry *registry, void (*func)(Registry_Entry *));
|
|
static void print_entry(Registry_Entry *entry);
|
|
|
|
void METRICS_dump_registry(METRICS_Registry *registry) {
|
|
assert(registry);
|
|
printf("Size: %zu\n", registry->count);
|
|
printf("Capacity: %zu\n", registry->capacity);
|
|
printf("Metrics:\n");
|
|
for_each(registry, print_entry);
|
|
}
|
|
|
|
static bool is_empty_entry(const Registry_Entry *entry) {
|
|
assert(entry);
|
|
return !entry->name;
|
|
}
|
|
|
|
long METRICS_Counter_inc(METRICS_Registry *registry, const char *name) {
|
|
assert(registry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
long value;
|
|
{
|
|
Registry_Entry *entry = find(registry, name, hash);
|
|
if (is_empty_entry(entry)) {
|
|
init_counter_entry(registry, &entry, name);
|
|
}
|
|
assert(entry->type == COUNTER);
|
|
value = entry->counter.value++;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
long METRICS_Counter_dec(METRICS_Registry *registry, const char *name) {
|
|
assert(registry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
long value;
|
|
{
|
|
Registry_Entry *entry = find(registry, name, hash);
|
|
if (is_empty_entry(entry)) {
|
|
init_counter_entry(registry, &entry, name);
|
|
}
|
|
assert(entry->type == COUNTER);
|
|
value = entry->counter.value--;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
long METRICS_Counter_value(METRICS_Registry *registry, const char *name) {
|
|
assert(registry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
long value;
|
|
{
|
|
Registry_Entry *entry = find(registry, name, hash);
|
|
if (is_empty_entry(entry)) {
|
|
init_counter_entry(registry, &entry, name);
|
|
}
|
|
assert(entry->type == COUNTER);
|
|
value = entry->counter.value;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
long METRICS_Gauge_value(METRICS_Registry *registry, const char *name) {
|
|
assert(registry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
long value = 0;
|
|
bool found = false;
|
|
{
|
|
Registry_Entry *entry = find(registry, name, hash);
|
|
found = !is_empty_entry(entry) && entry->type == GAUGE;
|
|
|
|
if (found) {
|
|
value = entry->gauge.measure(entry->gauge.state);
|
|
}
|
|
}
|
|
if (!found) {
|
|
Throw(E_INVALID_ARGUMENT);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
METRICS_Registry *METRICS_Registry_new(void) {
|
|
METRICS_Registry *registry = calloc(1, sizeof(METRICS_Registry));
|
|
if (!registry) {
|
|
Throw(E_MALLOC_FAILED);
|
|
return NULL;
|
|
}
|
|
registry->metric = calloc(INITIAL_REGISTRY_CAPACITY, sizeof(Registry_Entry));
|
|
if (!registry->metric) {
|
|
free(registry);
|
|
Throw(E_MALLOC_FAILED);
|
|
return NULL;
|
|
}
|
|
registry->capacity = INITIAL_REGISTRY_CAPACITY;
|
|
return registry;
|
|
}
|
|
|
|
void METRICS_Registry_delete(METRICS_Registry *registry) {
|
|
assert(registry);
|
|
assert(registry->metric);
|
|
free(registry->metric);
|
|
free(registry);
|
|
}
|
|
|
|
static uint64_t hash_string(const char *name) {
|
|
assert(name);
|
|
uint64_t hash = 5381;
|
|
int c;
|
|
while ((c = *name++)) {
|
|
hash = ((hash << 5) + hash) + c;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
static size_t calculate_index(Registry_Entry *entry, size_t capacity, const char *name, uint64_t hash) {
|
|
assert(entry);
|
|
assert(name);
|
|
assert(capacity > 0);
|
|
size_t index = hash % capacity;
|
|
while (!is_empty_entry(&entry[index])) {
|
|
if (entry[index].hash == hash && strcmp(entry[index].name, name) == 0) {
|
|
/* found the entry */
|
|
return index;
|
|
}
|
|
index = (index + 1) % capacity;
|
|
}
|
|
/* found an empty entry */
|
|
return index;
|
|
}
|
|
|
|
static void initialize_name(Registry_Entry *entry, const char *name) {
|
|
assert(entry);
|
|
assert(name);
|
|
entry->name = strdup(name);
|
|
if (!entry->name) {
|
|
Throw(E_MALLOC_FAILED);
|
|
}
|
|
}
|
|
|
|
static Registry_Entry *find(METRICS_Registry *registry, const char *name, uint64_t hash) {
|
|
assert(registry);
|
|
assert(name);
|
|
size_t index = calculate_index(registry->metric, registry->capacity, name, hash);
|
|
return ®istry->metric[index];
|
|
}
|
|
|
|
static void init_entry(METRICS_Registry *registry, Registry_Entry *entry, MetricType type, const char *name, uint64_t hash) {
|
|
assert(registry);
|
|
assert(entry);
|
|
assert(name);
|
|
entry->type = type;
|
|
initialize_name(entry, name);
|
|
registry->count++;
|
|
entry->hash = hash;
|
|
}
|
|
|
|
static bool check_load_factor(const METRICS_Registry *registry) {
|
|
assert(registry);
|
|
return registry->count + 1 > registry->capacity * MAX_LOAD_FACTOR;
|
|
}
|
|
|
|
static void expand_map(METRICS_Registry *registry) {
|
|
assert(registry);
|
|
if (check_load_factor(registry)) {
|
|
size_t new_capacity = registry->capacity * 2;
|
|
Registry_Entry *new_metric = calloc(new_capacity, sizeof(Registry_Entry));
|
|
if (!new_metric) {
|
|
Throw(E_MALLOC_FAILED);
|
|
}
|
|
for (size_t i = 0; i < registry->capacity; i++) {
|
|
if (!is_empty_entry(®istry->metric[i])) {
|
|
uint64_t hash = hash_string(registry->metric[i].name);
|
|
size_t index = calculate_index(new_metric, new_capacity, registry->metric[i].name, hash);
|
|
new_metric[index] = registry->metric[i];
|
|
}
|
|
}
|
|
free(registry->metric);
|
|
registry->metric = new_metric;
|
|
registry->capacity = new_capacity;
|
|
}
|
|
}
|
|
|
|
static void init_counter_entry(METRICS_Registry *registry, Registry_Entry **entry, const char *name) {
|
|
assert(registry);
|
|
assert(entry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
expand_map(registry);
|
|
*entry = find(registry, name, hash);
|
|
init_entry(registry, *entry, COUNTER, name, hash);
|
|
(*entry)->counter.value = 0;
|
|
}
|
|
|
|
static void init_histogram_entry(METRICS_Registry *registry, Registry_Entry **entry, const char *name) {
|
|
assert(registry);
|
|
assert(entry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
expand_map(registry);
|
|
*entry = find(registry, name, hash);
|
|
init_entry(registry, *entry, HISTOGRAM, name, hash);
|
|
(*entry)->histogram.min = 0;
|
|
(*entry)->histogram.max = 0;
|
|
(*entry)->histogram.count = 0;
|
|
(*entry)->histogram.mean = 0;
|
|
}
|
|
|
|
void METRICS_Histogram_update(METRICS_Registry *registry, const char *name, long value) {
|
|
assert(registry);
|
|
assert(name);
|
|
uint64_t hash = hash_string(name);
|
|
{
|
|
Registry_Entry *entry = find(registry, name, hash);
|
|
if (is_empty_entry(entry)) {
|
|
init_histogram_entry(registry, &entry, name);
|
|
}
|
|
assert(entry->type == HISTOGRAM);
|
|
if (entry->histogram.count == 0) {
|
|
entry->histogram.min = value;
|
|
entry->histogram.max = value;
|
|
entry->histogram.mean = value;
|
|
} else {
|
|
if (value < entry->histogram.min) {
|
|
entry->histogram.min = value;
|
|
}
|
|
if (value > entry->histogram.max) {
|
|
entry->histogram.max = value;
|
|
}
|
|
entry->histogram.mean = (entry->histogram.mean * entry->histogram.count + value) / (entry->histogram.count + 1);
|
|
}
|
|
entry->histogram.count++;
|
|
}
|
|
}
|
|
|
|
METRICS_Histogram METRICS_Histogram_value(METRICS_Registry *registry, const char *name) {
|
|
assert(registry);
|
|
assert(name);
|
|
METRICS_Histogram histogram;
|
|
uint64_t hash = hash_string(name);
|
|
{
|
|
Registry_Entry *entry = find(registry, name, hash);
|
|
if (is_empty_entry(entry)) {
|
|
init_histogram_entry(registry, &entry, name);
|
|
}
|
|
assert(entry->type == HISTOGRAM);
|
|
histogram = entry->histogram;
|
|
}
|
|
return histogram;
|
|
}
|
|
|
|
void METRICS_Gauge_register(METRICS_Registry *registry, const char *name, METRICS_Gauge_measure measure, void *state) {
|
|
assert(registry);
|
|
assert(name);
|
|
assert(measure);
|
|
uint64_t hash = hash_string(name);
|
|
bool exists = false;
|
|
{
|
|
expand_map(registry);
|
|
size_t index = calculate_index(registry->metric, registry->capacity, name, hash);
|
|
if (is_empty_entry(®istry->metric[index])) {
|
|
init_entry(registry, ®istry->metric[index], GAUGE, name, hash);
|
|
registry->metric[index].gauge.measure = measure;
|
|
registry->metric[index].gauge.state = state;
|
|
} else {
|
|
exists = true;
|
|
}
|
|
}
|
|
if (exists) {
|
|
Throw(E_INVALID_ARGUMENT);
|
|
}
|
|
}
|
|
|
|
static void for_each(METRICS_Registry *registry, void (*func)(Registry_Entry *)) {
|
|
assert(registry);
|
|
assert(func);
|
|
for (size_t i = 0; i < registry->capacity; i++) {
|
|
if (!is_empty_entry(®istry->metric[i])) {
|
|
func(®istry->metric[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_entry(Registry_Entry *entry) {
|
|
assert(entry);
|
|
switch (entry->type) {
|
|
case COUNTER:
|
|
printf("%s: %ld\n", entry->name, entry->counter.value);
|
|
break;
|
|
case GAUGE:
|
|
printf("%s: %ld\n", entry->name, entry->gauge.measure(entry->gauge.state));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|