- services/framecache/: new standalone container
- slot.h/slot.c: shm ring buffer (120 frames, FC_MAGIC header, atomic
write_cursor, POSIX semaphore per slot)
- registry.h/registry.c: in-memory slot registry + /dev/shm/framecache/
registry.json persistence
- framecache.c: HTTP API server (libmicrohttpd, port 7435)
POST /slots, GET /slots, GET /slots/:id, DELETE /slots/:id, GET /health
- fc_client.h/fc_client.c: consumer library — fc_consumer_open/read/close
with per-consumer cursor, timeout via sem_timedwait, automatic skip+count
when consumer falls behind writer by > ring_depth frames
- fc_test_consumer.c: dev utility to attach to any slot and print fps/stats
- CMakeLists.txt: framecache server + fc_client static lib + test consumer
- Dockerfile: builder + slim runtime stages
- docker-compose.worker.yml: add framecache service (profile: capture,
ipc: host, shm_size from FC_SHM_SIZE_GB env var, healthcheck)
- .env.example: document FC_SHM_SIZE_GB with per-node guidance
108 lines
2.9 KiB
C
108 lines
2.9 KiB
C
/**
|
|
* registry.c — In-memory slot registry + JSON persistence.
|
|
*/
|
|
#include "registry.h"
|
|
#include "slot.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
fc_registry_entry_t g_registry[FC_MAX_SLOTS];
|
|
int g_registry_count = 0;
|
|
|
|
static const char *REGISTRY_JSON = "/dev/shm/framecache/registry.json";
|
|
|
|
void registry_add(struct fc_slot *slot)
|
|
{
|
|
for (int i = 0; i < FC_MAX_SLOTS; i++) {
|
|
if (!g_registry[i].active) {
|
|
g_registry[i].active = 1;
|
|
g_registry[i].slot = slot;
|
|
strncpy(g_registry[i].slot_id, fc_slot_id(slot),
|
|
FC_MAX_SLOT_ID - 1);
|
|
g_registry_count++;
|
|
registry_write_json();
|
|
return;
|
|
}
|
|
}
|
|
fprintf(stderr, "[framecache] registry full (%d slots)\n", FC_MAX_SLOTS);
|
|
}
|
|
|
|
void registry_remove(const char *slot_id)
|
|
{
|
|
for (int i = 0; i < FC_MAX_SLOTS; i++) {
|
|
if (g_registry[i].active &&
|
|
strncmp(g_registry[i].slot_id, slot_id, FC_MAX_SLOT_ID) == 0)
|
|
{
|
|
g_registry[i].active = 0;
|
|
g_registry[i].slot = NULL;
|
|
g_registry[i].slot_id[0] = '\0';
|
|
g_registry_count--;
|
|
registry_write_json();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct fc_slot *registry_find(const char *slot_id)
|
|
{
|
|
for (int i = 0; i < FC_MAX_SLOTS; i++) {
|
|
if (g_registry[i].active &&
|
|
strncmp(g_registry[i].slot_id, slot_id, FC_MAX_SLOT_ID) == 0)
|
|
{
|
|
return g_registry[i].slot;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void registry_write_json(void)
|
|
{
|
|
FILE *f = fopen(REGISTRY_JSON, "w");
|
|
if (!f) return;
|
|
|
|
fprintf(f, "{\n \"version\": 1,\n \"slots\": {\n");
|
|
|
|
int first = 1;
|
|
for (int i = 0; i < FC_MAX_SLOTS; i++) {
|
|
if (!g_registry[i].active) continue;
|
|
fc_header_t *hdr = fc_slot_header(g_registry[i].slot);
|
|
|
|
char ts[32];
|
|
time_t now = time(NULL);
|
|
struct tm *t = gmtime(&now);
|
|
strftime(ts, sizeof ts, "%Y-%m-%dT%H:%M:%SZ", t);
|
|
|
|
if (!first) fprintf(f, ",\n");
|
|
first = 0;
|
|
|
|
fprintf(f,
|
|
" \"%s\": {\n"
|
|
" \"shm_path\": \"%s\",\n"
|
|
" \"sem_name\": \"%s\",\n"
|
|
" \"width\": %u,\n"
|
|
" \"height\": %u,\n"
|
|
" \"fps_num\": %u,\n"
|
|
" \"fps_den\": %u,\n"
|
|
" \"pixel_format\": \"UYVY422\",\n"
|
|
" \"source_type\": \"%s\",\n"
|
|
" \"frame_size\": %u,\n"
|
|
" \"ring_depth\": %u,\n"
|
|
" \"created_at\": \"%s\"\n"
|
|
" }",
|
|
g_registry[i].slot_id,
|
|
fc_slot_shm_path(g_registry[i].slot),
|
|
fc_slot_sem_name(g_registry[i].slot),
|
|
hdr->width, hdr->height,
|
|
hdr->fps_num, hdr->fps_den,
|
|
hdr->source_type,
|
|
hdr->frame_size,
|
|
hdr->ring_depth,
|
|
ts
|
|
);
|
|
}
|
|
|
|
fprintf(f, "\n }\n}\n");
|
|
fclose(f);
|
|
}
|