health: a CLI dump of where the main loop's time goes

Every probe of a misbehaving switch so far measured it from the
outside. This puts the witnesses inside.

Each idle() pass increments a loop counter and stamps a checkpoint
after every phase: link polling, SFP, RX, TX, STP and the command
dispatch. A phase spanning two or more system ticks counts as slow and
its worst time is kept, in 5 ms units. handle_rx counts frames, so the
dump separates a flooded CPU from a stalled loop.

At boot the free stack area is painted, preserving the interrupt state
around the painting, and the dump reports how much of the pattern
survives next to the live stack pointer. The TCP connection table is
printed whole: state, timers, retransmissions, ports and peer, next to
the httpd transfer state, so a permanently occupied slot names its
occupant. With more than one slot configured every slot is listed.

Everything is raw hex; two dumps a known time apart give the rates.
The implementation sits in cmd_parser next to the other command
handlers and needs no storage beyond its counters.
This commit is contained in:
d00f authored and DrDoof committed 2026-09-21 22:25:32 +02:00
1 parent 4bd6ffd083
commit d37ce07119
3 files changed
+185

No files matched your search

+159
View File
@@ -5,6 +5,8 @@
// #define DEBUG // #define DEBUG
// #define REGDBG 1 // #define REGDBG 1
#include <8051.h>
#include "cmd_parser.h"
#include "rtl837x_common.h" #include "rtl837x_common.h"
#include "rtl837x_port.h" #include "rtl837x_port.h"
#include "rtl837x_flash.h" #include "rtl837x_flash.h"
@@ -1847,6 +1849,8 @@ void cmd_parser(void) __banked
} }
} else if (cmd_compare(0, "stp")) { } else if (cmd_compare(0, "stp")) {
stp_parse(); stp_parse();
} else if (cmd_compare(0, "health")) {
health_show();
} else if (cmd_compare(0, "pvid")) { } else if (cmd_compare(0, "pvid")) {
if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0 if (cmd_words_len == 3 && cmd_parse_port_separator(cmd_words_b[1]) != 0
&& atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094) && atoi_short(cmd_words_b[2]) && atoi_results_short && atoi_results_short <= 4094)
@@ -2045,3 +2049,158 @@ void execute_commands(__xdata uint8_t *p) __banked {
p++; p++;
}; };
} }
/*
* Health instrumentation. The counters live in xdata and are fed from the
* main loop through the banked entry points below; the "health" command
* prints everything in one go. All figures are raw hex, diffing two dumps
* gives the rates. Phase times are in system ticks (SYS_TICK_HZ = 200,
* 5 ms each); a phase counts as slow when it spans 2 or more ticks.
*/
extern __xdata uint16_t len_left; /* httpd: bytes still to send of the current file */
extern __xdata uint8_t entry; /* httpd: index of the file being sent */
static __xdata uint32_t health_loops;
__xdata uint16_t health_rx_frames;
static __xdata uint16_t ph_last_t;
static __xdata uint8_t ph_max[HEALTH_PHASES];
static __xdata uint16_t ph_slow[HEALTH_PHASES];
/* The ISR increments ticks between the two byte reads of a 16-bit load, so
* read until two loads agree. */
static uint16_t ticks_lo(void)
{
uint16_t a, b;
a = (uint16_t)ticks;
do {
b = a;
a = (uint16_t)ticks;
} while (a != b);
return a;
}
void health_loop_start(void) __banked
{
health_loops++;
ph_last_t = ticks_lo();
}
void health_phase(uint8_t id) __banked
{
__xdata uint16_t now = ticks_lo();
__xdata uint16_t d = now - ph_last_t;
ph_last_t = now;
if (d > 0xff)
d = 0xff;
if ((uint8_t)d > ph_max[id])
ph_max[id] = d;
if (d >= 2)
ph_slow[id]++;
}
/* Fill the stack area above the current frame with a pattern; the dump scans
* for how much of it survived. The 8051 stack grows upward, so everything
* above SP is free at this point. Interrupts are held off so an ISR does not
* push into the area mid-paint. */
void health_stack_paint(void) __banked
{
__xdata uint8_t from = SP + 4;
__xdata uint8_t ea = EA;
EA = 0;
for (uint8_t i = 0xff; i >= from; i--)
*(__idata uint8_t *)i = 0xa5;
EA = ea;
}
static void ph_line(__code const char *name, uint8_t id)
{
print_string(name);
print_string(" max ");
print_byte(ph_max[id]);
print_string(" slow ");
print_short(ph_slow[id]);
write_char('\n');
}
void health_show(void) __banked
{
__xdata uint8_t i;
print_string("up ");
reg_read_m(RTL837X_REG_SEC_COUNTER);
print_byte(sfr_data[0]); print_byte(sfr_data[1]);
print_byte(sfr_data[2]); print_byte(sfr_data[3]);
print_string(" ticks ");
print_long(ticks);
print_string(" loops ");
print_long(health_loops);
print_string(" rx ");
print_short(health_rx_frames);
write_char('\n');
/* Phase timing: max duration seen (ticks of 5 ms) and slow-pass count */
ph_line("link", HEALTH_PH_LINK);
ph_line("sfp ", HEALTH_PH_SFP);
ph_line("rx ", HEALTH_PH_RX);
ph_line("tx ", HEALTH_PH_TX);
ph_line("stp ", HEALTH_PH_STP);
ph_line("cmd ", HEALTH_PH_CMD);
/* Stack: current depth plus how much of the painted area is untouched */
print_string("sp ");
print_byte(SP);
print_string(" untouched ");
i = 0;
while (*(__idata uint8_t *)(0xff - i) == 0xa5 && i < 0x86)
i++;
print_byte(i);
write_char('\n');
/* The TCP connection slots (a single one on this configuration) */
for (i = 0; i < UIP_CONNS; i++) {
print_string("tcp ");
if (UIP_CONNS > 1) {
print_byte(i);
write_char(' ');
}
print_string("st ");
print_byte(uip_conns[i].tcpstateflags);
print_string(" tmr ");
print_byte(uip_conns[i].timer);
print_string(" rtx ");
print_byte(uip_conns[i].nrtx);
print_string(" rto ");
print_byte(uip_conns[i].rto);
print_string(" len ");
print_short(uip_conns[i].len);
print_string(" mss ");
print_short(uip_conns[i].mss);
write_char('\n');
print_string(" lport ");
print_short(HTONS(uip_conns[i].lport));
print_string(" rport ");
print_short(HTONS(uip_conns[i].rport));
print_string(" rip ");
print_short(HTONS(uip_conns[i].ripaddr[0]));
write_char(' ');
print_short(HTONS(uip_conns[i].ripaddr[1]));
write_char('\n');
}
/* httpd transfer state and the protocol flags */
print_string("httpd left ");
print_short(len_left);
print_string(" entry ");
print_byte(entry);
print_string(" stp ");
print_byte(stp_enabled);
print_string(" mvlan ");
print_short(management_vlan);
write_char('\n');
}
+16
View File
@@ -9,6 +9,22 @@ extern __xdata uint8_t cmd_buffer[CMD_BUF_SIZE];
extern __xdata uint8_t cmd_available; extern __xdata uint8_t cmd_available;
extern __xdata uint8_t err_status; extern __xdata uint8_t err_status;
/* Phase ids of the health instrumentation, in main-loop order */
#define HEALTH_PH_LINK 0
#define HEALTH_PH_SFP 1
#define HEALTH_PH_RX 2
#define HEALTH_PH_TX 3
#define HEALTH_PH_STP 4
#define HEALTH_PH_CMD 5
#define HEALTH_PHASES 6
extern __xdata uint16_t health_rx_frames;
void health_loop_start(void) __banked; /* once per idle() pass, resets the phase baseline */
void health_phase(uint8_t id) __banked; /* after each phase; accounts ticks since last checkpoint */
void health_stack_paint(void) __banked; /* paint the unused stack area once at boot */
void health_show(void) __banked; /* "health" CLI dump */
void cmd_tokenize(void) __banked; void cmd_tokenize(void) __banked;
void cmd_parser(void) __banked; void cmd_parser(void) __banked;
void execute_config(void) __banked; void execute_config(void) __banked;
+10
View File
@@ -1004,6 +1004,7 @@ void handle_rx(void)
REG_SET(RTL837X_REG_NIC_RXCMD, 1); REG_SET(RTL837X_REG_NIC_RXCMD, 1);
return; return;
} }
health_rx_frames++;
#ifdef RXTXDBG #ifdef RXTXDBG
print_string("\n<< "); print_string("\n<< ");
@@ -1165,6 +1166,7 @@ void idle(void)
reg_read(RTL837X_REG_NIC_RX_BUFF_DATA); reg_read(RTL837X_REG_NIC_RX_BUFF_DATA);
if (!SFR_DATA_U16) if (!SFR_DATA_U16)
PCON |= 1; PCON |= 1;
health_loop_start();
if (sec_counter >= SYS_TICK_HZ) { if (sec_counter >= SYS_TICK_HZ) {
sec_counter -= SYS_TICK_HZ; sec_counter -= SYS_TICK_HZ;
reg_read_m(RTL837X_REG_SEC_COUNTER); reg_read_m(RTL837X_REG_SEC_COUNTER);
@@ -1240,13 +1242,18 @@ void idle(void)
} }
} }
health_phase(HEALTH_PH_LINK);
// Check for changes with SFP modules // Check for changes with SFP modules
handle_sfp(); handle_sfp();
health_phase(HEALTH_PH_SFP);
// Check new Packets RX // Check new Packets RX
handle_rx(); handle_rx();
health_phase(HEALTH_PH_RX);
// Check UIP for packets to transmit // Check UIP for packets to transmit
handle_tx(); handle_tx();
health_phase(HEALTH_PH_TX);
// If STP protocol enabled, decrease STP timers to trigger actions // If STP protocol enabled, decrease STP timers to trigger actions
if (stp_enabled) { if (stp_enabled) {
if (!stp_clock) { if (!stp_clock) {
@@ -1256,6 +1263,7 @@ void idle(void)
stp_clock--; stp_clock--;
} }
} }
health_phase(HEALTH_PH_STP);
// Check whether a command is waiting in the cmd_buffer and execute // Check whether a command is waiting in the cmd_buffer and execute
if (cmd_available) { if (cmd_available) {
cmd_available = 0; cmd_available = 0;
@@ -1264,6 +1272,7 @@ void idle(void)
cmd_parser(); cmd_parser();
print_cmd_prompt(); print_cmd_prompt();
} }
health_phase(HEALTH_PH_CMD);
} }
@@ -1758,6 +1767,7 @@ void main(void)
cmd_editor_init(); cmd_editor_init();
health_stack_paint();
while (1) { while (1) {
cmd_edit(); cmd_edit();
idle(); // Enter Idle mode until interrupt occurs idle(); // Enter Idle mode until interrupt occurs