Files
PixelDefense/engine/libs/flecs/src/addons/rest.c

1133 lines
35 KiB
C

/**
* @file addons/rest.c
* @brief Rest addon.
*/
#include "../private_api.h"
#ifdef FLECS_REST
static ECS_TAG_DECLARE(EcsRestPlecs);
typedef struct {
ecs_world_t *world;
ecs_http_server_t *srv;
int32_t rc;
} ecs_rest_ctx_t;
/* Global statistics */
int64_t ecs_rest_request_count = 0;
int64_t ecs_rest_entity_count = 0;
int64_t ecs_rest_entity_error_count = 0;
int64_t ecs_rest_query_count = 0;
int64_t ecs_rest_query_error_count = 0;
int64_t ecs_rest_query_name_count = 0;
int64_t ecs_rest_query_name_error_count = 0;
int64_t ecs_rest_query_name_from_cache_count = 0;
int64_t ecs_rest_enable_count = 0;
int64_t ecs_rest_enable_error_count = 0;
int64_t ecs_rest_delete_count = 0;
int64_t ecs_rest_delete_error_count = 0;
int64_t ecs_rest_world_stats_count = 0;
int64_t ecs_rest_pipeline_stats_count = 0;
int64_t ecs_rest_stats_error_count = 0;
static ECS_COPY(EcsRest, dst, src, {
ecs_rest_ctx_t *impl = src->impl;
if (impl) {
impl->rc ++;
}
ecs_os_strset(&dst->ipaddr, src->ipaddr);
dst->port = src->port;
dst->impl = impl;
})
static ECS_MOVE(EcsRest, dst, src, {
*dst = *src;
src->ipaddr = NULL;
src->impl = NULL;
})
static ECS_DTOR(EcsRest, ptr, {
ecs_rest_ctx_t *impl = ptr->impl;
if (impl) {
impl->rc --;
if (!impl->rc) {
ecs_http_server_fini(impl->srv);
ecs_os_free(impl);
}
}
ecs_os_free(ptr->ipaddr);
})
static char *rest_last_err;
static ecs_os_api_log_t rest_prev_log;
static
void flecs_rest_capture_log(
int32_t level,
const char *file,
int32_t line,
const char *msg)
{
(void)file; (void)line;
if (level < 0) {
if (rest_prev_log) {
// Also log to previous log function
ecs_log_enable_colors(true);
rest_prev_log(level, file, line, msg);
ecs_log_enable_colors(false);
}
}
if (!rest_last_err && level < 0) {
rest_last_err = ecs_os_strdup(msg);
}
}
static
char* flecs_rest_get_captured_log(void) {
char *result = rest_last_err;
rest_last_err = NULL;
return result;
}
static
void flecs_reply_verror(
ecs_http_reply_t *reply,
const char *fmt,
va_list args)
{
ecs_strbuf_appendlit(&reply->body, "{\"error\":\"");
ecs_strbuf_vappend(&reply->body, fmt, args);
ecs_strbuf_appendlit(&reply->body, "\"}");
}
static
void flecs_reply_error(
ecs_http_reply_t *reply,
const char *fmt,
...)
{
va_list args;
va_start(args, fmt);
flecs_reply_verror(reply, fmt, args);
va_end(args);
}
static
void flecs_rest_bool_param(
const ecs_http_request_t *req,
const char *name,
bool *value_out)
{
const char *value = ecs_http_get_param(req, name);
if (value) {
if (!ecs_os_strcmp(value, "true")) {
value_out[0] = true;
} else {
value_out[0] = false;
}
}
}
static
void flecs_rest_int_param(
const ecs_http_request_t *req,
const char *name,
int32_t *value_out)
{
const char *value = ecs_http_get_param(req, name);
if (value) {
*value_out = atoi(value);
}
}
static
void flecs_rest_string_param(
const ecs_http_request_t *req,
const char *name,
char **value_out)
{
const char *value = ecs_http_get_param(req, name);
if (value) {
*value_out = ECS_CONST_CAST(char*, value);
}
}
static
void flecs_rest_parse_json_ser_entity_params(
ecs_world_t *world,
ecs_entity_to_json_desc_t *desc,
const ecs_http_request_t *req)
{
flecs_rest_bool_param(req, "path", &desc->serialize_path);
flecs_rest_bool_param(req, "label", &desc->serialize_label);
flecs_rest_bool_param(req, "brief", &desc->serialize_brief);
flecs_rest_bool_param(req, "link", &desc->serialize_link);
flecs_rest_bool_param(req, "color", &desc->serialize_color);
flecs_rest_bool_param(req, "ids", &desc->serialize_ids);
flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels);
flecs_rest_bool_param(req, "base", &desc->serialize_base);
flecs_rest_bool_param(req, "values", &desc->serialize_values);
flecs_rest_bool_param(req, "private", &desc->serialize_private);
flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info);
flecs_rest_bool_param(req, "matches", &desc->serialize_matches);
flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts);
char *rel = NULL;
flecs_rest_string_param(req, "refs", &rel);
if (rel) {
desc->serialize_refs = ecs_lookup_fullpath(world, rel);
}
}
static
void flecs_rest_parse_json_ser_iter_params(
ecs_iter_to_json_desc_t *desc,
const ecs_http_request_t *req)
{
flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids);
flecs_rest_bool_param(req, "term_labels", &desc->serialize_term_labels);
flecs_rest_bool_param(req, "ids", &desc->serialize_ids);
flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels);
flecs_rest_bool_param(req, "sources", &desc->serialize_sources);
flecs_rest_bool_param(req, "variables", &desc->serialize_variables);
flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set);
flecs_rest_bool_param(req, "values", &desc->serialize_values);
flecs_rest_bool_param(req, "private", &desc->serialize_private);
flecs_rest_bool_param(req, "entities", &desc->serialize_entities);
flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels);
flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels);
flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids);
flecs_rest_bool_param(req, "colors", &desc->serialize_colors);
flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration);
flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info);
flecs_rest_bool_param(req, "table", &desc->serialize_table);
}
static
bool flecs_rest_reply_entity(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
char *path = &req->path[7];
ecs_dbg_2("rest: request entity '%s'", path);
ecs_os_linc(&ecs_rest_entity_count);
ecs_entity_t e = ecs_lookup_path_w_sep(
world, 0, path, "/", NULL, false);
if (!e) {
ecs_dbg_2("rest: entity '%s' not found", path);
flecs_reply_error(reply, "entity '%s' not found", path);
reply->code = 404;
ecs_os_linc(&ecs_rest_entity_error_count);
return true;
}
ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT;
flecs_rest_parse_json_ser_entity_params(world, &desc, req);
if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) {
ecs_strbuf_reset(&reply->body);
reply->code = 500;
reply->status = "Internal server error";
return true;
}
return true;
}
static
bool flecs_rest_reply_world(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
(void)req;
if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) {
ecs_strbuf_reset(&reply->body);
reply->code = 500;
reply->status = "Internal server error";
return true;
}
return true;
}
static
ecs_entity_t flecs_rest_entity_from_path(
ecs_world_t *world,
ecs_http_reply_t *reply,
const char *path)
{
ecs_entity_t e = ecs_lookup_path_w_sep(
world, 0, path, "/", NULL, false);
if (!e) {
flecs_reply_error(reply, "entity '%s' not found", path);
reply->code = 404;
}
return e;
}
static
bool flecs_rest_set(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply,
const char *path)
{
ecs_entity_t e;
if (!(e = flecs_rest_entity_from_path(world, reply, path))) {
return true;
}
const char *data = ecs_http_get_param(req, "data");
ecs_from_json_desc_t desc = {0};
desc.expr = data;
desc.name = path;
if (ecs_entity_from_json(world, e, data, &desc) == NULL) {
flecs_reply_error(reply, "invalid request");
reply->code = 400;
return true;
}
return true;
}
static
bool flecs_rest_delete(
ecs_world_t *world,
ecs_http_reply_t *reply,
const char *path)
{
ecs_entity_t e;
if (!(e = flecs_rest_entity_from_path(world, reply, path))) {
ecs_os_linc(&ecs_rest_delete_error_count);
return true;
}
ecs_delete(world, e);
return true;
}
static
bool flecs_rest_enable(
ecs_world_t *world,
ecs_http_reply_t *reply,
const char *path,
bool enable)
{
ecs_os_linc(&ecs_rest_enable_count);
ecs_entity_t e;
if (!(e = flecs_rest_entity_from_path(world, reply, path))) {
ecs_os_linc(&ecs_rest_enable_error_count);
return true;
}
ecs_enable(world, e, enable);
return true;
}
static
bool flecs_rest_script(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
(void)world;
(void)req;
(void)reply;
#ifdef FLECS_PLECS
const char *data = ecs_http_get_param(req, "data");
if (!data) {
flecs_reply_error(reply, "missing data parameter");
return true;
}
bool prev_color = ecs_log_enable_colors(false);
rest_prev_log = ecs_os_api.log_;
ecs_os_api.log_ = flecs_rest_capture_log;
ecs_entity_t script = ecs_script(world, {
.entity = ecs_entity(world, { .name = "scripts.main" }),
.str = data
});
if (!script) {
char *err = flecs_rest_get_captured_log();
char *escaped_err = ecs_astresc('"', err);
flecs_reply_error(reply, escaped_err);
ecs_os_linc(&ecs_rest_query_error_count);
reply->code = 400; /* bad request */
ecs_os_free(escaped_err);
ecs_os_free(err);
}
ecs_os_api.log_ = rest_prev_log;
ecs_log_enable_colors(prev_color);
return true;
#else
return false;
#endif
}
static
void flecs_rest_reply_set_captured_log(
ecs_http_reply_t *reply)
{
char *err = flecs_rest_get_captured_log();
if (err) {
char *escaped_err = ecs_astresc('"', err);
flecs_reply_error(reply, escaped_err);
reply->code = 400;
ecs_os_free(escaped_err);
ecs_os_free(err);
}
}
static
int flecs_rest_iter_to_reply(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply,
ecs_iter_t *it)
{
ecs_iter_to_json_desc_t desc = {0};
desc.serialize_entities = true;
desc.serialize_variables = true;
flecs_rest_parse_json_ser_iter_params(&desc, req);
int32_t offset = 0;
int32_t limit = 1000;
flecs_rest_int_param(req, "offset", &offset);
flecs_rest_int_param(req, "limit", &limit);
if (offset < 0 || limit < 0) {
flecs_reply_error(reply, "invalid offset/limit parameter");
reply->code = 400;
return -1;
}
ecs_iter_t pit = ecs_page_iter(it, offset, limit);
if (ecs_iter_to_json_buf(world, &pit, &reply->body, &desc)) {
flecs_rest_reply_set_captured_log(reply);
return -1;
}
return 0;
}
static
bool flecs_rest_reply_existing_query(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply,
const char *name)
{
ecs_os_linc(&ecs_rest_query_name_count);
ecs_entity_t q = ecs_lookup_fullpath(world, name);
if (!q) {
flecs_reply_error(reply, "unresolved identifier '%s'", name);
reply->code = 404;
ecs_os_linc(&ecs_rest_query_name_error_count);
return true;
}
const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery);
if (!poly) {
flecs_reply_error(reply,
"resolved identifier '%s' is not a query", name);
reply->code = 400;
ecs_os_linc(&ecs_rest_query_name_error_count);
return true;
}
ecs_iter_t it;
ecs_iter_poly(world, poly->poly, &it, NULL);
ecs_dbg_2("rest: request query '%s'", q);
bool prev_color = ecs_log_enable_colors(false);
rest_prev_log = ecs_os_api.log_;
ecs_os_api.log_ = flecs_rest_capture_log;
const char *vars = ecs_http_get_param(req, "vars");
if (vars) {
if (!ecs_poly_is(poly->poly, ecs_rule_t)) {
flecs_reply_error(reply,
"variables are only supported for rule queries");
reply->code = 400;
ecs_os_linc(&ecs_rest_query_name_error_count);
return true;
}
if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) {
flecs_rest_reply_set_captured_log(reply);
ecs_os_linc(&ecs_rest_query_name_error_count);
return true;
}
}
flecs_rest_iter_to_reply(world, req, reply, &it);
ecs_os_api.log_ = rest_prev_log;
ecs_log_enable_colors(prev_color);
return true;
}
static
bool flecs_rest_reply_query(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
const char *q_name = ecs_http_get_param(req, "name");
if (q_name) {
return flecs_rest_reply_existing_query(world, req, reply, q_name);
}
ecs_os_linc(&ecs_rest_query_count);
const char *q = ecs_http_get_param(req, "q");
if (!q) {
ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'");
reply->code = 400; /* bad request */
ecs_os_linc(&ecs_rest_query_error_count);
return true;
}
ecs_dbg_2("rest: request query '%s'", q);
bool prev_color = ecs_log_enable_colors(false);
rest_prev_log = ecs_os_api.log_;
ecs_os_api.log_ = flecs_rest_capture_log;
ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){
.expr = q
});
if (!r) {
flecs_rest_reply_set_captured_log(reply);
ecs_os_linc(&ecs_rest_query_error_count);
} else {
ecs_iter_t it = ecs_rule_iter(world, r);
flecs_rest_iter_to_reply(world, req, reply, &it);
ecs_rule_fini(r);
}
ecs_os_api.log_ = rest_prev_log;
ecs_log_enable_colors(prev_color);
return true;
}
#ifdef FLECS_MONITOR
static
void flecs_rest_array_append_(
ecs_strbuf_t *reply,
const char *field,
int32_t field_len,
const ecs_float_t *values,
int32_t t)
{
ecs_strbuf_list_appendch(reply, '"');
ecs_strbuf_appendstrn(reply, field, field_len);
ecs_strbuf_appendlit(reply, "\":");
ecs_strbuf_list_push(reply, "[", ",");
int32_t i;
for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) {
int32_t index = i % ECS_STAT_WINDOW;
ecs_strbuf_list_next(reply);
ecs_strbuf_appendflt(reply, (double)values[index], '"');
}
ecs_strbuf_list_pop(reply, "]");
}
#define flecs_rest_array_append(reply, field, values, t)\
flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t)
static
void flecs_rest_gauge_append(
ecs_strbuf_t *reply,
const ecs_metric_t *m,
const char *field,
int32_t field_len,
int32_t t,
const char *brief,
int32_t brief_len)
{
ecs_strbuf_list_appendch(reply, '"');
ecs_strbuf_appendstrn(reply, field, field_len);
ecs_strbuf_appendlit(reply, "\":");
ecs_strbuf_list_push(reply, "{", ",");
flecs_rest_array_append(reply, "avg", m->gauge.avg, t);
flecs_rest_array_append(reply, "min", m->gauge.min, t);
flecs_rest_array_append(reply, "max", m->gauge.max, t);
if (brief) {
ecs_strbuf_list_appendlit(reply, "\"brief\":\"");
ecs_strbuf_appendstrn(reply, brief, brief_len);
ecs_strbuf_appendch(reply, '"');
}
ecs_strbuf_list_pop(reply, "}");
}
static
void flecs_rest_counter_append(
ecs_strbuf_t *reply,
const ecs_metric_t *m,
const char *field,
int32_t field_len,
int32_t t,
const char *brief,
int32_t brief_len)
{
flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len);
}
#define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\
flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1)
#define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\
flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1)
#define ECS_GAUGE_APPEND(reply, s, field, brief)\
ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief)
#define ECS_COUNTER_APPEND(reply, s, field, brief)\
ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief)
static
void flecs_world_stats_to_json(
ecs_strbuf_t *reply,
const EcsWorldStats *monitor_stats)
{
const ecs_world_stats_t *stats = &monitor_stats->stats;
ecs_strbuf_list_push(reply, "{", ",");
ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world");
ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world");
ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second");
ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame");
ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame");
ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame");
ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame");
ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame");
ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.get_mut_count, "Get_mut commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed");
ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities");
ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands");
ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched");
ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)");
ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)");
ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame");
ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame");
ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame");
ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations");
ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)");
ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world");
ECS_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags");
ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)");
ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices");
ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables");
ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created");
ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted");
ECS_GAUGE_APPEND(reply, stats, ids.count, "Component, tag and pair ids in use");
ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use");
ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use");
ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use");
ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use");
ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types");
ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created");
ECS_COUNTER_APPEND(reply, stats, ids.delete_count, "Number of component, pair and tag ids deleted");
ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world");
ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world");
ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world");
ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API");
ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API");
ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API");
ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API");
ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators");
ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators");
ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations");
ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators");
ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators");
ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations");
ECS_COUNTER_APPEND(reply, stats, rest.request_count, "Received requests");
ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache");
ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests");
ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests");
ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests");
ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests");
ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests");
ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests");
ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully");
ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code");
ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)");
ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received");
ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies");
ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies");
ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)");
ecs_strbuf_list_pop(reply, "}");
}
static
void flecs_system_stats_to_json(
ecs_world_t *world,
ecs_strbuf_t *reply,
ecs_entity_t system,
const ecs_system_stats_t *stats)
{
ecs_strbuf_list_push(reply, "{", ",");
ecs_strbuf_list_appendlit(reply, "\"name\":\"");
ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply);
ecs_strbuf_appendch(reply, '"');
if (!stats->task) {
ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, "");
ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, "");
}
ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, "");
ecs_strbuf_list_pop(reply, "}");
}
static
void flecs_pipeline_stats_to_json(
ecs_world_t *world,
ecs_strbuf_t *reply,
const EcsPipelineStats *stats)
{
ecs_strbuf_list_push(reply, "[", ",");
int32_t i, count = ecs_vec_count(&stats->stats.systems), sync_cur = 0;
ecs_entity_t *ids = ecs_vec_first_t(&stats->stats.systems, ecs_entity_t);
for (i = 0; i < count; i ++) {
ecs_entity_t id = ids[i];
ecs_strbuf_list_next(reply);
if (id) {
ecs_system_stats_t *sys_stats = ecs_map_get_deref(
&stats->stats.system_stats, ecs_system_stats_t, id);
flecs_system_stats_to_json(world, reply, id, sys_stats);
} else {
/* Sync point */
ecs_strbuf_list_push(reply, "{", ",");
ecs_sync_stats_t *sync_stats = ecs_vec_get_t(
&stats->stats.sync_points, ecs_sync_stats_t, sync_cur);
ecs_strbuf_list_appendlit(reply, "\"system_count\":");
ecs_strbuf_appendint(reply, sync_stats->system_count);
ecs_strbuf_list_appendlit(reply, "\"multi_threaded\":");
ecs_strbuf_appendbool(reply, sync_stats->multi_threaded);
ecs_strbuf_list_appendlit(reply, "\"no_readonly\":");
ecs_strbuf_appendbool(reply, sync_stats->no_readonly);
ECS_GAUGE_APPEND_T(reply, sync_stats,
time_spent, stats->stats.t, "");
ECS_GAUGE_APPEND_T(reply, sync_stats,
commands_enqueued, stats->stats.t, "");
ecs_strbuf_list_pop(reply, "}");
sync_cur ++;
}
}
ecs_strbuf_list_pop(reply, "]");
}
static
bool flecs_rest_reply_stats(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
char *period_str = NULL;
flecs_rest_string_param(req, "period", &period_str);
char *category = &req->path[6];
ecs_entity_t period = EcsPeriod1s;
if (period_str) {
char *period_name = ecs_asprintf("Period%s", period_str);
period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name);
ecs_os_free(period_name);
if (!period) {
flecs_reply_error(reply, "bad request (invalid period string)");
reply->code = 400;
ecs_os_linc(&ecs_rest_stats_error_count);
return false;
}
}
if (!ecs_os_strcmp(category, "world")) {
const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld,
EcsWorldStats, period);
flecs_world_stats_to_json(&reply->body, stats);
ecs_os_linc(&ecs_rest_world_stats_count);
return true;
} else if (!ecs_os_strcmp(category, "pipeline")) {
const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld,
EcsPipelineStats, period);
flecs_pipeline_stats_to_json(world, &reply->body, stats);
ecs_os_linc(&ecs_rest_pipeline_stats_count);
return true;
} else {
flecs_reply_error(reply, "bad request (unsupported category)");
reply->code = 400;
ecs_os_linc(&ecs_rest_stats_error_count);
return false;
}
}
#else
static
bool flecs_rest_reply_stats(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
(void)world;
(void)req;
(void)reply;
return false;
}
#endif
static
void flecs_rest_reply_table_append_type(
ecs_world_t *world,
ecs_strbuf_t *reply,
const ecs_table_t *table)
{
ecs_strbuf_list_push(reply, "[", ",");
int32_t i, count = table->type.count;
ecs_id_t *ids = table->type.array;
for (i = 0; i < count; i ++) {
ecs_strbuf_list_next(reply);
ecs_strbuf_appendch(reply, '"');
ecs_id_str_buf(world, ids[i], reply);
ecs_strbuf_appendch(reply, '"');
}
ecs_strbuf_list_pop(reply, "]");
}
static
void flecs_rest_reply_table_append_memory(
ecs_strbuf_t *reply,
const ecs_table_t *table)
{
int32_t used = 0, allocated = 0;
used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t);
allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t);
int32_t i, storage_count = table->column_count;
ecs_column_t *columns = table->data.columns;
for (i = 0; i < storage_count; i ++) {
used += columns[i].data.count * columns[i].ti->size;
allocated += columns[i].data.size * columns[i].ti->size;
}
ecs_strbuf_list_push(reply, "{", ",");
ecs_strbuf_list_append(reply, "\"used\":%d", used);
ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated);
ecs_strbuf_list_pop(reply, "}");
}
static
void flecs_rest_reply_table_append(
ecs_world_t *world,
ecs_strbuf_t *reply,
const ecs_table_t *table)
{
ecs_strbuf_list_next(reply);
ecs_strbuf_list_push(reply, "{", ",");
ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id);
ecs_strbuf_list_appendstr(reply, "\"type\":");
flecs_rest_reply_table_append_type(world, reply, table);
ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table));
ecs_strbuf_list_append(reply, "\"memory\":");
flecs_rest_reply_table_append_memory(reply, table);
ecs_strbuf_list_pop(reply, "}");
}
static
bool flecs_rest_reply_tables(
ecs_world_t *world,
const ecs_http_request_t* req,
ecs_http_reply_t *reply)
{
(void)req;
ecs_strbuf_list_push(&reply->body, "[", ",");
ecs_sparse_t *tables = &world->store.tables;
int32_t i, count = flecs_sparse_count(tables);
for (i = 0; i < count; i ++) {
ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i);
flecs_rest_reply_table_append(world, &reply->body, table);
}
ecs_strbuf_list_pop(&reply->body, "]");
return true;
}
static
bool flecs_rest_reply(
const ecs_http_request_t* req,
ecs_http_reply_t *reply,
void *ctx)
{
ecs_rest_ctx_t *impl = ctx;
ecs_world_t *world = impl->world;
ecs_os_linc(&ecs_rest_request_count);
if (req->path == NULL) {
ecs_dbg("rest: bad request (missing path)");
flecs_reply_error(reply, "bad request (missing path)");
reply->code = 400;
return false;
}
if (req->method == EcsHttpGet) {
/* Entity endpoint */
if (!ecs_os_strncmp(req->path, "entity/", 7)) {
return flecs_rest_reply_entity(world, req, reply);
/* Query endpoint */
} else if (!ecs_os_strcmp(req->path, "query")) {
return flecs_rest_reply_query(world, req, reply);
/* World endpoint */
} else if (!ecs_os_strcmp(req->path, "world")) {
return flecs_rest_reply_world(world, req, reply);
/* Stats endpoint */
} else if (!ecs_os_strncmp(req->path, "stats/", 6)) {
return flecs_rest_reply_stats(world, req, reply);
/* Tables endpoint */
} else if (!ecs_os_strncmp(req->path, "tables", 6)) {
return flecs_rest_reply_tables(world, req, reply);
}
} else if (req->method == EcsHttpPut) {
/* Set endpoint */
if (!ecs_os_strncmp(req->path, "set/", 4)) {
return flecs_rest_set(world, req, reply, &req->path[4]);
/* Delete endpoint */
} else if (!ecs_os_strncmp(req->path, "delete/", 7)) {
return flecs_rest_delete(world, reply, &req->path[7]);
/* Enable endpoint */
} else if (!ecs_os_strncmp(req->path, "enable/", 7)) {
return flecs_rest_enable(world, reply, &req->path[7], true);
/* Disable endpoint */
} else if (!ecs_os_strncmp(req->path, "disable/", 8)) {
return flecs_rest_enable(world, reply, &req->path[8], false);
/* Script endpoint */
} else if (!ecs_os_strncmp(req->path, "script", 6)) {
return flecs_rest_script(world, req, reply);
}
}
return false;
}
ecs_http_server_t* ecs_rest_server_init(
ecs_world_t *world,
const ecs_http_server_desc_t *desc)
{
ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t);
ecs_http_server_desc_t private_desc = {0};
if (desc) {
private_desc = *desc;
}
private_desc.callback = flecs_rest_reply;
private_desc.ctx = srv_ctx;
ecs_http_server_t *srv = ecs_http_server_init(&private_desc);
if (!srv) {
ecs_os_free(srv_ctx);
return NULL;
}
srv_ctx->world = world;
srv_ctx->srv = srv;
srv_ctx->rc = 1;
srv_ctx->srv = srv;
return srv;
}
void ecs_rest_server_fini(
ecs_http_server_t *srv)
{
ecs_rest_ctx_t *srv_ctx = ecs_http_server_ctx(srv);
ecs_os_free(srv_ctx);
ecs_http_server_fini(srv);
}
static
void flecs_on_set_rest(ecs_iter_t *it) {
EcsRest *rest = it->ptrs[0];
int i;
for(i = 0; i < it->count; i ++) {
if (!rest[i].port) {
rest[i].port = ECS_REST_DEFAULT_PORT;
}
ecs_http_server_t *srv = ecs_rest_server_init(it->real_world,
&(ecs_http_server_desc_t){
.ipaddr = rest[i].ipaddr,
.port = rest[i].port
});
if (!srv) {
const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0";
ecs_err("failed to create REST server on %s:%u",
ipaddr, rest[i].port);
continue;
}
rest[i].impl = ecs_http_server_ctx(srv);
ecs_http_server_start(srv);
}
}
static
void DequeueRest(ecs_iter_t *it) {
EcsRest *rest = ecs_field(it, EcsRest, 1);
if (it->delta_system_time > (ecs_ftime_t)1.0) {
ecs_warn(
"detected large progress interval (%.2fs), REST request may timeout",
(double)it->delta_system_time);
}
int32_t i;
for(i = 0; i < it->count; i ++) {
ecs_rest_ctx_t *ctx = rest[i].impl;
if (ctx) {
ecs_http_server_dequeue(ctx->srv, it->delta_time);
}
}
}
static
void DisableRest(ecs_iter_t *it) {
ecs_world_t *world = it->world;
ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){
.id = ecs_id(EcsRest),
.src.flags = EcsSelf
});
if (it->event == EcsOnAdd) {
/* REST module was disabled */
while (ecs_term_next(&rit)) {
EcsRest *rest = ecs_field(&rit, EcsRest, 1);
int i;
for (i = 0; i < rit.count; i ++) {
ecs_rest_ctx_t *ctx = rest[i].impl;
ecs_http_server_stop(ctx->srv);
}
}
} else if (it->event == EcsOnRemove) {
/* REST module was enabled */
while (ecs_term_next(&rit)) {
EcsRest *rest = ecs_field(&rit, EcsRest, 1);
int i;
for (i = 0; i < rit.count; i ++) {
ecs_rest_ctx_t *ctx = rest[i].impl;
ecs_http_server_start(ctx->srv);
}
}
}
}
void FlecsRestImport(
ecs_world_t *world)
{
ECS_MODULE(world, FlecsRest);
ECS_IMPORT(world, FlecsPipeline);
#ifdef FLECS_PLECS
ECS_IMPORT(world, FlecsScript);
#endif
ecs_set_name_prefix(world, "Ecs");
flecs_bootstrap_component(world, EcsRest);
ecs_set_hooks(world, EcsRest, {
.ctor = ecs_default_ctor,
.move = ecs_move(EcsRest),
.copy = ecs_copy(EcsRest),
.dtor = ecs_dtor(EcsRest),
.on_set = flecs_on_set_rest
});
ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest);
ecs_system(world, {
.entity = ecs_id(DequeueRest),
.no_readonly = true
});
ecs_observer(world, {
.filter = {
.terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }}
},
.events = {EcsOnAdd, EcsOnRemove},
.callback = DisableRest
});
ecs_set_name_prefix(world, "EcsRest");
ECS_TAG_DEFINE(world, EcsRestPlecs);
}
#endif