Compare commits
2 Commits
26560effb0
...
c187161829
Author | SHA1 | Date | |
---|---|---|---|
c187161829 | |||
63b636da7c |
@ -19,21 +19,17 @@ add_compile_options(-Wall -Wextra -pedantic -Werror -fno-omit-frame-pointer)
|
||||
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
OPTION (USE_OpenMP "Use OpenMP" ON)
|
||||
OPTION (USE_Threads "Use Threads" ON)
|
||||
|
||||
if (CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
add_compile_options(--coverage)
|
||||
add_link_options(--coverage)
|
||||
message( "Forcing OpenMP off for Debug build" )
|
||||
set( USE_OpenMP OFF )
|
||||
endif()
|
||||
|
||||
IF(USE_OpenMP)
|
||||
FIND_PACKAGE(OpenMP REQUIRED)
|
||||
IF(OPENMP_C_FOUND)
|
||||
add_compile_options(${OpenMP_C_FLAGS})
|
||||
add_link_options(${OpenMP_C_FLAGS})
|
||||
ENDIF()
|
||||
IF(USE_Threads)
|
||||
find_package(Threads REQUIRED)
|
||||
link_libraries(Threads::Threads)
|
||||
add_definitions(-DUSE_THREADS)
|
||||
ENDIF()
|
||||
|
||||
#jemalloc seems to be quite a bit faster
|
||||
|
@ -107,3 +107,7 @@ target_link_libraries(texture_map PRIVATE
|
||||
module_raytracer
|
||||
module_patterns
|
||||
module_shapes)
|
||||
|
||||
add_executable(metrics_histogram metrics_histogram.c)
|
||||
target_link_libraries(metrics_histogram PRIVATE
|
||||
module_utilities)
|
37
demo/metrics_histogram.c
Normal file
37
demo/metrics_histogram.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include "metrics.h"
|
||||
#include "exceptions.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
CEXCEPTION_T e;
|
||||
|
||||
static uint64_t hash_string(const char *name) {
|
||||
uint64_t hash = 5381;
|
||||
int c;
|
||||
while ((c = *name++)) {
|
||||
hash = ((hash << 5) + hash) + c;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
Try {
|
||||
METRICS_INIT_DEFAULT_REGISTRY();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
char name[20];
|
||||
sprintf(name, "test_metric_%d", i);
|
||||
uint64_t hash = hash_string(name) % 256;
|
||||
METRICS_HISTOGRAM_UPDATE("hash_string", hash);
|
||||
printf("Hashed String: %s -> %" PRIu64 "\n", name, hash);
|
||||
}
|
||||
|
||||
METRICS_Histogram h = METRICS_HISTOGRAM_VALUE("hash_string");
|
||||
printf("Histogram: min=%lu, max=%lu, count=%lu, mean=%f\n", h.min, h.max, h.count, h.mean);
|
||||
|
||||
} Catch(e) {
|
||||
printf("exception %i occurred\n", e);
|
||||
}
|
||||
METRICS_DESTROY_DEFAULT_REGISTRY();
|
||||
}
|
@ -11,12 +11,6 @@
|
||||
#include <math.h>
|
||||
#include <wavefrontobj.h>
|
||||
|
||||
#ifdef _OPENMP
|
||||
#include <omp.h>
|
||||
#else
|
||||
#define omp_get_max_threads() 1
|
||||
#endif
|
||||
|
||||
CEXCEPTION_T global_exception;
|
||||
static void build_world(WORLD_World* world) {
|
||||
TUPLES_Color red, green, blue;
|
||||
@ -61,11 +55,21 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
Try {
|
||||
LOGGER_log(LOGGER_INFO, "Building world...\n");
|
||||
TUPLES_Point* light_position = TUPLES_new_point(-10, 10, -10);
|
||||
TUPLES_Color* light_color = TUPLES_new_color(1, 1, 1);
|
||||
LIGHTS_Light* light = LIGHTS_new_pointlight(light_position, light_color);
|
||||
TUPLES_delete_all(light_position, light_color);
|
||||
TUPLES_Vector light_position, light_color;
|
||||
TUPLES_init_point(&light_position, -10, 10, -10);
|
||||
TUPLES_init_color(&light_color, 1, 1, 1);
|
||||
#if 0
|
||||
LIGHTS_Light* light = LIGHTS_new_pointlight(&light_position, &light_color);
|
||||
#else
|
||||
TUPLES_Vector v1, v2;
|
||||
TUPLES_init_vector(&v1, 4, 0, 0);
|
||||
TUPLES_init_vector(&v2, 0, 3, 0);
|
||||
|
||||
LIGHTS_Light* light = LIGHTS_new_arealight(&light_position, &v1, 8, &v2, 8, &light_color);
|
||||
SEQUENCES_Sequence* seq = SEQUENCES_new_random(4096);
|
||||
LIGHTS_set_jitter_on_area_light(light, seq);
|
||||
SEQUENCES_delete(seq);
|
||||
#endif
|
||||
CAMERA_Camera* camera = CAMERA_new(1920,1080, M_PI / 3.0);
|
||||
TUPLES_Point* from = TUPLES_new_point(0, 1.5, -5);
|
||||
TUPLES_Point* to = TUPLES_new_point(0, 1, 0);
|
||||
@ -103,7 +107,6 @@ int main(int argc, char *argv[]) {
|
||||
UTILITIES_Timer_Results divide_results = UTILITIES_Timer_stop(divide_timer);
|
||||
log_perf(divide_results, "Divide time: ");
|
||||
WORLD_add_object(world, obj_group);
|
||||
LOGGER_log(LOGGER_INFO, "Rendering with %i thread(s)...\n", omp_get_max_threads());
|
||||
UTILITIES_Timer* render_timer = UTILITIES_Timer_start();
|
||||
CANVAS_Canvas* canvas = CAMERA_render(camera, world);
|
||||
UTILITIES_Timer_Results render_results = UTILITIES_Timer_stop(render_timer);
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include <logger.h>
|
||||
#include <math.h>
|
||||
#include <memory.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
static void calculate_pixel_size(CAMERA_Camera *camera) {
|
||||
assert(camera);
|
||||
@ -106,35 +108,70 @@ void CAMERA_ray_for_pixel(RAY_Ray *dest, const CAMERA_Camera *camera, unsigned i
|
||||
RAY_init_from_tuples(dest, &origin, &direction);
|
||||
}
|
||||
|
||||
CANVAS_Canvas *CAMERA_render(const CAMERA_Camera *camera, const WORLD_World *world) {
|
||||
assert(camera);
|
||||
assert(world);
|
||||
CANVAS_Canvas *canvas = CANVAS_new(camera->hsize, camera->vsize);
|
||||
const uint total_pixels = camera->vsize * camera->hsize;
|
||||
const uint one_percent_pixels = total_pixels / 100;
|
||||
uint pixel_count = 0;
|
||||
#ifdef _OPENMP
|
||||
#pragma omp parallel for collapse(2) schedule(dynamic)
|
||||
#endif
|
||||
for (uint y = 0; y < camera->vsize - 1; y++) {
|
||||
for (uint x = 0; x < camera->hsize - 1; x++) {
|
||||
uint this_pixel_count;
|
||||
#ifdef _OPENMP
|
||||
#pragma omp atomic capture
|
||||
#endif
|
||||
this_pixel_count = pixel_count++;
|
||||
if (this_pixel_count % one_percent_pixels == 0) {
|
||||
const uint percent = this_pixel_count * 100 / total_pixels;
|
||||
LOGGER_log(LOGGER_INFO, "Rendering %u%% complete\n", percent);
|
||||
typedef struct {
|
||||
const CAMERA_Camera *camera;
|
||||
const WORLD_World *world;
|
||||
CANVAS_Canvas *canvas;
|
||||
uint thread_num;
|
||||
uint total_threads;
|
||||
atomic_uint pixels_processed;
|
||||
} render_thread_args;
|
||||
|
||||
static void *render_thread(void *args) {
|
||||
assert(args);
|
||||
render_thread_args *rta = (render_thread_args *)args;
|
||||
assert(rta->camera);
|
||||
assert(rta->world);
|
||||
for (uint y = 0; y < rta->camera->vsize; y++) {
|
||||
for (uint x = 0; x < rta->camera->hsize; x++) {
|
||||
const uint this_pixel = y * rta->camera->hsize + x;
|
||||
if (this_pixel % rta->total_threads != rta->thread_num) {
|
||||
continue;
|
||||
}
|
||||
RAY_Ray ray;
|
||||
TUPLES_Color color;
|
||||
|
||||
CAMERA_ray_for_pixel(&ray, camera, x, y);
|
||||
WORLD_color_at(&color, world, &ray, WORLD_default_ttl);
|
||||
CANVAS_write_pixel(canvas, x, y, &color);
|
||||
CAMERA_ray_for_pixel(&ray, rta->camera, x, y);
|
||||
WORLD_color_at(&color, rta->world, &ray, WORLD_default_ttl);
|
||||
CANVAS_write_pixel(rta->canvas, x, y, &color);
|
||||
rta->pixels_processed++;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CANVAS_Canvas *CAMERA_render(const CAMERA_Camera *camera, const WORLD_World *world) {
|
||||
assert(camera);
|
||||
assert(world);
|
||||
CANVAS_Canvas *canvas = CANVAS_new(camera->hsize, camera->vsize);
|
||||
long thread_count = UTILITIES_get_thread_count();
|
||||
LOGGER_log(LOGGER_INFO, "Rendering with %i thread(s)...\n", thread_count);
|
||||
pthread_t threads[thread_count];
|
||||
render_thread_args args[thread_count];
|
||||
for (uint i = 0; i < thread_count; i++) {
|
||||
args[i].camera = camera;
|
||||
args[i].world = world;
|
||||
args[i].canvas = canvas;
|
||||
args[i].thread_num = i;
|
||||
args[i].total_threads = thread_count;
|
||||
args[i].pixels_processed = 0;
|
||||
pthread_create(&threads[i], NULL, &render_thread, &args[i]);
|
||||
}
|
||||
|
||||
const uint total_pixels = camera->vsize * camera->hsize;
|
||||
uint pixel_count = 0;
|
||||
while (pixel_count != total_pixels) {
|
||||
pixel_count = 0;
|
||||
for (uint i = 0; i < thread_count; i++) {
|
||||
pixel_count += args[i].pixels_processed;
|
||||
}
|
||||
const uint percent = pixel_count * 100 / total_pixels;
|
||||
LOGGER_log(LOGGER_INFO, "Rendering %u%% complete (%u/%u)\n", percent, pixel_count, total_pixels);
|
||||
UTILITIES_msleep(500);
|
||||
}
|
||||
for (uint i = 0; i < thread_count; i++) {
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
return canvas;
|
||||
}
|
||||
|
||||
|
@ -5,18 +5,11 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <utilities.h>
|
||||
|
||||
#ifdef _OPENMP
|
||||
#include <omp.h>
|
||||
#else
|
||||
#define omp_get_num_threads() 1
|
||||
#define omp_get_thread_num() 0
|
||||
#define omp_get_max_threads() 1
|
||||
#endif
|
||||
#include <pthread.h>
|
||||
|
||||
typedef struct SEQUENCES_Sequence {
|
||||
unsigned int count;
|
||||
unsigned int *next_ndx;
|
||||
pthread_key_t tl_next_ndx;
|
||||
double seq[];
|
||||
} SEQUENCES_Sequence;
|
||||
|
||||
@ -28,28 +21,35 @@ SEQUENCES_Sequence *SEQUENCES_new(unsigned int count, double numbers[]) {
|
||||
}
|
||||
seq->count = count;
|
||||
memcpy(seq->seq, numbers, count * sizeof(numbers[0]));
|
||||
seq->next_ndx = calloc(omp_get_max_threads(), sizeof(unsigned int));
|
||||
|
||||
pthread_key_create(&seq->tl_next_ndx, free);
|
||||
return seq;
|
||||
}
|
||||
|
||||
void SEQUENCES_delete(SEQUENCES_Sequence *sequence) {
|
||||
assert(sequence);
|
||||
free(sequence->next_ndx);
|
||||
/* TODO: Is this needed? */
|
||||
size_t* ndx = pthread_getspecific(sequence->tl_next_ndx);
|
||||
if (ndx) {
|
||||
free(ndx);
|
||||
}
|
||||
pthread_key_delete(sequence->tl_next_ndx);
|
||||
free(sequence);
|
||||
}
|
||||
|
||||
double SEQUENCES_next(SEQUENCES_Sequence *sequence) {
|
||||
assert(sequence);
|
||||
int thread_id = omp_get_thread_num();
|
||||
unsigned int ndx = sequence->next_ndx[thread_id];
|
||||
sequence->next_ndx[thread_id] = (ndx + 1) % sequence->count;
|
||||
return sequence->seq[ndx];
|
||||
size_t* ndx = pthread_getspecific(sequence->tl_next_ndx);
|
||||
if (!ndx) {
|
||||
ndx = calloc(1, sizeof(size_t));
|
||||
pthread_setspecific(sequence->tl_next_ndx, ndx);
|
||||
}
|
||||
double val = sequence->seq[*ndx];
|
||||
*ndx = (*ndx + 1) % sequence->count;
|
||||
return val;
|
||||
}
|
||||
|
||||
SEQUENCES_Sequence *SEQUENCES_copy(SEQUENCES_Sequence *sequence) {
|
||||
SEQUENCES_Sequence *seq = SEQUENCES_new(sequence->count, sequence->seq);
|
||||
memcpy(seq->next_ndx, sequence->next_ndx, omp_get_max_threads());
|
||||
return seq;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
add_library(module_utilities STATIC
|
||||
exceptions.h exceptions.c
|
||||
logger.c logger.h utilities.h utilities.c)
|
||||
exceptions.c exceptions.h
|
||||
logger.c logger.h
|
||||
utilities.c utilities.h
|
||||
metrics.c metrics.h)
|
||||
|
||||
target_include_directories(module_utilities PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
@ -9,4 +11,5 @@ target_include_directories(module_utilities PUBLIC
|
||||
|
||||
target_link_libraries(module_utilities
|
||||
CException
|
||||
m
|
||||
)
|
||||
|
@ -8,7 +8,6 @@ typedef enum LOGGER_LEVEL { LOGGER_ERROR, LOGGER_WARN, LOGGER_INFO, LOGGER_DEBUG
|
||||
* @param level A member of the LogLevel enum used to colorize terminal output
|
||||
* @param msg The null terminated string
|
||||
*/
|
||||
//void LOGGER_log(LOGGER_LEVEL level, const char *fmt, ...);
|
||||
void LOGGER_log_with_file_line(LOGGER_LEVEL level, char *file, int line, const char *fmt, ...);
|
||||
|
||||
#ifdef __clang__
|
||||
|
349
module_utilities/metrics.c
Normal file
349
module_utilities/metrics.c
Normal file
@ -0,0 +1,349 @@
|
||||
#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;
|
||||
}
|
||||
}
|
82
module_utilities/metrics.h
Normal file
82
module_utilities/metrics.h
Normal file
@ -0,0 +1,82 @@
|
||||
#ifndef SIMPLE_RAYTRACER_METRICS_H
|
||||
#define SIMPLE_RAYTRACER_METRICS_H
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* - [X] Add registry for metrics
|
||||
* - [ ] Add metric types:
|
||||
* - [X] Counter - incrementing and decrementing
|
||||
* - [X] Gauge - measure an instantaneous value
|
||||
* - [ ] Meter - measure a rate of events
|
||||
* - [ ] Timer - measure the duration of an event
|
||||
* - [X] Histogram - measure the distribution of values
|
||||
* - [ ] Add reporter implementation
|
||||
* - [X] Add default registry
|
||||
* - [ ] Improve histogram - double instead of long? Add percentiles? Options for sliding window vs. cumulative?
|
||||
*/
|
||||
|
||||
|
||||
typedef struct METRICS_Registry METRICS_Registry;
|
||||
METRICS_Registry *METRICS_Registry_new(void);
|
||||
void METRICS_Registry_delete(METRICS_Registry *registry);
|
||||
|
||||
/**
|
||||
* Increments the counter by 1
|
||||
* @param registry
|
||||
* @param name
|
||||
* @return pre-increment value
|
||||
*/
|
||||
long METRICS_Counter_inc(METRICS_Registry *registry, const char *name);
|
||||
|
||||
/**
|
||||
* Decrements the counter by 1
|
||||
* @param registry
|
||||
* @param name
|
||||
* @return pre-decrement value
|
||||
*/
|
||||
long METRICS_Counter_dec(METRICS_Registry *registry, const char *name);
|
||||
|
||||
/**
|
||||
* Returns the current value of the counter
|
||||
* @param registry
|
||||
* @param name
|
||||
* @return
|
||||
*/
|
||||
long METRICS_Counter_value(METRICS_Registry *registry, const char *name);
|
||||
|
||||
long METRICS_Gauge_value(METRICS_Registry *registry, const char *name);
|
||||
typedef long (*METRICS_Gauge_measure)(void *state);
|
||||
void METRICS_Gauge_register(METRICS_Registry *registry, const char *name, METRICS_Gauge_measure measure, void *state);
|
||||
|
||||
typedef struct METRICS_Histogram {
|
||||
long min, max, count;
|
||||
double mean;
|
||||
} METRICS_Histogram;
|
||||
|
||||
void METRICS_Histogram_update(METRICS_Registry *registry, const char *name, long value);
|
||||
METRICS_Histogram METRICS_Histogram_value(METRICS_Registry *registry, const char *name);
|
||||
|
||||
void METRICS_Timer_start(METRICS_Registry *registry, const char *name);
|
||||
void METRICS_Timer_stop(METRICS_Registry *registry, const char *name);
|
||||
|
||||
/**
|
||||
* Dumps metrics to stdout, generally for debugging purposes
|
||||
* @param registry
|
||||
*/
|
||||
void METRICS_dump_registry(METRICS_Registry *registry);
|
||||
|
||||
/* Helpers for default registry */
|
||||
extern METRICS_Registry *METRICS_DEFAULT_REGISTRY;
|
||||
#define METRICS_INIT_DEFAULT_REGISTRY() METRICS_DEFAULT_REGISTRY = METRICS_Registry_new()
|
||||
#define METRICS_DESTROY_DEFAULT_REGISTRY() METRICS_Registry_delete(METRICS_DEFAULT_REGISTRY)
|
||||
#define METRICS_COUNTER_INC(name) METRICS_Counter_inc(METRICS_DEFAULT_REGISTRY, (name))
|
||||
#define METRICS_COUNTER_DEC(name) METRICS_Counter_dec(METRICS_DEFAULT_REGISTRY, (name))
|
||||
#define METRICS_COUNTER_VALUE(name) METRICS_Counter_value(METRICS_DEFAULT_REGISTRY, (name))
|
||||
#define METRICS_GAUGE_REGISTER(name, measure, state) METRICS_Gauge_register(METRICS_DEFAULT_REGISTRY, (name), (measure), (state))
|
||||
#define METRICS_GAUGE_VALUE(name) METRICS_Gauge_value(METRICS_DEFAULT_REGISTRY, (name))
|
||||
#define METRICS_HISTOGRAM_UPDATE(name, value) METRICS_Histogram_update(METRICS_DEFAULT_REGISTRY, (name), (value))
|
||||
#define METRICS_HISTOGRAM_VALUE(name) METRICS_Histogram_value(METRICS_DEFAULT_REGISTRY, (name))
|
||||
|
||||
|
||||
|
||||
#endif // SIMPLE_RAYTRACER_METRICS_H
|
@ -1,11 +1,13 @@
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/times.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/times.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "exceptions.h"
|
||||
#include "utilities.h"
|
||||
@ -142,3 +144,25 @@ char *UTILITIES_slurp(char *input_filename) {
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
long UTILITIES_get_thread_count(void) {
|
||||
return sysconf(_SC_NPROCESSORS_ONLN);
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/questions/1157209/is-there-an-alternative-sleep-function-in-c-to-milliseconds */
|
||||
void UTILITIES_msleep(long msec)
|
||||
{
|
||||
if (msec < 0) {
|
||||
Throw(E_INVALID_ARGUMENT);
|
||||
return;
|
||||
}
|
||||
|
||||
struct timespec ts;
|
||||
ts.tv_sec = msec / 1000;
|
||||
ts.tv_nsec = (msec % 1000) * 1000000;
|
||||
|
||||
int res;
|
||||
do {
|
||||
res = nanosleep(&ts, &ts);
|
||||
} while (res && errno == EINTR);
|
||||
}
|
||||
|
@ -76,4 +76,7 @@ UTILITIES_Timer_Results UTILITIES_Timer_stop(UTILITIES_Timer *timer);
|
||||
|
||||
char *UTILITIES_slurp(char *input_filename);
|
||||
|
||||
long UTILITIES_get_thread_count(void);
|
||||
void UTILITIES_msleep(long milliseconds);
|
||||
|
||||
#endif // UTILITIES_UTILITIES_H
|
||||
|
@ -11,3 +11,10 @@ target_link_libraries(module_utilities_utilities
|
||||
module_utilities
|
||||
Unity)
|
||||
add_test(utilities_utilities module_utilities_utilities)
|
||||
|
||||
add_executable(module_utilities_metrics
|
||||
test_metrics.c)
|
||||
target_link_libraries(module_utilities_metrics
|
||||
module_utilities
|
||||
Unity)
|
||||
add_test(utilities_metrics module_utilities_metrics)
|
147
test/module_utilities/test_metrics.c
Normal file
147
test/module_utilities/test_metrics.c
Normal file
@ -0,0 +1,147 @@
|
||||
#include <unity.h>
|
||||
|
||||
#include "exceptions.h"
|
||||
#include "metrics.h"
|
||||
#include "utilities.h"
|
||||
|
||||
CEXCEPTION_T e;
|
||||
|
||||
METRICS_Registry *registry;
|
||||
void setUp(void) { registry = METRICS_Registry_new(); }
|
||||
void tearDown(void) { METRICS_Registry_delete(registry); }
|
||||
|
||||
void test_create_registry(void) { TEST_ASSERT_NOT_NULL(registry); }
|
||||
|
||||
void test_default_counter_value(void) { TEST_ASSERT_EQUAL(0, METRICS_Counter_value(registry, "test_counter")); }
|
||||
|
||||
void test_increment_counter(void) {
|
||||
METRICS_Counter_inc(registry, "test_counter");
|
||||
TEST_ASSERT_EQUAL(1, METRICS_Counter_value(registry, "test_counter"));
|
||||
}
|
||||
|
||||
void test_decrement_counter(void) {
|
||||
METRICS_Counter_dec(registry, "test_counter");
|
||||
TEST_ASSERT_EQUAL(-1, METRICS_Counter_value(registry, "test_counter"));
|
||||
}
|
||||
|
||||
void test_inc_dec_counter(void) {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
METRICS_Counter_inc(registry, "test_counter");
|
||||
}
|
||||
TEST_ASSERT_EQUAL(5, METRICS_Counter_value(registry, "test_counter"));
|
||||
for (int i = 0; i < 10; i++) {
|
||||
METRICS_Counter_dec(registry, "test_counter");
|
||||
}
|
||||
TEST_ASSERT_EQUAL(-5, METRICS_Counter_value(registry, "test_counter"));
|
||||
}
|
||||
|
||||
long get_value_2(void *state) {
|
||||
UNUSED(state);
|
||||
return 2;
|
||||
}
|
||||
|
||||
void test_get_gauge_value(void) {
|
||||
METRICS_Gauge_register(registry, "test_gauge", get_value_2, NULL);
|
||||
TEST_ASSERT_EQUAL(2, METRICS_Gauge_value(registry, "test_gauge"));
|
||||
}
|
||||
|
||||
void test_gauge_register_should_throw_exception_when_use_name_for_two_different_metric_types(void) {
|
||||
METRICS_Counter_inc(registry, "test_metric");
|
||||
bool exception_thrown = false;
|
||||
Try { METRICS_Gauge_register(registry, "test_metric", get_value_2, NULL); }
|
||||
Catch(e) { exception_thrown = true; }
|
||||
TEST_ASSERT_TRUE(exception_thrown);
|
||||
}
|
||||
|
||||
void test_getting_unregistered_gauge_value_should_throw_exception(void) {
|
||||
bool exception_thrown = false;
|
||||
Try { METRICS_Gauge_value(registry, "test_gauge"); }
|
||||
Catch(e) { exception_thrown = true; }
|
||||
TEST_ASSERT_TRUE(exception_thrown);
|
||||
}
|
||||
|
||||
void test_adding_more_than_initial_registry_size_metrics(void) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
char name[20];
|
||||
sprintf(name, "test_metric_%d", i);
|
||||
METRICS_Counter_inc(registry, name);
|
||||
}
|
||||
METRICS_dump_registry(registry);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
char name[20];
|
||||
sprintf(name, "test_metric_%d", i);
|
||||
TEST_ASSERT_EQUAL(1, METRICS_Counter_value(registry, name));
|
||||
}
|
||||
}
|
||||
|
||||
void test_initialize_default_registry(void) {
|
||||
METRICS_INIT_DEFAULT_REGISTRY();
|
||||
TEST_ASSERT_NOT_NULL(METRICS_DEFAULT_REGISTRY);
|
||||
METRICS_DESTROY_DEFAULT_REGISTRY();
|
||||
}
|
||||
|
||||
void test_inc_and_dec_counter_in_default_registry(void) {
|
||||
METRICS_INIT_DEFAULT_REGISTRY();
|
||||
METRICS_COUNTER_INC("test_counter");
|
||||
TEST_ASSERT_EQUAL(1, METRICS_COUNTER_VALUE("test_counter"));
|
||||
METRICS_COUNTER_DEC("test_counter");
|
||||
TEST_ASSERT_EQUAL(0, METRICS_COUNTER_VALUE("test_counter"));
|
||||
METRICS_DESTROY_DEFAULT_REGISTRY();
|
||||
}
|
||||
|
||||
void test_gauge_in_default_registry(void) {
|
||||
METRICS_INIT_DEFAULT_REGISTRY();
|
||||
METRICS_GAUGE_REGISTER("test_gauge", get_value_2, NULL);
|
||||
TEST_ASSERT_EQUAL(2, METRICS_GAUGE_VALUE("test_gauge"));
|
||||
METRICS_DESTROY_DEFAULT_REGISTRY();
|
||||
}
|
||||
|
||||
void test_update_histogram(void) {
|
||||
METRICS_Histogram_update(registry, "test_histogram", 1);
|
||||
METRICS_Histogram_update(registry, "test_histogram", 2);
|
||||
}
|
||||
|
||||
void test_get_histogram_value(void) {
|
||||
METRICS_Histogram_update(registry, "test_histogram", 1);
|
||||
METRICS_Histogram_update(registry, "test_histogram", 3);
|
||||
METRICS_Histogram_update(registry, "test_histogram", 5);
|
||||
METRICS_Histogram_update(registry, "test_histogram", 7);
|
||||
METRICS_Histogram histogram = METRICS_Histogram_value(registry, "test_histogram");
|
||||
TEST_ASSERT_EQUAL(1, histogram.min);
|
||||
TEST_ASSERT_EQUAL(7, histogram.max);
|
||||
TEST_ASSERT_EQUAL(4, histogram.count);
|
||||
TEST_ASSERT_EQUAL(4, histogram.mean);
|
||||
}
|
||||
|
||||
void test_histogram_with_default_registry(void) {
|
||||
METRICS_INIT_DEFAULT_REGISTRY();
|
||||
METRICS_HISTOGRAM_UPDATE("test_histogram", 1);
|
||||
METRICS_HISTOGRAM_UPDATE("test_histogram", 3);
|
||||
METRICS_HISTOGRAM_UPDATE("test_histogram", 5);
|
||||
METRICS_HISTOGRAM_UPDATE("test_histogram", 7);
|
||||
METRICS_Histogram histogram = METRICS_HISTOGRAM_VALUE("test_histogram");
|
||||
TEST_ASSERT_EQUAL(1, histogram.min);
|
||||
TEST_ASSERT_EQUAL(7, histogram.max);
|
||||
TEST_ASSERT_EQUAL(4, histogram.count);
|
||||
TEST_ASSERT_EQUAL(4, histogram.mean);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_create_registry);
|
||||
RUN_TEST(test_default_counter_value);
|
||||
RUN_TEST(test_increment_counter);
|
||||
RUN_TEST(test_decrement_counter);
|
||||
RUN_TEST(test_inc_dec_counter);
|
||||
RUN_TEST(test_get_gauge_value);
|
||||
RUN_TEST(test_gauge_register_should_throw_exception_when_use_name_for_two_different_metric_types);
|
||||
RUN_TEST(test_getting_unregistered_gauge_value_should_throw_exception);
|
||||
RUN_TEST(test_adding_more_than_initial_registry_size_metrics);
|
||||
RUN_TEST(test_initialize_default_registry);
|
||||
RUN_TEST(test_inc_and_dec_counter_in_default_registry);
|
||||
RUN_TEST(test_gauge_in_default_registry);
|
||||
RUN_TEST(test_update_histogram);
|
||||
RUN_TEST(test_get_histogram_value);
|
||||
RUN_TEST(test_histogram_with_default_registry);
|
||||
return UNITY_END();
|
||||
}
|
Reference in New Issue
Block a user