raytracer-c/module_utilities/metrics.c
2023-12-10 02:10:20 -05:00

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 &registry->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(&registry->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(&registry->metric[index])) {
init_entry(registry, &registry->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(&registry->metric[i])) {
func(&registry->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;
}
}