480 lines
16 KiB
C
480 lines
16 KiB
C
#ifndef FLECS_MONITOR_IMPL
|
|
#include "flecs_monitor.h"
|
|
#endif
|
|
|
|
typedef struct WorldStats {
|
|
ecs_world_stats_t stats;
|
|
} WorldStats;
|
|
|
|
typedef struct DeltaTime {
|
|
ecs_gauge_t frame;
|
|
ecs_gauge_t minute;
|
|
int32_t t_frame;
|
|
int32_t t_minute;
|
|
} DeltaTime;
|
|
|
|
typedef struct PipelineStats {
|
|
ecs_pipeline_stats_t stats;
|
|
} PipelineStats;
|
|
|
|
static ECS_COMPONENT_DECLARE(WorldStats);
|
|
static ECS_COMPONENT_DECLARE(DeltaTime);
|
|
static ECS_COMPONENT_DECLARE(PipelineStats);
|
|
|
|
static void add_float_array(
|
|
ecs_strbuf_t *r,
|
|
int32_t t_start,
|
|
const float *values,
|
|
float scale)
|
|
{
|
|
ecs_strbuf_list_push(r, "[", ",");
|
|
|
|
int32_t i;
|
|
for (i = 0; i < ECS_STAT_WINDOW; i ++) {
|
|
int32_t t = (t_start + i + 1) % ECS_STAT_WINDOW;
|
|
ecs_strbuf_list_append(r, "%f", values[t] / scale);
|
|
}
|
|
|
|
ecs_strbuf_list_pop(r, "]");
|
|
}
|
|
|
|
static void add_gauge(
|
|
ecs_strbuf_t *r,
|
|
const char *name,
|
|
int32_t t,
|
|
const ecs_gauge_t *m,
|
|
bool min_max,
|
|
float scale)
|
|
{
|
|
ecs_strbuf_list_append(r, "\"%s\":", name);
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
ecs_strbuf_list_append(r, "\"avg\":");
|
|
add_float_array(r, t, m->avg, scale);
|
|
|
|
if (min_max) {
|
|
ecs_strbuf_list_append(r, "\"min\":");
|
|
add_float_array(r, t, m->min, scale);
|
|
ecs_strbuf_list_append(r, "\"max\":");
|
|
add_float_array(r, t, m->max, scale);
|
|
}
|
|
|
|
ecs_strbuf_list_pop(r, "}");
|
|
}
|
|
|
|
static void add_counter(
|
|
ecs_strbuf_t *r,
|
|
const char *name,
|
|
int32_t t,
|
|
const ecs_counter_t *m,
|
|
bool min_max,
|
|
float scale)
|
|
{
|
|
add_gauge(r, name, t, &m->rate, min_max, scale);
|
|
}
|
|
|
|
#define add_current(r, name, t, m, scale)\
|
|
_add_current(r, name, t, (ecs_gauge_t*)m, scale)
|
|
|
|
static void _add_current(
|
|
ecs_strbuf_t *r,
|
|
const char *name,
|
|
int32_t t,
|
|
const ecs_gauge_t *m,
|
|
float scale)
|
|
{
|
|
ecs_strbuf_list_append(r, "\"%s\":%f", name, m->avg[t] / scale);
|
|
}
|
|
|
|
static void add_world_stats(
|
|
ecs_world_t *world,
|
|
const ecs_world_info_t *info,
|
|
ecs_strbuf_t *r)
|
|
{
|
|
const WorldStats *s = ecs_get(world, ecs_typeid(WorldStats), WorldStats);
|
|
if (!s) {
|
|
return;
|
|
}
|
|
|
|
const DeltaTime *s_dt = ecs_get(world, ecs_typeid(DeltaTime), DeltaTime);
|
|
if (!s_dt) {
|
|
return;
|
|
}
|
|
|
|
const ecs_world_stats_t *stats = &s->stats;
|
|
int32_t t = stats->t;
|
|
|
|
float df = stats->frame_count_total.rate.avg[t];
|
|
if (df == 0.0) {
|
|
return;
|
|
}
|
|
|
|
ecs_strbuf_list_appendstr(r, "\"world\":");
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
|
|
ecs_strbuf_list_appendstr(r, "\"current\":");
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
add_current(r, "entity_count", t, &stats->entity_count, 1.0);
|
|
add_current(r, "component_count", t, &stats->component_count, 1.0);
|
|
add_current(r, "query_count", t, &stats->query_count, 1.0);
|
|
add_current(r, "system_count", t, &stats->system_count, 1.0);
|
|
add_current(r, "table_count", t, &stats->table_count, 1.0);
|
|
add_current(r, "empty_table_count", t, &stats->empty_table_count, 1.0);
|
|
add_current(r, "singleton_table_count", t, &stats->singleton_table_count, 1.0);
|
|
add_current(r, "matched_table_count", t, &stats->matched_table_count, 1.0);
|
|
add_current(r, "matched_entity_count", t, &stats->matched_entity_count, 1.0);
|
|
add_current(r, "systems_ran", t, &stats->systems_ran_frame, df);
|
|
add_current(r, "new_count", t, &stats->new_count, 1.0);
|
|
add_current(r, "bulk_new_count", t, &stats->bulk_new_count, 1.0);
|
|
add_current(r, "delete_count", t, &stats->delete_count, 1.0);
|
|
add_current(r, "clear_count", t, &stats->clear_count, 1.0);
|
|
add_current(r, "add_count", t, &stats->add_count, 1.0);
|
|
add_current(r, "remove_count", t, &stats->remove_count, 1.0);
|
|
add_current(r, "set_count", t, &stats->set_count, 1.0);
|
|
add_current(r, "discard_count", t, &stats->discard_count, 1.0);
|
|
ecs_strbuf_list_pop(r, "}");
|
|
|
|
ecs_strbuf_list_appendstr(r, "\"history_1m\":");
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
add_gauge(r, "fps", t, &stats->fps, false, 1.0);
|
|
add_gauge(r, "delta_time", t, &s_dt->minute, true, 1.0);
|
|
add_counter(r, "frame_time_total", t, &stats->frame_time_total, false, df);
|
|
add_counter(r, "system_time_total", t, &stats->system_time_total, false, df);
|
|
add_counter(r, "merge_time_total", t, &stats->merge_time_total, false, df);
|
|
add_gauge(r, "entity_count", t, &stats->entity_count, false, 1.0);
|
|
add_gauge(r, "matched_entity_count", t, &stats->matched_entity_count, false, 1.0);
|
|
add_gauge(r, "table_count", t, &stats->table_count, false, 1.0);
|
|
add_gauge(r, "empty_table_count", t, &stats->empty_table_count, false, 1.0);
|
|
add_gauge(r, "singleton_table_count", t, &stats->singleton_table_count, false, 1.0);
|
|
add_gauge(r, "matched_table_count", t, &stats->matched_table_count, false, 1.0);
|
|
add_counter(r, "new_count", t, &stats->new_count, false, 1.0);
|
|
add_counter(r, "bulk_new_count", t, &stats->bulk_new_count, false, 1.0);
|
|
add_counter(r, "delete_count", t, &stats->delete_count, false, 1.0);
|
|
add_counter(r, "clear_count", t, &stats->clear_count, false, 1.0);
|
|
add_counter(r, "add_count", t, &stats->add_count, false, 1.0);
|
|
add_counter(r, "remove_count", t, &stats->remove_count, false, 1.0);
|
|
add_counter(r, "set_count", t, &stats->set_count, false, 1.0);
|
|
add_counter(r, "discard_count", t, &stats->discard_count, false, 1.0);
|
|
ecs_strbuf_list_pop(r, "}");
|
|
|
|
ecs_strbuf_list_pop(r, "}");
|
|
}
|
|
|
|
static void add_signature(
|
|
ecs_world_t *world,
|
|
ecs_strbuf_t *r,
|
|
ecs_entity_t system)
|
|
{
|
|
const EcsQuery *q = ecs_get(world, system, EcsQuery);
|
|
if (q) {
|
|
ecs_strbuf_list_append(r, "\"signature\":");
|
|
ecs_strbuf_list_push(r, "[", ",");
|
|
|
|
ecs_sig_t *sig = ecs_query_get_sig(q->query);
|
|
|
|
ecs_sig_column_t *columns =
|
|
ecs_vector_first(sig->columns, ecs_sig_column_t);
|
|
int32_t i, count = ecs_vector_count(sig->columns);
|
|
|
|
for (i = 0; i < count; i ++) {
|
|
ecs_sig_column_t *col = &columns[i];
|
|
|
|
if (col->oper_kind != EcsOperOr) {
|
|
ecs_entity_t component = col->is.component;
|
|
|
|
ecs_strbuf_list_next(r);
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
|
|
if (component & ECS_TRAIT) {
|
|
ecs_entity_t
|
|
lo = ecs_entity_t_lo(component & ECS_COMPONENT_MASK),
|
|
hi = ecs_entity_t_hi(component & ECS_COMPONENT_MASK);
|
|
|
|
const char
|
|
*lo_name = ecs_get_name(world, lo),
|
|
*hi_name = ecs_get_name(world, hi);
|
|
|
|
if (!hi) {
|
|
ecs_strbuf_list_append(r, "\"name\":\"TRAIT | %s\"", lo_name);
|
|
} else {
|
|
if (lo_name) {
|
|
ecs_strbuf_list_append(r, "\"name\":\"%s FOR %s\"",
|
|
hi_name, lo_name);
|
|
} else {
|
|
ecs_strbuf_list_append(r, "\"name\":\"%s FOR %u\"",
|
|
hi_name, (uint32_t)lo);
|
|
}
|
|
}
|
|
} else {
|
|
ecs_strbuf_list_append(r, "\"name\":\"%s\"",
|
|
ecs_get_name(world, col->is.component));
|
|
}
|
|
|
|
ecs_entity_t actual = ecs_get_typeid(world, component);
|
|
if (!ecs_has(world, actual, EcsComponent)) {
|
|
ecs_strbuf_list_append(r, "\"tag\":true");
|
|
}
|
|
|
|
if (col->inout_kind == EcsIn) {
|
|
ecs_strbuf_list_append(r, "\"const\":true");
|
|
}
|
|
|
|
if (col->oper_kind == EcsOperNot) {
|
|
ecs_strbuf_list_append(r, "\"exclude\":true");
|
|
}
|
|
|
|
if (col->from_kind != EcsFromOwned &&
|
|
col->from_kind != EcsFromAny &&
|
|
col->from_kind != EcsFromShared &&
|
|
col->from_kind != EcsFromEmpty)
|
|
{
|
|
ecs_strbuf_list_append(r, "\"ref\":true");
|
|
}
|
|
|
|
if (col->from_kind == EcsFromEmpty) {
|
|
ecs_strbuf_list_append(r, "\"empty\":true");
|
|
}
|
|
|
|
if (col->from_kind == EcsFromEntity && col->source == component) {
|
|
ecs_strbuf_list_append(r, "\"singleton\":true");
|
|
}
|
|
|
|
if (col->from_kind == EcsFromParent || col->from_kind == EcsCascade) {
|
|
ecs_strbuf_list_append(r, "\"parent\":true");
|
|
}
|
|
|
|
ecs_strbuf_list_pop(r, "}");
|
|
}
|
|
}
|
|
|
|
ecs_strbuf_list_pop(r, "]");
|
|
}
|
|
}
|
|
|
|
static void add_system(
|
|
ecs_world_t *world,
|
|
ecs_strbuf_t *r,
|
|
const ecs_pipeline_stats_t *stats,
|
|
ecs_entity_t system,
|
|
float df)
|
|
{
|
|
ecs_system_stats_t *s = ecs_map_get(
|
|
stats->system_stats, ecs_system_stats_t, system);
|
|
|
|
ecs_strbuf_list_next(r);
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
ecs_strbuf_list_append(r, "\"name\":\"%s\"", ecs_get_name(world, system));
|
|
|
|
ecs_entity_t module = ecs_get_parent_w_entity(world, system, EcsModule);
|
|
if (module) {
|
|
char *module_name = ecs_get_fullpath(world, module);
|
|
ecs_strbuf_list_append(r, "\"module\":\"%s\"", module_name);
|
|
ecs_os_free(module_name);
|
|
}
|
|
|
|
add_signature(world, r, system);
|
|
|
|
if (s) {
|
|
int32_t t = s->query_stats.t;
|
|
ecs_strbuf_list_appendstr(r, "\"current\":");
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
add_current(r, "matched_table_count", t, &s->query_stats.matched_table_count, 1.0);
|
|
add_current(r, "matched_empty_table_count", t, &s->query_stats.matched_empty_table_count, 1.0);
|
|
add_current(r, "matched_entity_count", t, &s->query_stats.matched_entity_count, 1.0);
|
|
add_current(r, "time_spent", t, &s->time_spent, df);
|
|
add_current(r, "invoke_count", t, &s->invoke_count, df);
|
|
add_current(r, "active", t, &s->active, 1.0);
|
|
add_current(r, "enabled", t, &s->enabled, 1.0);
|
|
ecs_strbuf_list_pop(r, "}");
|
|
|
|
ecs_strbuf_list_appendstr(r, "\"history_1m\":");
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
add_gauge(r, "matched_table_count", t, &s->query_stats.matched_table_count, false, 1.0);
|
|
add_gauge(r, "matched_empty_table_count", t, &s->query_stats.matched_empty_table_count, false, 1.0);
|
|
add_gauge(r, "matched_entity_count", t, &s->query_stats.matched_entity_count, false, 1.0);
|
|
add_counter(r, "time_spent", t, &s->time_spent, false, 1.0);
|
|
add_counter(r, "invoke_count", t, &s->invoke_count, false, 1.0);
|
|
add_gauge(r, "active", t, &s->active, false, 1.0);
|
|
add_gauge(r, "enabled", t, &s->enabled, false, 1.0);
|
|
ecs_strbuf_list_pop(r, "}");
|
|
}
|
|
|
|
ecs_strbuf_list_pop(r, "}");
|
|
}
|
|
|
|
static void add_pipeline_stats(
|
|
ecs_world_t *world,
|
|
const ecs_world_info_t *info,
|
|
ecs_strbuf_t *r)
|
|
{
|
|
/* Get number of frames passed in interval */
|
|
const WorldStats *ws = ecs_get(world, ecs_typeid(WorldStats), WorldStats);
|
|
if (!ws) {
|
|
return;
|
|
}
|
|
|
|
const ecs_world_stats_t *wstats = &ws->stats;
|
|
int32_t t = wstats->t;
|
|
float df = wstats->frame_count_total.rate.avg[t];
|
|
if (df == 0.0) {
|
|
return;
|
|
}
|
|
|
|
const PipelineStats *s = ecs_get(world,
|
|
ecs_typeid(PipelineStats), PipelineStats);
|
|
if (!s) {
|
|
return;
|
|
}
|
|
|
|
const ecs_pipeline_stats_t *stats = &s->stats;
|
|
int32_t i, count = ecs_vector_count(stats->systems);
|
|
ecs_entity_t *systems = ecs_vector_first(stats->systems, ecs_entity_t);
|
|
|
|
ecs_strbuf_list_appendstr(r, "\"pipeline\":");
|
|
ecs_strbuf_list_push(r, "{", ",");
|
|
|
|
add_current(r, "fps", t, &wstats->fps, 1.0);
|
|
add_current(r, "frame_time_total", t, &wstats->frame_time_total, df);
|
|
add_current(r, "system_time_total", t, &wstats->system_time_total, df);
|
|
add_current(r, "merge_time_total", t, &wstats->merge_time_total, df);
|
|
|
|
ecs_strbuf_list_appendstr(r, "\"systems\":");
|
|
ecs_strbuf_list_push(r, "[", ",");
|
|
for (i = 0; i < count; i ++) {
|
|
if (systems[i]) {
|
|
add_system(world, r, stats, systems[i], df);
|
|
} else {
|
|
/* Merge */
|
|
ecs_strbuf_list_appendstr(r, "null");
|
|
}
|
|
}
|
|
ecs_strbuf_list_pop(r, "]");
|
|
|
|
ecs_strbuf_list_pop(r, "}");
|
|
}
|
|
|
|
static bool endpoint_world(
|
|
ecs_world_t *world,
|
|
ecs_entity_t entity,
|
|
EcsHttpEndpoint *endpoint,
|
|
EcsHttpRequest *request,
|
|
EcsHttpReply *reply)
|
|
{
|
|
ecs_strbuf_t r = ECS_STRBUF_INIT;
|
|
|
|
const ecs_world_info_t *info = ecs_get_world_info(world);
|
|
|
|
ecs_strbuf_list_push(&r, "{", ",");
|
|
|
|
ecs_strbuf_list_append(&r, "\"target_fps\":%f", info->target_fps);
|
|
|
|
char param[256];
|
|
if (ecs_http_get_query_param(
|
|
request->params, "world", param, sizeof(param)) &&
|
|
!strcmp(param, "yes"))
|
|
{
|
|
add_world_stats(world, info, &r);
|
|
}
|
|
|
|
if (ecs_http_get_query_param(
|
|
request->params, "pipeline", param, sizeof(param)) &&
|
|
!strcmp(param, "yes"))
|
|
{
|
|
add_pipeline_stats(world, info, &r);
|
|
}
|
|
|
|
ecs_strbuf_list_pop(&r, "}");
|
|
|
|
reply->body = ecs_strbuf_get(&r);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void CollectWorldStats(ecs_iter_t *it) {
|
|
WorldStats *s = ecs_column(it, WorldStats, 1);
|
|
ecs_get_world_stats(it->world, &s->stats);
|
|
}
|
|
|
|
static void CollectPipelineStats(ecs_iter_t *it) {
|
|
PipelineStats *s = ecs_column(it, PipelineStats, 1);
|
|
ecs_get_pipeline_stats(it->world, ecs_get_pipeline(it->world), &s->stats);
|
|
}
|
|
|
|
static void RecordDeltaTime(ecs_iter_t *it) {
|
|
DeltaTime *s = ecs_column(it, DeltaTime, 1);
|
|
int32_t t_frame = s->t_frame;
|
|
s->frame.min[t_frame] = it->delta_time;
|
|
s->frame.avg[t_frame] = it->delta_time;
|
|
s->frame.max[t_frame] = it->delta_time;
|
|
s->t_frame = (t_frame + 1) % ECS_STAT_WINDOW;
|
|
}
|
|
|
|
static void CollectDeltaTime(ecs_iter_t *it) {
|
|
DeltaTime *s = ecs_column(it, DeltaTime, 1);
|
|
ecs_gauge_reduce(&s->minute, s->t_minute, &s->frame, s->t_frame);
|
|
s->t_minute = (s->t_minute + 1) % ECS_STAT_WINDOW;
|
|
}
|
|
|
|
static void RunServer(ecs_iter_t *it) {
|
|
ecs_world_t *world = it->world;
|
|
EcsMonitorServer *server = ecs_column(it, EcsMonitorServer, 1);
|
|
ecs_entity_t ecs_typeid(EcsHttpServer) = ecs_column_entity(it, 2);
|
|
ecs_entity_t ecs_typeid(EcsHttpEndpoint) = ecs_column_entity(it, 3);
|
|
|
|
int32_t i;
|
|
for (i = 0; i < it->count; i ++) {
|
|
ecs_entity_t e = it->entities[i];
|
|
|
|
ecs_set(world, e, EcsHttpServer, {.port = server[i].port});
|
|
|
|
/* Add endpoint to server that returns entity id of server */
|
|
ecs_entity_t e_metrics = ecs_new_w_entity(world, ECS_CHILDOF | e);
|
|
ecs_set(world, e_metrics, EcsName, {"e_metrics"});
|
|
ecs_set(world, e_metrics, EcsHttpEndpoint, {
|
|
.url = "metrics",
|
|
.action = endpoint_world,
|
|
.synchronous = true});
|
|
}
|
|
}
|
|
|
|
void FlecsMonitorImport(
|
|
ecs_world_t *world)
|
|
{
|
|
ECS_MODULE(world, FlecsMonitor);
|
|
|
|
ECS_IMPORT(world, FlecsMeta);
|
|
ECS_IMPORT(world, FlecsComponentsHttp);
|
|
|
|
ecs_set_name_prefix(world, "EcsMonitor");
|
|
|
|
ECS_META(world, EcsMonitorServer);
|
|
|
|
/* Private components */
|
|
ECS_COMPONENT_DEFINE(world, WorldStats);
|
|
ECS_COMPONENT_DEFINE(world, DeltaTime);
|
|
ECS_COMPONENT_DEFINE(world, PipelineStats);
|
|
|
|
ECS_SYSTEM(world, RunServer, EcsOnSet, Server,
|
|
:flecs.components.http.Server,
|
|
:flecs.components.http.Endpoint);
|
|
|
|
ECS_SYSTEM(world, CollectWorldStats, EcsOnLoad, $WorldStats);
|
|
ECS_SYSTEM(world, CollectPipelineStats, EcsOnLoad, $PipelineStats);
|
|
ECS_SYSTEM(world, RecordDeltaTime, EcsOnLoad, $DeltaTime);
|
|
ECS_SYSTEM(world, CollectDeltaTime, EcsOnLoad, $DeltaTime);
|
|
|
|
/* Collect statistics once per second */
|
|
ecs_set_interval(world, CollectWorldStats, 1.0);
|
|
ecs_set_interval(world, CollectPipelineStats, 1.0);
|
|
ecs_set_interval(world, CollectDeltaTime, 1.0);
|
|
|
|
/* Initialize singleton entities for statistics components */
|
|
ecs_set_ptr(world, ecs_typeid(WorldStats), WorldStats, NULL);
|
|
ecs_set_ptr(world, ecs_typeid(PipelineStats), PipelineStats, NULL);
|
|
ecs_set_ptr(world, ecs_typeid(DeltaTime), DeltaTime, NULL);
|
|
|
|
ECS_EXPORT_COMPONENT(EcsMonitorServer);
|
|
|
|
/* Enable system time monitoring */
|
|
ecs_measure_system_time(world, true);
|
|
}
|