diff --git a/components/artnet/artnet.c b/components/artnet/artnet.c index dadab24a..df9da283 100644 --- a/components/artnet/artnet.c +++ b/components/artnet/artnet.c @@ -162,7 +162,7 @@ void artnet_get_stats(struct artnet *artnet, struct artnet_stats *stats) } // node in synchronous DMX mode? -bool artnet_sync_state (struct artnet *artnet) +bool artnet_is_sync_state (struct artnet *artnet) { if (artnet->sync_tick) { return xTaskGetTickCount() - artnet->sync_tick < ARTNET_SYNC_TICKS; diff --git a/components/artnet/artnet.h b/components/artnet/artnet.h index 3a687d44..54ff1b4e 100644 --- a/components/artnet/artnet.h +++ b/components/artnet/artnet.h @@ -89,5 +89,3 @@ struct artnet { struct artnet_stats stats; }; - -bool artnet_sync_state (struct artnet *artnet); diff --git a/components/artnet/include/artnet.h b/components/artnet/include/artnet.h index b0ea6b32..e729847f 100644 --- a/components/artnet/include/artnet.h +++ b/components/artnet/include/artnet.h @@ -296,6 +296,11 @@ int artnet_get_input_state(struct artnet *artnet, int index, struct artnet_input */ int artnet_get_output_state(struct artnet *artnet, int index, struct artnet_output_state *state); +/** + * Return if node is expecting ArtSync. + */ +bool artnet_is_sync_state (struct artnet *artnet); + /** * Sync all artnet outputs. * diff --git a/components/artnet/include/artnet_stats.h b/components/artnet/include/artnet_stats.h index 91acdf31..2181712f 100644 --- a/components/artnet/include/artnet_stats.h +++ b/components/artnet/include/artnet_stats.h @@ -19,7 +19,7 @@ struct artnet_stats { /* Received ArtSync packets */ struct stats_counter recv_sync; - /* Received ArtPoll packets */ + /* Received unknown packets */ struct stats_counter recv_unknown; /* Received packets, rejected as invalid */ @@ -38,30 +38,36 @@ struct artnet_input_stats { struct stats_counter dmx_recv; /* Output queue overflowed, previous packet overwritten */ - struct stats_counter queue_overwrite; + struct stats_counter queue_overflow; }; struct artnet_output_stats { - /* Received ArtSync packets */ - struct stats_counter sync_recv; - /* Received ArtDMX packets */ struct stats_counter dmx_recv; /* Received ArtDMX packets in sync mode */ struct stats_counter dmx_sync; - /* Received ArtDMX packets with seq larger than expected */ - struct stats_counter seq_skip; + /* Received ArtDMX packets without seq */ + struct stats_counter seq_zero; - /* Dropped ArtDMX packets with seq smaller than expected */ + /* Received ArtDMX packets with expected seq */ + struct stats_counter seq_good; + + /* Received ArtDMX packets with seq larger than expected, missed packets */ + struct stats_counter seq_miss; + + /* Received ArtDMX packets with seq smaller than expected, dropping packet */ struct stats_counter seq_drop; /* Received ArtDMX packets with seq resynced after timeout */ struct stats_counter seq_resync; + /* Output queue updated */ + struct stats_counter queue_update; + /* Output queue overflowed, previous packet overwritten */ - struct stats_counter queue_overwrite; + struct stats_counter queue_overflow; }; /* diff --git a/components/artnet/input.c b/components/artnet/input.c index 17be847d..0e8d1bfc 100644 --- a/components/artnet/input.c +++ b/components/artnet/input.c @@ -5,7 +5,7 @@ static void init_input_stats(struct artnet_input_stats *stats) { stats_counter_init(&stats->dmx_recv); - stats_counter_init(&stats->queue_overwrite); + stats_counter_init(&stats->queue_overflow); } int artnet_add_input(struct artnet *artnet, struct artnet_input **inputp, struct artnet_input_options options) @@ -59,7 +59,7 @@ void artnet_input_dmx(struct artnet_input *input, const struct artnet_dmx *dmx) // attempt normal send first, before overwriting for overflow stats if (xQueueSend(input->queue, dmx, 0) == errQUEUE_FULL) { - stats_counter_increment(&input->stats.queue_overwrite); + stats_counter_increment(&input->stats.queue_overflow); xQueueOverwrite(input->queue, dmx); } @@ -163,7 +163,7 @@ int artnet_get_input_stats(struct artnet *artnet, int index, struct artnet_input struct artnet_input *input = &artnet->input_ports[index]; stats->dmx_recv = stats_counter_copy(&input->stats.dmx_recv); - stats->queue_overwrite = stats_counter_copy(&input->stats.queue_overwrite); + stats->queue_overflow = stats_counter_copy(&input->stats.queue_overflow); return 0; } diff --git a/components/artnet/output.c b/components/artnet/output.c index 26f38672..0f15ead5 100644 --- a/components/artnet/output.c +++ b/components/artnet/output.c @@ -4,13 +4,14 @@ static void init_output_stats(struct artnet_output_stats *stats) { - stats_counter_init(&stats->sync_recv); stats_counter_init(&stats->dmx_recv); - stats_counter_init(&stats->dmx_sync); - stats_counter_init(&stats->seq_skip); + stats_counter_init(&stats->seq_zero); + stats_counter_init(&stats->seq_good); + stats_counter_init(&stats->seq_miss); stats_counter_init(&stats->seq_drop); stats_counter_init(&stats->seq_resync); - stats_counter_init(&stats->queue_overwrite); + stats_counter_init(&stats->queue_update); + stats_counter_init(&stats->queue_overflow); } int artnet_add_output(struct artnet *artnet, struct artnet_output **outputp, struct artnet_output_options options) @@ -137,13 +138,14 @@ int artnet_get_output_stats(struct artnet *artnet, int index, struct artnet_outp struct artnet_output *output = &artnet->output_ports[index]; - stats->sync_recv = stats_counter_copy(&output->stats.sync_recv); stats->dmx_recv = stats_counter_copy(&output->stats.dmx_recv); - stats->dmx_sync = stats_counter_copy(&output->stats.dmx_sync); - stats->seq_skip = stats_counter_copy(&output->stats.seq_skip); + stats->seq_zero = stats_counter_copy(&output->stats.seq_zero); + stats->seq_good = stats_counter_copy(&output->stats.seq_good); + stats->seq_miss = stats_counter_copy(&output->stats.seq_miss); stats->seq_drop = stats_counter_copy(&output->stats.seq_drop); stats->seq_resync = stats_counter_copy(&output->stats.seq_resync); - stats->queue_overwrite = stats_counter_copy(&output->stats.queue_overwrite); + stats->queue_update = stats_counter_copy(&output->stats.queue_update); + stats->queue_overflow = stats_counter_copy(&output->stats.queue_overflow); return 0; } @@ -173,15 +175,20 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) stats_counter_increment(&output->stats.dmx_recv); - if (dmx->seq == 0 || output->state.seq == 0) { + if (output->state.seq == 0) { // init or reset + } else if (dmx->seq == 0) { + // disabled + stats_counter_increment(&output->stats.seq_zero); + } else if (dmx->seq == artnet_seq_next(output->state.seq)) { // in-order + stats_counter_increment(&output->stats.seq_good); } else if (dmx->seq > output->state.seq || output->state.seq - dmx->seq >= 128) { - // skipped - stats_counter_increment(&output->stats.seq_skip); + // missed + stats_counter_increment(&output->stats.seq_miss); } else if (output->state.tick < tick && (tick - output->state.tick) > ARTNET_SEQ_TICKS) { LOG_WARN("resync address=%04x seq=%d < %d on timeout", output->options.address, dmx->seq, output->state.seq); @@ -194,35 +201,32 @@ void artnet_output_dmx(struct artnet_output *output, struct artnet_dmx *dmx) } else { LOG_WARN("drop address=%04x seq=%d < %d", output->options.address, dmx->seq, output->state.seq); + // dropping stats_counter_increment(&output->stats.seq_drop); // do NOT update output->state.tick, in order to resync on timeout return; } - // advance - if (dmx->seq) { - output->state.seq = dmx->seq; - } else { - output->state.seq++; - } - + // update + output->state.seq = dmx->seq; output->state.tick = tick; // attempt normal send first, before overwriting for overflow stats if (xQueueSend(output->queue, dmx, 0) == errQUEUE_FULL) { - stats_counter_increment(&output->stats.queue_overwrite); + stats_counter_increment(&output->stats.queue_overflow); xQueueOverwrite(output->queue, dmx); + } else { + stats_counter_increment(&output->stats.queue_update); } if (output->options.output_events) { xEventGroupSetBits(output->options.output_events, output->options.output_event_bit); } - if (artnet_sync_state(output->artnet)) { + if (artnet_is_sync_state(output->artnet)) { // wait for hard sync - stats_counter_increment(&output->stats.dmx_sync); } else if (output->options.event_group && output->options.dmx_event_bit) { // sync each update xEventGroupSetBits(output->options.event_group, output->options.dmx_event_bit); @@ -264,8 +268,6 @@ int artnet_sync_outputs(struct artnet *artnet) continue; } - stats_counter_increment(&output->stats.sync_recv); - if (output->options.event_group != event_group) { xEventGroupSetBits(output->options.event_group, output->options.sync_event_bit); diff --git a/components/i2s_out/CMakeLists.txt b/components/i2s_out/CMakeLists.txt index 9ceaebd6..e6fb4b04 100644 --- a/components/i2s_out/CMakeLists.txt +++ b/components/i2s_out/CMakeLists.txt @@ -1,7 +1,7 @@ idf_component_register( SRC_DIRS . ${IDF_TARGET} INCLUDE_DIRS "include" - PRIV_REQUIRES logging + PRIV_REQUIRES logging stats LDFRAGMENTS ${IDF_TARGET}.lf ) diff --git a/components/i2s_out/esp32/dma.c b/components/i2s_out/esp32/dma.c index 944fd2ea..39d4a921 100644 --- a/components/i2s_out/esp32/dma.c +++ b/components/i2s_out/esp32/dma.c @@ -556,9 +556,7 @@ int i2s_out_dma_flush(struct i2s_out *i2s_out, TickType_t timeout) taskENTER_CRITICAL(&i2s_out->mux); - dma_done = i2s_out->dma_done; - - if (!dma_done) { + if (!(dma_done = i2s_out->dma_done)) { i2s_out->dma_done_task = xTaskGetCurrentTaskHandle(); } diff --git a/components/i2s_out/esp32/i2s.c b/components/i2s_out/esp32/i2s.c index aafe41c5..cc4f2488 100644 --- a/components/i2s_out/esp32/i2s.c +++ b/components/i2s_out/esp32/i2s.c @@ -131,6 +131,9 @@ void i2s_out_i2s_start(struct i2s_out *i2s_out) i2s_out->i2s_done = false; i2s_out->i2s_done_task = NULL; + // track active time + i2s_out->stats_out_timer_start = stats_timer_start(&i2s_out->stats.out_timer); + taskENTER_CRITICAL(&i2s_out->mux); // NOTE: there seems to always be three extra BCK cycles at the start of TX @@ -139,6 +142,9 @@ void i2s_out_i2s_start(struct i2s_out *i2s_out) // let's hope that the EOF frame is always zeroes, and zero bytes at the start are harmless... i2s_ll_tx_start(i2s_out->dev); + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + taskEXIT_CRITICAL(&i2s_out->mux); } @@ -151,13 +157,8 @@ int i2s_out_i2s_flush(struct i2s_out *i2s_out, TickType_t timeout) taskENTER_CRITICAL(&i2s_out->mux); - i2s_done = i2s_out->i2s_done; - - if (!i2s_done) { + if (!(i2s_done = i2s_out->i2s_done)) { i2s_out->i2s_done_task = xTaskGetCurrentTaskHandle(); - - i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); - i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); } taskEXIT_CRITICAL(&i2s_out->mux); @@ -188,6 +189,9 @@ void i2s_out_i2s_stop(struct i2s_out *i2s_out) taskENTER_CRITICAL(&i2s_out->mux); + i2s_intr_disable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + i2s_ll_tx_stop(i2s_out->dev); taskEXIT_CRITICAL(&i2s_out->mux); diff --git a/components/i2s_out/esp32/intr_iram.c b/components/i2s_out/esp32/intr_iram.c index 72488c9b..6340e57b 100644 --- a/components/i2s_out/esp32/intr_iram.c +++ b/components/i2s_out/esp32/intr_iram.c @@ -40,6 +40,21 @@ static void i2s_out_intr_dma_done(struct i2s_out *i2s_out, BaseType_t *pxHigherP } } +static void i2s_out_intr_i2s_done(struct i2s_out *i2s_out, BaseType_t *pxHigherPriorityTaskWoken) +{ + // unblock flush() task + i2s_out->i2s_done = true; + + if (i2s_out->i2s_done_task) { + xTaskNotifyFromISR(i2s_out->i2s_done_task, I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE, eSetBits, pxHigherPriorityTaskWoken); + + i2s_out->i2s_done_task = NULL; + } + + // stats + stats_timer_stop(&i2s_out->stats.out_timer, &i2s_out->stats_out_timer_start); +} + static void i2s_intr_out_dscr_err_handler(struct i2s_out *i2s_out) { uint32_t dscr_addr; @@ -76,6 +91,10 @@ static void i2s_intr_out_eof_handler(struct i2s_out *i2s_out, BaseType_t *pxHigh i2s_out_intr_dma_out_desc(i2s_out, eof_desc, pxHigherPriorityTaskWoken); i2s_out_intr_dma_done(i2s_out, pxHigherPriorityTaskWoken); + // XXX: ensure tx rempty intr is enabled, in case it fired during DMA and was disabled? + i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); + i2s_intr_enable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); + } else { LOG_ISR_ERROR("unknown desc=%p owner=%u len=%u", eof_desc, eof_desc->owner, eof_desc->len); } @@ -89,13 +108,14 @@ static void i2s_intr_tx_rempty_handler(struct i2s_out *i2s_out, BaseType_t *pxHi i2s_intr_disable(i2s_out->dev, I2S_TX_REMPTY_INT_ENA); i2s_intr_clear(i2s_out->dev, I2S_TX_REMPTY_INT_CLR); - // unblock flush() task - i2s_out->i2s_done = true; - - if (i2s_out->i2s_done_task) { - xTaskNotifyFromISR(i2s_out->i2s_done_task, I2S_OUT_TASK_NOTIFY_BIT_I2S_DONE, eSetBits, pxHigherPriorityTaskWoken); + if (!i2s_out->dma_done) { + // XXX: ignore if fired before dma_done, will be re-enabled + // XXX: may indicate a timing glitch in the output data? + LOG_ISR_WARN("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done); + } else { + LOG_ISR_DEBUG("tx rempty dma_done=%u i2s_done=%u", i2s_out->dma_done, i2s_out->i2s_done); - i2s_out->i2s_done_task = NULL; + i2s_out_intr_i2s_done(i2s_out, pxHigherPriorityTaskWoken); } } diff --git a/components/i2s_out/i2s_out.c b/components/i2s_out/i2s_out.c index 8069eb87..fae08019 100644 --- a/components/i2s_out/i2s_out.c +++ b/components/i2s_out/i2s_out.c @@ -28,9 +28,11 @@ int i2s_out_init(struct i2s_out *i2s_out, i2s_port_t port) } #endif - #if CONFIG_IDF_TARGET_ESP32 - portMUX_INITIALIZE(&i2s_out->mux); - #endif +#if CONFIG_IDF_TARGET_ESP32 + portMUX_INITIALIZE(&i2s_out->mux); +#endif + + i2s_out_stats_reset(&i2s_out->stats); return 0; } @@ -76,6 +78,16 @@ int i2s_out_new(struct i2s_out **i2s_outp, i2s_port_t port, size_t buffer_size, return err; } +void i2s_out_reset_stats(struct i2s_out *i2s_out) +{ + i2s_out_stats_reset(&i2s_out->stats); +} + +struct i2s_out_stats i2s_out_stats(struct i2s_out *i2s_out) +{ + return i2s_out_stats_copy(&i2s_out->stats); +} + int i2s_out_open(struct i2s_out *i2s_out, const struct i2s_out_options *options, TickType_t timeout) { int err = 0; diff --git a/components/i2s_out/i2s_out.h b/components/i2s_out/i2s_out.h index ba6fd6f6..30af2b7a 100644 --- a/components/i2s_out/i2s_out.h +++ b/components/i2s_out/i2s_out.h @@ -1,4 +1,7 @@ #include +#include + +#include #include #include @@ -73,6 +76,10 @@ struct i2s_out { TaskHandle_t dma_out_task; // task waiting for dma_out_desc to update TaskHandle_t dma_done_task; // task waiting for dma_done to be set TaskHandle_t i2s_done_task; // task waiting for i2s_done to be set + + struct i2s_out_stats stats; + + stats_timer_start_t stats_out_timer_start; }; /* dma.c */ @@ -107,3 +114,7 @@ void i2s_out_pin_teardown(struct i2s_out *i2s_out); /* intr.c */ int i2s_out_intr_setup(struct i2s_out *i2s_out, const struct i2s_out_options *options); void i2s_out_intr_teardown(struct i2s_out *i2s_out); + +/* stats.c */ +void i2s_out_stats_reset(struct i2s_out_stats *stats); +struct i2s_out_stats i2s_out_stats_copy(struct i2s_out_stats *stats); diff --git a/components/i2s_out/include/i2s_out_stats.h b/components/i2s_out/include/i2s_out_stats.h new file mode 100644 index 00000000..39c15939 --- /dev/null +++ b/components/i2s_out/include/i2s_out_stats.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +struct i2s_out; + +struct i2s_out_stats { + struct stats_timer out_timer; +}; + +void i2s_out_reset_stats(struct i2s_out *i2s_out); + +struct i2s_out_stats i2s_out_stats(struct i2s_out *i2s_out); diff --git a/components/i2s_out/stats.c b/components/i2s_out/stats.c new file mode 100644 index 00000000..1889b35f --- /dev/null +++ b/components/i2s_out/stats.c @@ -0,0 +1,15 @@ +#include + +#include "i2s_out.h" + +void i2s_out_stats_reset(struct i2s_out_stats *stats) +{ + stats_timer_init(&stats->out_timer); +} + +struct i2s_out_stats i2s_out_stats_copy(struct i2s_out_stats *stats) +{ + return (struct i2s_out_stats) { + .out_timer = stats_timer_copy(&stats->out_timer), + }; +} diff --git a/components/leds/stats.c b/components/leds/stats.c index 1d4d9b90..ccbd0665 100644 --- a/components/leds/stats.c +++ b/components/leds/stats.c @@ -19,6 +19,7 @@ void leds_reset_interface_stats() #if LEDS_I2S_INTERFACE_COUNT > 0 stats_timer_init(&leds_interface_stats.i2s0.open); stats_timer_init(&leds_interface_stats.i2s0.write); + stats_timer_init(&leds_interface_stats.i2s0.start); stats_timer_init(&leds_interface_stats.i2s0.flush); #endif #if LEDS_I2S_INTERFACE_COUNT > 1 diff --git a/components/stats/counter.c b/components/stats/counter.c new file mode 100644 index 00000000..5647de13 --- /dev/null +++ b/components/stats/counter.c @@ -0,0 +1,47 @@ +#include + +struct stats_counter_metrics stats_counter_diff_metrics(const struct stats_counter *old, const struct stats_counter *new) +{ + if ((!old->update || new->reset > old->reset) && !new->update) { + // increasing interval with zero rate until first update + return (struct stats_counter_metrics) { + .interval = (float)(esp_timer_get_time() - new->reset) / 1000000.0f, + }; + } else if ((!old->update || new->reset > old->reset) && (new->update > new->reset)) { + // initial update + return (struct stats_counter_metrics) { + .interval = (float)(new->update - new->reset) / 1000000.0f, + .rate = ((float) (new->count)) / ((float)(new->update - new->reset) / 1000000.0f), + }; + } else if ((old->update && old->reset == new->reset) && new->update == old->update) { + // increasing interval with zero rate if updates stop + return (struct stats_counter_metrics) { + .interval = (float)(esp_timer_get_time() - old->update) / 1000000.0f, + }; + } else if ((old->update && old->reset == new->reset) && new->update > old->update) { + // incremental update + return (struct stats_counter_metrics) { + .interval = (float)(new->update - old->update) / 1000000.0f, + .rate = ((float) (new->count - old->count)) / ((float)(new->update - old->update) / 1000000.0f), + }; + } else { + // ??? + return (struct stats_counter_metrics) {}; + } +} + +struct stats_counter_metrics stats_counter_metrics_average(const struct stats_counter_metrics *old, const struct stats_counter_metrics *new) +{ + if (old->interval && new->interval) { + return (struct stats_counter_metrics) { + .interval = (old->interval + new->interval) / 2, + .rate = (old->rate + new->rate) / 2, + }; + } else if (old->interval && !new->interval) { + return *old; + } else if (!old->interval && new->interval) { + return *new; + } else { + return (struct stats_counter_metrics) {}; + } +} diff --git a/components/stats/include/stats_counter.h b/components/stats/include/stats_counter.h index 7de58562..60ee1a3a 100644 --- a/components/stats/include/stats_counter.h +++ b/components/stats/include/stats_counter.h @@ -46,3 +46,11 @@ static inline float stats_counter_seconds_passed(const struct stats_counter *cou return 0.0f; } } + +struct stats_counter_metrics { + float interval; + float rate; +}; + +struct stats_counter_metrics stats_counter_diff_metrics(const struct stats_counter *old, const struct stats_counter *new); +struct stats_counter_metrics stats_counter_metrics_average(const struct stats_counter_metrics *old, const struct stats_counter_metrics *new); diff --git a/components/stats/include/stats_timer.h b/components/stats/include/stats_timer.h index eb01176a..09082972 100644 --- a/components/stats/include/stats_timer.h +++ b/components/stats/include/stats_timer.h @@ -4,16 +4,16 @@ #include +#define WITH_STATS_TIMER(timer) \ + for (stats_timer_start_t _stats_timer_start = stats_timer_start(timer); _stats_timer_start; stats_timer_stop(timer, &_stats_timer_start)) + struct stats_timer { uint64_t reset, update; uint32_t count; uint64_t total; // us }; -struct stats_timer_sample { - uint64_t start; - bool running; -}; +typedef uint64_t stats_timer_start_t; static inline void stats_timer_init(struct stats_timer *timer) { @@ -29,28 +29,28 @@ static inline struct stats_timer stats_timer_copy(const struct stats_timer *time return *timer; } -static inline void stats_timer_update(struct stats_timer *timer, uint32_t count, uint64_t interval) +// IRAM-safe +static inline void stats_timer_update(struct stats_timer *timer, stats_timer_start_t start) { + assert(start); + + uint64_t stop = esp_timer_get_time(); + timer->update = esp_timer_get_time(); - timer->count += count; - timer->total += interval; + timer->count += 1; + timer->total += stop - start; } -static inline struct stats_timer_sample stats_timer_start(struct stats_timer *timer) +static inline stats_timer_start_t stats_timer_start(struct stats_timer *timer) { - return (struct stats_timer_sample) { - .start = esp_timer_get_time(), - .running = true, - }; + return esp_timer_get_time(); } -static inline void stats_timer_stop(struct stats_timer *timer, struct stats_timer_sample *sample) +static inline void stats_timer_stop(struct stats_timer *timer, stats_timer_start_t *startp) { - uint64_t stop = esp_timer_get_time(); + stats_timer_update(timer, *startp); - stats_timer_update(timer, 1, stop - sample->start); - - sample->running = false; + *startp = 0; } static inline float stats_timer_seconds_passed(const struct stats_timer *timer) @@ -99,5 +99,22 @@ static inline float stats_timer_utilization(const struct stats_timer *timer) } } -#define WITH_STATS_TIMER(timer) \ - for (struct stats_timer_sample _stats_timer_sample = stats_timer_start(timer); _stats_timer_sample.running; stats_timer_stop(timer, &_stats_timer_sample)) +struct stats_timer_metrics { + float interval; + float rate; + float util; +}; + +/* + * Compute metrics between old -> new. + * + * Returns zero values if new is invalid. + * Returns values from new if old is invalid. + */ +struct stats_timer_metrics stats_timer_diff_metrics(const struct stats_timer *old, const struct stats_timer *new); + +/* + * Compute moving averge between old + new / 2. + */ +struct stats_timer_metrics stats_timer_metrics_average(const struct stats_timer_metrics *old, const struct stats_timer_metrics *new); + diff --git a/components/stats/timer.c b/components/stats/timer.c new file mode 100644 index 00000000..7d3bbbf1 --- /dev/null +++ b/components/stats/timer.c @@ -0,0 +1,50 @@ +#include + +struct stats_timer_metrics stats_timer_diff_metrics(const struct stats_timer *old, const struct stats_timer *new) +{ + if ((!old->update || new->reset > old->reset) && !new->update) { + // increasing interval with zero rate until first update + return (struct stats_timer_metrics) { + .interval = (float)(esp_timer_get_time() - new->reset) / 1000000.0f, + }; + } else if ((!old->update || new->reset > old->reset) && (new->update > new->reset)) { + // initial update + return (struct stats_timer_metrics) { + .interval = (float)(new->update - new->reset) / 1000000.0f, + .rate = ((float) (new->count)) / ((float)(new->update - new->reset) / 1000000.0f), + .util = ((float) (new->total)) / ((float)(new->update - new->reset)), + }; + } else if ((old->update && old->reset == new->reset) && new->update == old->update) { + // increasing interval with zero rate if updates stop + return (struct stats_timer_metrics) { + .interval = (float)(esp_timer_get_time() - old->update) / 1000000.0f, + }; + } else if ((old->update && old->reset == new->reset) && new->update > old->update) { + // incremental update + return (struct stats_timer_metrics) { + .interval = (float)(new->update - old->update) / 1000000.0f, + .rate = ((float) (new->count - old->count)) / ((float)(new->update - old->update) / 1000000.0f), + .util = ((float) (new->total - old->total)) / ((float)(new->update - old->update)), + }; + } else { + // ??? + return (struct stats_timer_metrics) {}; + } +} + +struct stats_timer_metrics stats_timer_metrics_average(const struct stats_timer_metrics *old, const struct stats_timer_metrics *new) +{ + if (old->interval && new->interval) { + return (struct stats_timer_metrics) { + .interval = (old->interval + new->interval) / 2, + .rate = (old->rate + new->rate) / 2, + .util = (old->util + new->util) / 2, + }; + } else if (old->interval && !new->interval) { + return *old; + } else if (!old->interval && new->interval) { + return *new; + } else { + return (struct stats_timer_metrics) {}; + } +} diff --git a/main/artnet_cmd.c b/main/artnet_cmd.c index 073eedbe..f8eb4cac 100644 --- a/main/artnet_cmd.c +++ b/main/artnet_cmd.c @@ -1,5 +1,6 @@ #include "artnet.h" #include "artnet_state.h" +#include "artnet_status.h" #include #include "artnet_config.h" @@ -19,6 +20,7 @@ int artnet_cmd_info(int argc, char **argv, void *ctx) return 1; } + struct artnet_status status = get_artnet_status(artnet); struct artnet_options options = artnet_get_options(artnet); unsigned input_count = artnet_get_input_count(artnet); unsigned output_count = artnet_get_output_count(artnet); @@ -43,6 +45,7 @@ int artnet_cmd_info(int argc, char **argv, void *ctx) printf("\tUDP: port=%u\n", options.port); printf("\tShort name: %s\n", options.metadata.short_name); printf("\tLong name: %s\n", options.metadata.long_name); + printf("\n"); if ((err = artnet_get_inputs(artnet, artnet_input_options, &inputs_size))) { LOG_ERROR("artnet_get_inputs"); @@ -54,7 +57,18 @@ int artnet_cmd_info(int argc, char **argv, void *ctx) return err; } + printf("Status:\n"); + printf("\tSync Mode : %s\n", status.sync_mode ? "true" : "false"); + printf("\n"); + + printf("Metrics:\n"); + printf("\tRecv : %6.1f/s @ %6.1f%% (%.0fs)\n", status.metrics.recv_timer.rate, status.metrics.recv_timer.util * 100.0f, status.metrics.recv_timer.interval); + printf("\tRecv Poll : %6.1f/s (%.0fs)\n", status.metrics.recv_poll_counter.rate, status.metrics.recv_poll_counter.interval); + printf("\tRecv DMX : %6.1f/s (%.0fs)\n", status.metrics.recv_dmx_counter.rate, status.metrics.recv_dmx_counter.interval); + printf("\tRecv Sync : %6.1f/s (%.0fs)\n", status.metrics.recv_sync_counter.rate, status.metrics.recv_sync_counter.interval); + printf("\tDMX Disc : %6.1f/s (%.0fs)\n", status.metrics.dmx_discard_counter.rate, status.metrics.dmx_discard_counter.interval); printf("\n"); + printf("Inputs: count=%u / max=%d\n", input_count, ARTNET_INPUTS_MAX); for (int i = 0; i < inputs_size && i < ARTNET_INPUTS_MAX; i++) { @@ -88,11 +102,12 @@ int artnet_cmd_info(int argc, char **argv, void *ctx) continue; } - printf("\t%2d: net %3u subnet %2u universe %2u -> %-16.16s: seq %3u @ %d ms\n", i, + printf("\t%2d: net %3u subnet %2u universe %2u -> %-16.16s: dmx %6.1f/s (%.0fs) (seq %3u @ %dms)\n", i, artnet_address_net(options->address), artnet_address_subnet(options->address), artnet_address_universe(options->address), options->name, + status.metrics.outputs[i].dmx_counter.rate, status.metrics.outputs[i].dmx_counter.interval, state.seq, state.tick ? (tick - state.tick) * portTICK_RATE_MS : 0 ); @@ -147,8 +162,8 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) printf("Input %d: \n", i); - print_stats_counter("DMX", "received", &input_stats.dmx_recv); - print_stats_counter("Queue", "overflowed", &input_stats.queue_overwrite); + print_stats_counter("DMX", "receive", &input_stats.dmx_recv); + print_stats_counter("Queue", "overflow", &input_stats.queue_overflow); printf("\n"); } @@ -166,13 +181,14 @@ int artnet_cmd_stats(int argc, char **argv, void *ctx) printf("Output %d: \n", i); - print_stats_counter("Sync", "received", &output_stats.sync_recv); - print_stats_counter("DMX", "received", &output_stats.dmx_recv); - print_stats_counter("DMX", "synced", &output_stats.dmx_sync); - print_stats_counter("Seq", "skipped", &output_stats.seq_skip); - print_stats_counter("Seq", "dropped", &output_stats.seq_drop); - print_stats_counter("Seq", "resynced", &output_stats.seq_resync); - print_stats_counter("Queue", "overflowed", &output_stats.queue_overwrite); + print_stats_counter("DMX", "receive", &output_stats.dmx_recv); + print_stats_counter("Seq", "zero", &output_stats.seq_zero); + print_stats_counter("Seq", "good", &output_stats.seq_good); + print_stats_counter("Seq", "miss", &output_stats.seq_miss); + print_stats_counter("Seq", "drop", &output_stats.seq_drop); + print_stats_counter("Seq", "resync", &output_stats.seq_resync); + print_stats_counter("Queue", "update", &output_stats.queue_update); + print_stats_counter("Queue", "overflow", &output_stats.queue_overflow); printf("\n"); } diff --git a/main/artnet_http.c b/main/artnet_http.c index 4540c83b..4cd021cc 100644 --- a/main/artnet_http.c +++ b/main/artnet_http.c @@ -1,5 +1,6 @@ #include "artnet.h" #include "artnet_state.h" +#include "artnet_status.h" #include "http_routes.h" #include "http_handlers.h" @@ -12,6 +13,24 @@ #define TICK_MS(current_tick, tick) (tick ? (current_tick - tick) * portTICK_RATE_MS : 0) + +static int artnet_api_write_object_status_timer_metrics(struct json_writer *w, const struct stats_timer_metrics *metrics) +{ + return ( + JSON_WRITE_MEMBER_FLOAT(w, "interval", metrics->interval) + || JSON_WRITE_MEMBER_FLOAT(w, "rate", metrics->rate) + || JSON_WRITE_MEMBER_FLOAT(w, "util", metrics->util) + ); +} + +static int artnet_api_write_object_status_counter_metrics(struct json_writer *w, const struct stats_counter_metrics *metrics) +{ + return ( + JSON_WRITE_MEMBER_FLOAT(w, "interval", metrics->interval) + || JSON_WRITE_MEMBER_FLOAT(w, "rate", metrics->rate) + ); +} + static int artnet_api_write_input_object(struct json_writer *w, const struct artnet_input_options *options, const struct artnet_input_state *state) { TickType_t tick = xTaskGetTickCount(); @@ -54,7 +73,7 @@ static int artnet_api_write_inputs_array(struct json_writer *w, void *ctx) return 0; } -static int artnet_api_write_output_object(struct json_writer *w, const struct artnet_output_options *options, const struct artnet_output_state *state) +static int artnet_api_write_output_object(struct json_writer *w, const struct artnet_output_options *options, const struct artnet_output_state *state, const struct artnet_status_output_metrics *metrics) { TickType_t tick = xTaskGetTickCount(); @@ -68,13 +87,19 @@ static int artnet_api_write_output_object(struct json_writer *w, const struct ar || JSON_WRITE_MEMBER_UINT(w, "tick_ms", TICK_MS(tick, state->tick)) || JSON_WRITE_MEMBER_UINT(w, "seq", state->seq) ) + || JSON_WRITE_MEMBER_OBJECT(w, "metrics", + JSON_WRITE_MEMBER_OBJECT(w, "dmx_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->dmx_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "seq_miss_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_miss_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "seq_drop_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->seq_drop_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "update_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->update_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "overflow_counter", artnet_api_write_object_status_counter_metrics(w, &metrics->overflow_counter)) + ) ); } -static int artnet_api_write_outputs_array(struct json_writer *w, void *ctx) +static int artnet_api_write_outputs_array(struct json_writer *w, struct artnet *artnet, const struct artnet_status *status) { - struct artnet *artnet = ctx; struct artnet_output_options options; struct artnet_output_state state; int err; @@ -85,7 +110,7 @@ static int artnet_api_write_outputs_array(struct json_writer *w, void *ctx) state = (struct artnet_output_state) {}; } - if ((err = JSON_WRITE_OBJECT(w, artnet_api_write_output_object(w, &options, &state)))) { + if ((err = JSON_WRITE_OBJECT(w, artnet_api_write_output_object(w, &options, &state, &status->metrics.outputs[index])))) { return err; } } @@ -107,14 +132,16 @@ static int artnet_api_write_inputs(struct json_writer *w, void *ctx) static int artnet_api_write_outputs(struct json_writer *w, void *ctx) { struct artnet *artnet = ctx; + struct artnet_status status = get_artnet_status(artnet); - return JSON_WRITE_ARRAY(w, artnet_api_write_outputs_array(w, artnet)); + return JSON_WRITE_ARRAY(w, artnet_api_write_outputs_array(w, artnet, &status)); } static int artnet_api_write(struct json_writer *w, void *ctx) { struct artnet *artnet = ctx; struct artnet_options options = artnet_get_options(artnet); + struct artnet_status status = get_artnet_status(artnet); return JSON_WRITE_OBJECT(w, JSON_WRITE_MEMBER_OBJECT(w, "info", @@ -136,8 +163,18 @@ static int artnet_api_write(struct json_writer *w, void *ctx) || JSON_WRITE_MEMBER_STRING(w, "short_name", options.metadata.short_name) || JSON_WRITE_MEMBER_STRING(w, "long_name", options.metadata.long_name) ) + || JSON_WRITE_MEMBER_OBJECT(w, "status", + JSON_WRITE_MEMBER_BOOL(w, "sync_mode", status.sync_mode) + ) + || JSON_WRITE_MEMBER_OBJECT(w, "metrics", + JSON_WRITE_MEMBER_OBJECT(w, "recv_timer", artnet_api_write_object_status_timer_metrics(w, &status.metrics.recv_timer)) + || JSON_WRITE_MEMBER_OBJECT(w, "recv_poll_counter", artnet_api_write_object_status_counter_metrics(w, &status.metrics.recv_poll_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "recv_dmx_counter", artnet_api_write_object_status_counter_metrics(w, &status.metrics.recv_dmx_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "recv_sync_counter", artnet_api_write_object_status_counter_metrics(w, &status.metrics.recv_sync_counter)) + || JSON_WRITE_MEMBER_OBJECT(w, "dmx_discard_counter", artnet_api_write_object_status_counter_metrics(w, &status.metrics.dmx_discard_counter)) + ) || JSON_WRITE_MEMBER_ARRAY(w, "inputs", artnet_api_write_inputs_array(w, artnet)) - || JSON_WRITE_MEMBER_ARRAY(w, "outputs", artnet_api_write_outputs_array(w, artnet)) + || JSON_WRITE_MEMBER_ARRAY(w, "outputs", artnet_api_write_outputs_array(w, artnet, &status)) ); } diff --git a/main/artnet_status.c b/main/artnet_status.c new file mode 100644 index 00000000..32af2615 --- /dev/null +++ b/main/artnet_status.c @@ -0,0 +1,60 @@ +#include "artnet_status.h" +#include + +struct artnet_status_stats artnet_status_stats = {}; +struct artnet_status_metrics artnet_status_metrics = {}; + +// basic moving average +static void update_stats_timer_metrics(struct stats_timer *baseline, const struct stats_timer *timer, struct stats_timer_metrics *avg) +{ + struct stats_timer_metrics metrics = stats_timer_diff_metrics(baseline, timer); + + *avg = stats_timer_metrics_average(avg, &metrics); + + *baseline = *timer; +} + +static void update_stats_counter_metrics(struct stats_counter *baseline, const struct stats_counter *counter, struct stats_counter_metrics *avg) +{ + struct stats_counter_metrics metrics = stats_counter_diff_metrics(baseline, counter); + + *avg = stats_counter_metrics_average(avg, &metrics); + + *baseline = *counter; +} + +void update_artnet_status(struct artnet *artnet) +{ + struct artnet_stats artnet_stats; + unsigned artnet_output_coumt = artnet_get_output_count(artnet); + + artnet_get_stats(artnet, &artnet_stats); + + update_stats_timer_metrics(&artnet_status_stats.recv_timer, &artnet_stats.recv, &artnet_status_metrics.recv_timer); + update_stats_counter_metrics(&artnet_status_stats.recv_poll_counter, &artnet_stats.recv_poll, &artnet_status_metrics.recv_poll_counter); + update_stats_counter_metrics(&artnet_status_stats.recv_dmx_counter, &artnet_stats.recv_dmx, &artnet_status_metrics.recv_dmx_counter); + update_stats_counter_metrics(&artnet_status_stats.recv_sync_counter, &artnet_stats.recv_sync, &artnet_status_metrics.recv_sync_counter); + update_stats_counter_metrics(&artnet_status_stats.dmx_discard_counter, &artnet_stats.dmx_discard, &artnet_status_metrics.dmx_discard_counter); + + for (unsigned i = 0; i < artnet_output_coumt; i++) { + struct artnet_output_stats artnet_output_stats; + + artnet_get_output_stats(artnet, i, &artnet_output_stats); + + update_stats_counter_metrics(&artnet_status_stats.outputs[i].dmx_counter, &artnet_output_stats.dmx_recv, &artnet_status_metrics.outputs[i].dmx_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_miss_counter, &artnet_output_stats.seq_miss, &artnet_status_metrics.outputs[i].seq_miss_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].seq_drop_counter, &artnet_output_stats.seq_drop, &artnet_status_metrics.outputs[i].seq_drop_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].update_counter, &artnet_output_stats.queue_update, &artnet_status_metrics.outputs[i].update_counter); + update_stats_counter_metrics(&artnet_status_stats.outputs[i].overflow_counter, &artnet_output_stats.queue_overflow, &artnet_status_metrics.outputs[i].overflow_counter); + } +} + +struct artnet_status get_artnet_status(struct artnet *artnet) +{ + update_artnet_status(artnet); + + return (struct artnet_status) { + .sync_mode = artnet_is_sync_state(artnet), + .metrics = artnet_status_metrics, + }; +} diff --git a/main/artnet_status.h b/main/artnet_status.h new file mode 100644 index 00000000..8e79a651 --- /dev/null +++ b/main/artnet_status.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +struct artnet_status_stats { + struct stats_timer recv_timer; + struct stats_counter recv_poll_counter; + struct stats_counter recv_dmx_counter; + struct stats_counter recv_sync_counter; + struct stats_counter dmx_discard_counter; + + struct artnet_status_output_stats { + struct stats_counter dmx_counter; + struct stats_counter seq_miss_counter; + struct stats_counter seq_drop_counter; + struct stats_counter update_counter; + struct stats_counter overflow_counter; + } outputs[ARTNET_OUTPUTS_MAX]; +}; + +struct artnet_status_metrics { + struct stats_timer_metrics recv_timer; + struct stats_counter_metrics recv_poll_counter; + struct stats_counter_metrics recv_dmx_counter; + struct stats_counter_metrics recv_sync_counter; + struct stats_counter_metrics dmx_discard_counter; + + struct artnet_status_output_metrics { + struct stats_counter_metrics dmx_counter; + struct stats_counter_metrics seq_miss_counter; + struct stats_counter_metrics seq_drop_counter; + struct stats_counter_metrics update_counter; + struct stats_counter_metrics overflow_counter; + } outputs[ARTNET_OUTPUTS_MAX]; +}; + +struct artnet_status { + bool sync_mode; + + struct artnet_status_metrics metrics; +}; + +struct artnet_status get_artnet_status(struct artnet *artnet); diff --git a/main/leds_cmd.c b/main/leds_cmd.c index 8253d7ea..51b75626 100644 --- a/main/leds_cmd.c +++ b/main/leds_cmd.c @@ -1,5 +1,6 @@ #include "leds.h" #include "leds_state.h" +#include "leds_status.h" #include "leds_artnet.h" #include "leds_test.h" #include "leds_config.h" @@ -56,54 +57,50 @@ int leds_cmd_info(int argc, char **argv, void *ctx) int leds_cmd_status(int argc, char **argv, void *ctx) { + struct leds_status status; + for (int i = 0; i < LEDS_COUNT; i++) { - const struct leds_config *config = &leds_configs[i]; struct leds_state *state = &leds_states[i]; - if (!config->enabled || !state->leds) { + if (!state->leds) { continue; } - printf("leds%d:\n", i + 1); - - bool active = leds_is_active(state->leds); - struct leds_limit_status limit_total_status; - struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; - size_t groups = LEDS_LIMIT_GROUPS_MAX; - TickType_t tick = xTaskGetTickCount(); + get_leds_status(&leds_states[i], &status); - leds_get_limit_total_status(state->leds, &limit_total_status); - leds_get_limit_groups_status(state->leds, limit_groups_status, &groups); + printf("leds%d:\n", i + 1); - printf("\tActive : %s\n", active ? "true" : "false"); - printf("\tUpdate : %dms\n", state->update_tick ? (tick - state->update_tick) * portTICK_RATE_MS : 0); - if (state->test) { + printf("\tActive : %s\n", status.active ? "true" : "false"); + printf("\tUpdate : %dms\n", status.update_tick ? (status.tick - status.update_tick) * portTICK_RATE_MS : 0); + printf("\tTask : %6.1f/s @ %5.1f%% (%.0fs)\n", status.metrics.task.rate, status.metrics.task.util * 100.0f, status.metrics.task.interval); + printf("\tInterface : %6.1f/s @ %5.1f%% (%.0fs)\n", status.metrics.interface.rate, status.metrics.interface.util * 100.0f, status.metrics.interface.interval); + if (status.test) { printf("\tTest:\n"); - printf("\t\tMode : %s\n", state->test->mode ? config_enum_to_string(leds_test_mode_enum, state->test->mode) : ""); + printf("\t\tMode : %s\n", status.test_mode ? config_enum_to_string(leds_test_mode_enum, status.test_mode) : ""); } - if (state->artnet) { + if (status.artnet) { printf("\tArt-Net:\n"); - printf("\t\tUpdate : %dms\n", state->artnet->dmx_tick ? (tick - state->artnet->dmx_tick) * portTICK_RATE_MS : 0); + printf("\t\tUpdate : %dms\n", status.artnet_dmx_tick ? (status.tick - status.artnet_dmx_tick) * portTICK_RATE_MS : 0); } printf("\tLimit:\n"); printf("\t\tTotal : count %5d power %5.1f%% limit %5.1f%% util %5.1f%% applied %5.1f%% output %5.1f%%\n", - limit_total_status.count, - leds_limit_status_power(&limit_total_status) * 100.0f, - leds_limit_status_limit(&limit_total_status) * 100.0f, - leds_limit_status_util(&limit_total_status) * 100.0f, - leds_limit_status_applied(&limit_total_status) * 100.0f, - leds_limit_status_output(&limit_total_status) * 100.0f + status.limit_total_status.count, + leds_limit_status_power(&status.limit_total_status) * 100.0f, + leds_limit_status_limit(&status.limit_total_status) * 100.0f, + leds_limit_status_util(&status.limit_total_status) * 100.0f, + leds_limit_status_applied(&status.limit_total_status) * 100.0f, + leds_limit_status_output(&status.limit_total_status) * 100.0f ); - for (unsigned j = 0; j < groups; j++) { + for (unsigned j = 0; j < status.limit_groups_count; j++) { printf("\t\tGroup[%2d]: count %5d power %5.1f%% limit %5.1f%% util %5.1f%% applied %5.1f%% output %5.1f%%\n", j, - limit_groups_status[j].count, - leds_limit_status_power(&limit_groups_status[j]) * 100.0f, - leds_limit_status_limit(&limit_groups_status[j]) * 100.0f, - leds_limit_status_util(&limit_groups_status[j]) * 100.0f, - leds_limit_status_applied(&limit_groups_status[j]) * 100.0f, - leds_limit_status_output(&limit_groups_status[j]) * 100.0f + status.limit_groups_status[j].count, + leds_limit_status_power(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_limit(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_util(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_applied(&status.limit_groups_status[j]) * 100.0f, + leds_limit_status_output(&status.limit_groups_status[j]) * 100.0f ); } @@ -366,6 +363,9 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) #endif #if LEDS_I2S_INTERFACE_COUNT > 0 + struct i2s_out_stats i2s0_stats = get_leds_i2s_out_stats(0); + + print_stats_timer("i2s0", "out", &i2s0_stats.out_timer); print_stats_timer("i2s0", "open", &stats.i2s0.open); print_stats_timer("i2s0", "write", &stats.i2s0.write); print_stats_timer("i2s0", "start", &stats.i2s0.start); @@ -373,6 +373,9 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) printf("\n"); #endif #if LEDS_I2S_INTERFACE_COUNT > 1 + struct i2s_out_stats i2s1_stats = get_leds_i2s_out_stats(1); + + print_stats_timer("i2s1", "out", &i2s1_stats.out_timer); print_stats_timer("i2s1", "open", &stats.i2s1.open); print_stats_timer("i2s1", "write", &stats.i2s1.write); print_stats_timer("i2s1", "start", &stats.i2s1.start); @@ -419,6 +422,7 @@ int leds_cmd_stats(int argc, char **argv, void *ctx) LOG_INFO("reset leds stats"); init_leds_stats(); + reset_leds_i2s_out_stats(); leds_reset_interface_stats(); } diff --git a/main/leds_http.c b/main/leds_http.c index 839f4244..eb00e0de 100644 --- a/main/leds_http.c +++ b/main/leds_http.c @@ -1,5 +1,6 @@ #include "leds.h" #include "leds_state.h" +#include "leds_status.h" #include "leds_artnet.h" #include "leds_test.h" #include "leds_config.h" @@ -40,11 +41,11 @@ static int leds_api_write_object_leds_limit_status(struct json_writer *w, const ); } -static int leds_api_write_object_leds_limit_status_groups(struct json_writer *w, const struct leds_limit_status *groups_status, size_t groups) +static int leds_api_write_object_leds_limit_status_groups(struct json_writer *w, const struct leds_limit_status *groups_status, size_t count) { int err; - for (unsigned i = 0; i < groups; i++) { + for (unsigned i = 0; i < count; i++) { if ((err = JSON_WRITE_OBJECT(w, leds_api_write_object_leds_limit_status(w, &groups_status[i])))) { return err; } @@ -53,25 +54,33 @@ static int leds_api_write_object_leds_limit_status_groups(struct json_writer *w, return 0; } +static int leds_api_write_object_status_timer_metrics(struct json_writer *w, const struct stats_timer_metrics *metrics) +{ + return ( + JSON_WRITE_MEMBER_FLOAT(w, "interval", metrics->interval) + || JSON_WRITE_MEMBER_FLOAT(w, "rate", metrics->rate) + || JSON_WRITE_MEMBER_FLOAT(w, "util", metrics->util) + ); +} + static int leds_api_write_object_status(struct json_writer *w, struct leds_state *state) { - struct leds_limit_status limit_total_status; - struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; - size_t groups = LEDS_LIMIT_GROUPS_MAX; - bool active = leds_is_active(state->leds); - TickType_t tick = xTaskGetTickCount(); + struct leds_status status; - leds_get_limit_total_status(state->leds, &limit_total_status); - leds_get_limit_groups_status(state->leds, limit_groups_status, &groups); + get_leds_status(state, &status); return ( - JSON_WRITE_MEMBER_BOOL(w, "active", active) - || JSON_WRITE_MEMBER_UINT(w, "update_tick", state->update_tick) - || JSON_WRITE_MEMBER_UINT(w, "update_ms", TICK_MS(tick, state->update_tick)) - || JSON_WRITE_MEMBER_UINT(w, "artnet_dmx_ms", state->artnet ? TICK_MS(tick, state->artnet->dmx_tick) : 0) - || JSON_WRITE_MEMBER_STRING(w, "test_mode", (state->test && state->test->mode) ? config_enum_to_string(leds_test_mode_enum, state->test->mode) : "") - || JSON_WRITE_MEMBER_OBJECT(w, "limit_total", leds_api_write_object_leds_limit_status(w, &limit_total_status)) - || JSON_WRITE_MEMBER_ARRAY(w, "limit_groups", leds_api_write_object_leds_limit_status_groups(w, limit_groups_status, groups)) + JSON_WRITE_MEMBER_BOOL(w, "active", status.active) + || JSON_WRITE_MEMBER_UINT(w, "update_tick", status.update_tick) + || JSON_WRITE_MEMBER_UINT(w, "update_ms", TICK_MS(status.tick, status.update_tick)) + || JSON_WRITE_MEMBER_UINT(w, "artnet_dmx_ms", status.artnet ? TICK_MS(status.tick, status.artnet_dmx_tick) : 0) + || JSON_WRITE_MEMBER_OBJECT(w, "metrics", + JSON_WRITE_MEMBER_OBJECT(w, "task", leds_api_write_object_status_timer_metrics(w, &status.metrics.task)) + || JSON_WRITE_MEMBER_OBJECT(w, "interface", leds_api_write_object_status_timer_metrics(w, &status.metrics.interface)) + ) + || JSON_WRITE_MEMBER_STRING(w, "test_mode", (status.test && status.test_mode) ? config_enum_to_string(leds_test_mode_enum, status.test_mode) : "") + || JSON_WRITE_MEMBER_OBJECT(w, "limit_total", leds_api_write_object_leds_limit_status(w, &status.limit_total_status)) + || JSON_WRITE_MEMBER_ARRAY(w, "limit_groups", leds_api_write_object_leds_limit_status_groups(w, status.limit_groups_status, status.limit_groups_count)) ); } diff --git a/main/leds_i2s.h b/main/leds_i2s.h new file mode 100644 index 00000000..cbe436f5 --- /dev/null +++ b/main/leds_i2s.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +extern struct i2s_out *leds_i2s_out[LEDS_I2S_INTERFACE_COUNT]; diff --git a/main/leds_state.h b/main/leds_state.h index 5a205e3e..84c27494 100644 --- a/main/leds_state.h +++ b/main/leds_state.h @@ -2,6 +2,7 @@ #include #include "leds.h" +#include "leds_status.h" #include "user.h" #include @@ -27,6 +28,9 @@ struct leds_state { struct leds_test_state *test; struct leds_artnet_state *artnet; struct leds_sequence_state *sequence; + + struct leds_status_timers status_timers; + struct leds_status_timer_metrics status_timer_metrics; }; extern struct leds_state leds_states[LEDS_COUNT]; diff --git a/main/leds_stats.c b/main/leds_stats.c index be472ca5..610f3d70 100644 --- a/main/leds_stats.c +++ b/main/leds_stats.c @@ -1,4 +1,5 @@ #include "leds_stats.h" +#include "leds_i2s.h" struct leds_sequence_stats leds_sequence_stats; struct leds_stats leds_stats[LEDS_COUNT]; @@ -31,3 +32,29 @@ void init_leds_stats() stats_counter_init(&stats->update_timeout); } } + +struct i2s_out_stats get_leds_i2s_out_stats(unsigned port) +{ + struct i2s_out *i2s_out = NULL; + + if (port < LEDS_I2S_INTERFACE_COUNT) { + i2s_out = leds_i2s_out[port]; + } + + if (i2s_out) { + return i2s_out_stats(i2s_out); + } else { + return (struct i2s_out_stats) {}; + } +} + +void reset_leds_i2s_out_stats() +{ + struct i2s_out *i2s_out = NULL; + + for (unsigned port = 0; port < LEDS_I2S_INTERFACE_COUNT; port++) { + if ((i2s_out = leds_i2s_out[port])) { + i2s_out_reset_stats(i2s_out); + } + } +} diff --git a/main/leds_stats.h b/main/leds_stats.h index 79a6038b..faf35118 100644 --- a/main/leds_stats.h +++ b/main/leds_stats.h @@ -3,6 +3,7 @@ #include "leds.h" #include +#include struct leds_sequence_stats { struct stats_timer read; @@ -33,3 +34,6 @@ extern struct leds_sequence_stats leds_sequence_stats; extern struct leds_stats leds_stats[LEDS_COUNT]; void init_leds_stats(); + +struct i2s_out_stats get_leds_i2s_out_stats(unsigned port); +void reset_leds_i2s_out_stats(); diff --git a/main/leds_status.c b/main/leds_status.c new file mode 100644 index 00000000..33cd49f5 --- /dev/null +++ b/main/leds_status.c @@ -0,0 +1,78 @@ +#include "leds_status.h" +#include "leds_state.h" +#include "leds_stats.h" +#include "leds_test.h" +#include "leds_artnet.h" +#include "leds.h" + +static struct stats_timer get_leds_task_timer(struct leds_state *state) +{ + const struct leds_stats *stats = &leds_stats[state->index]; + + return stats->loop; +} + +static struct stats_timer get_leds_interface_timer(struct leds_state *state) +{ + enum leds_interface li = leds_interface(state->leds); + struct i2s_out_stats i2s_out_stats; + + switch (li) { + case LEDS_INTERFACE_I2S0: + case LEDS_INTERFACE_I2S1: + i2s_out_stats = get_leds_i2s_out_stats(leds_interface_i2s_port(li)); + + return i2s_out_stats.out_timer; + + default: + return (struct stats_timer) {}; + } +} + +// basic moving average +static void update_stats_timer_metrics(struct stats_timer *baseline, const struct stats_timer *timer, struct stats_timer_metrics *avg) +{ + struct stats_timer_metrics metrics = stats_timer_diff_metrics(baseline, timer); + + *avg = stats_timer_metrics_average(avg, &metrics); + + *baseline = *timer; +} + +void update_leds_status(struct leds_state *state) +{ + struct stats_timer task_timer = get_leds_task_timer(state); + struct stats_timer interface_timer = get_leds_interface_timer(state); + + update_stats_timer_metrics(&state->status_timers.task, &task_timer, &state->status_timer_metrics.task); + update_stats_timer_metrics(&state->status_timers.interface, &interface_timer, &state->status_timer_metrics.interface); +} + +void get_leds_status(struct leds_state *state, struct leds_status *status) +{ + *status = (struct leds_status) {}; + + // TODO: timer interval? + update_leds_status(state); + + status->tick = xTaskGetTickCount(); + status->update_tick = state->update_tick; + status->active = leds_is_active(state->leds); + + if ((status->test = !!state->test)) { + status->test_mode = state->test->mode; + } + + if ((status->artnet = !!state->artnet)) { + status->artnet_dmx_tick = state->artnet->dmx_tick; + } + + // limits + status->limit_groups_count = LEDS_LIMIT_GROUPS_MAX; + + leds_get_limit_total_status(state->leds, &status->limit_total_status); + leds_get_limit_groups_status(state->leds, status->limit_groups_status, &status->limit_groups_count); + + // metrics + status->metrics = state->status_timer_metrics; +} diff --git a/main/leds_status.h b/main/leds_status.h new file mode 100644 index 00000000..1c4e34ee --- /dev/null +++ b/main/leds_status.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include "leds_config.h" + +struct leds_state; + +struct leds_status_timers { + struct stats_timer task; + struct stats_timer interface; +}; + +struct leds_status_timer_metrics { + struct stats_timer_metrics task; + struct stats_timer_metrics interface; +}; + +struct leds_status { + TickType_t tick; + + bool active; + TickType_t update_tick; + + bool test; + enum leds_test_mode test_mode; + + bool artnet; + TickType_t artnet_dmx_tick; + + struct leds_limit_status limit_total_status; + struct leds_limit_status limit_groups_status[LEDS_LIMIT_GROUPS_MAX]; + size_t limit_groups_count; + + struct leds_status_timer_metrics metrics; +}; + +void get_leds_status(struct leds_state *leds, struct leds_status *status); diff --git a/main/leds_task.c b/main/leds_task.c index 21ead1ff..b4147368 100644 --- a/main/leds_task.c +++ b/main/leds_task.c @@ -107,14 +107,16 @@ static void leds_main(void *ctx) goto error; } - for(struct stats_timer_sample loop_sample;; stats_timer_stop(&stats->loop, &loop_sample)) { + for(stats_timer_start_t loop_start;; stats_timer_stop(&stats->loop, &loop_start)) { EventBits_t event_bits = leds_task_wait(state); enum user_activity update_activity = 0; bool update_timeout = false; - loop_sample = stats_timer_start(&stats->loop); + loop_start = stats_timer_start(&stats->loop); if (state->sequence && leds_sequence_active(state, event_bits)) { + LOG_DEBUG("sequence"); + WITH_STATS_TIMER(&stats->sequence) { if (leds_sequence_update(state, event_bits)) { update_activity = USER_ACTIVITY_LEDS_SEQUENCE; @@ -123,6 +125,8 @@ static void leds_main(void *ctx) } if (state->artnet && leds_artnet_active(state, event_bits)) { + LOG_DEBUG("artnet"); + WITH_STATS_TIMER(&stats->artnet) { switch (leds_artnet_update(state, event_bits)) { case 0: @@ -143,6 +147,8 @@ static void leds_main(void *ctx) } if (state->test && leds_test_active(state, event_bits)) { + LOG_DEBUG("test"); + WITH_STATS_TIMER(&stats->test) { if (leds_test_update(state, event_bits)) { update_activity = USER_ACTIVITY_LEDS_TEST; @@ -151,15 +157,17 @@ static void leds_main(void *ctx) } if (leds_update_active(state)) { + LOG_DEBUG("update timeout"); + // update without activity update_timeout = true; - LOG_DEBUG("update timeout"); - stats_counter_increment(&stats->update_timeout); } if (update_activity || update_timeout) { + LOG_DEBUG("update"); + state->update_tick = xTaskGetTickCount(); WITH_STATS_TIMER(&stats->update) { diff --git a/web/src/components/ArtNetView.vue b/web/src/components/ArtNetView.vue index dac81b8f..d9734204 100644 --- a/web/src/components/ArtNetView.vue +++ b/web/src/components/ArtNetView.vue @@ -7,11 +7,11 @@