Browse Source

восстановление морды (частичное)

nodeinfo-routing-update
jeka 7 months ago
parent
commit
b8ea33160d
  1. 1
      AGENTS.md
  2. 8
      src/control_server.c
  3. 82
      src/route_bgp.c
  4. 6
      tests/Makefile.am
  5. 40
      tools/etcpmon/Makefile
  6. 285
      tools/etcpmon/etcpmon_graph.c
  7. 44
      tools/etcpmon/etcpmon_graph.h
  8. 98
      tools/etcpmon/etcpmon_gui.c
  9. 15
      tools/etcpmon/etcpmon_gui.h

1
AGENTS.md

@ -12,7 +12,6 @@ This file contains essential information for AI coding agents working in the uTu
## 🔧 Build Commands ## 🔧 Build Commands
### Full Build ### Full Build
win (msys2 ucrt64): build.bat
```bash ```bash
./autogen.sh # Generate configure script (if needed) ./autogen.sh # Generate configure script (if needed)
./configure # Configure build ./configure # Configure build

8
src/control_server.c

@ -238,7 +238,7 @@ void control_server_shutdown(struct control_server* server) {
} }
/* Close listening socket */ /* Close listening socket */
if (server->listen_fd >= 0) { if (server->listen_fd != INVALID_SOCKET) {
uasync_remove_socket_t(server->ua, server->listen_fd); uasync_remove_socket_t(server->ua, server->listen_fd);
server->listen_socket_id = NULL; server->listen_socket_id = NULL;
} }
@ -419,10 +419,6 @@ static void client_read_callback(socket_t fd, void* arg) {
if (received == SOCKET_ERROR) { if (received == SOCKET_ERROR) {
int err = WSAGetLastError(); int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) { if (err != WSAEWOULDBLOCK) {
if (err == 10054) {
client->connected = 0;
return;
}
if (server && server->log_file) { if (server && server->log_file) {
fprintf(server->log_file, "%llu: [ERROR] Client recv error: %d\n", fprintf(server->log_file, "%llu: [ERROR] Client recv error: %d\n",
(unsigned long long)get_timestamp_ms(), err); (unsigned long long)get_timestamp_ms(), err);
@ -508,7 +504,7 @@ static void close_client(struct control_server* server, struct control_client* c
} }
/* Remove from uasync */ /* Remove from uasync */
if (client->fd >= 0) { if (client->fd != INVALID_SOCKET) {
uasync_remove_socket_t(server->ua, client->fd); uasync_remove_socket_t(server->ua, client->fd);
} }

82
src/route_bgp.c

@ -22,12 +22,6 @@
// Размер пакета (без заголовка ETCP) // Размер пакета (без заголовка ETCP)
#define BGP_PACKET_SIZE (sizeof(struct ROUTE_BGP_PACKET)) #define BGP_PACKET_SIZE (sizeof(struct ROUTE_BGP_PACKET))
static inline void format_network(uint32_t network, char* out, size_t out_size)
{
uint32_t addr = htonl(network);
inet_ntop(AF_INET, &addr, out, out_size);
}
// Forward declarations // Forward declarations
static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
const struct ROUTE_ENTRY* route); const struct ROUTE_ENTRY* route);
@ -70,20 +64,11 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry*
return; return;
} }
char network_str[INET_ADDRSTRLEN]; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet: subcmd=%d, network=%08x/%d, node_id=%016llx",
format_network(ntohl(pkt->network), network_str, sizeof(network_str)); pkt->subcmd, ntohl(pkt->network), pkt->prefix_length & 0x3F, (unsigned long long)pkt->node_id);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet: subcmd=%d, network=%s/%d, node_id=%016llx",
pkt->subcmd, network_str, pkt->prefix_length & 0x3F, (unsigned long long)pkt->node_id);
switch (pkt->subcmd) { switch (pkt->subcmd) {
case ROUTE_SUBCMD_ENTRY: { case ROUTE_SUBCMD_ENTRY: {
// Игнорируем маршруты от себя
if (pkt->node_id == instance->node_id) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Ignoring own route: %s/%d", network_str, pkt->prefix_length & 0x3F);
break;
}
// Создаем запись маршрута // Создаем запись маршрута
struct ROUTE_ENTRY route; struct ROUTE_ENTRY route;
memset(&route, 0, sizeof(route)); memset(&route, 0, sizeof(route));
@ -112,44 +97,32 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry*
} }
// Вставляем в таблицу // Вставляем в таблицу
char network_str[INET_ADDRSTRLEN];
format_network(route.network, network_str, sizeof(network_str));
if (route_table_insert(instance->rt, &route)) { if (route_table_insert(instance->rt, &route)) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added learned route: %s/%d via node %016llx (hops=%d)", DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added learned route: %08x/%d via node %016llx (hops=%d)",
network_str, route.prefix_length, route.network, route.prefix_length,
(unsigned long long)route.destination_node_id, (unsigned long long)route.destination_node_id,
route.metrics.hop_count); route.metrics.hop_count);
} else { } else {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Failed to insert route: %s/%d", DEBUG_WARN(DEBUG_CATEGORY_BGP, "Failed to insert route: %08x/%d",
network_str, route.prefix_length); route.network, route.prefix_length);
} }
break; break;
} }
case ROUTE_SUBCMD_WITHDRAW: { case ROUTE_SUBCMD_WITHDRAW: {
// Игнорируем withdraw от себя
if (pkt->node_id == instance->node_id) {
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Ignoring own withdraw: %s/%d", network_str, pkt->prefix_length & 0x3F);
break;
}
// Удаление маршрута (withdrawal) // Удаление маршрута (withdrawal)
uint32_t network = ntohl(pkt->network); uint32_t network = ntohl(pkt->network);
uint8_t prefix_length = pkt->prefix_length & 0x3F; uint8_t prefix_length = pkt->prefix_length & 0x3F;
char network_str[INET_ADDRSTRLEN]; DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing route withdrawal: %08x/%d from conn %p",
format_network(network, network_str, sizeof(network_str)); network, prefix_length, (void*)from_conn);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing route withdrawal: %s/%d from conn %p",
network_str, prefix_length, (void*)from_conn);
if (route_table_delete_entry(instance->rt, network, prefix_length, from_conn)) { if (route_table_delete_entry(instance->rt, network, prefix_length, from_conn)) {
DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route withdrawn successfully: %s/%d", DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route withdrawn successfully: %08x/%d",
network_str, prefix_length); network, prefix_length);
} else { } else {
DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route to withdraw not found: %s/%d", DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route to withdraw not found: %08x/%d",
network_str, prefix_length); network, prefix_length);
} }
break; break;
} }
@ -181,11 +154,6 @@ static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* con
return; return;
} }
// Не отправляем withdraw автору маршрута
if (route->destination_node_id == conn->peer_node_id) {
return;
}
// Создаем ETCP пакет // Создаем ETCP пакет
struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET)); struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET));
if (!entry) { if (!entry) {
@ -207,11 +175,8 @@ static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* con
entry->len = sizeof(struct ROUTE_BGP_PACKET); entry->len = sizeof(struct ROUTE_BGP_PACKET);
char network_str[INET_ADDRSTRLEN]; DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending withdraw: %08x/%d to conn %p",
format_network(route->network, network_str, sizeof(network_str)); route->network, route->prefix_length, (void*)conn);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending withdraw: %s/%d to conn %p",
network_str, route->prefix_length, (void*)conn);
// Отправляем через ETCP // Отправляем через ETCP
if (etcp_send(conn, entry) != 0) { if (etcp_send(conn, entry) != 0) {
@ -273,11 +238,8 @@ static void route_bgp_on_route_change(struct ROUTE_TABLE* table,
// Не рассылаем маршруты через которые мы их получили (чтобы избежать петель) // Не рассылаем маршруты через которые мы их получили (чтобы избежать петель)
// Это делается в цикле ниже // Это делается в цикле ниже
char network_str[INET_ADDRSTRLEN]; DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Route change: action=%d, network=%08x/%d, type=%d",
format_network(entry->network, network_str, sizeof(network_str)); action, entry->network, entry->prefix_length, entry->type);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Route change: action=%d, network=%s/%d, type=%d",
action, network_str, entry->prefix_length, entry->type);
// Перебираем все соединения в senders_list // Перебираем все соединения в senders_list
struct ll_entry* item_entry = bgp->senders_list->head; struct ll_entry* item_entry = bgp->senders_list->head;
@ -310,11 +272,6 @@ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
return; return;
} }
// Не отправляем маршрут автору этого маршрута
if (route->destination_node_id == conn->peer_node_id) {
return;
}
// Пропускаем неактивные маршруты // Пропускаем неактивные маршруты
if (!(route->flags & ROUTE_FLAG_ACTIVE)) { if (!(route->flags & ROUTE_FLAG_ACTIVE)) {
return; return;
@ -356,11 +313,8 @@ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn,
entry->len = sizeof(struct ROUTE_BGP_PACKET); entry->len = sizeof(struct ROUTE_BGP_PACKET);
char network_str[INET_ADDRSTRLEN]; DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending route: %08x/%d to conn %p",
format_network(route->network, network_str, sizeof(network_str)); route->network, route->prefix_length & 0x3F, (void*)conn);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending route: %s/%d to conn %p",
network_str, route->prefix_length & 0x3F, (void*)conn);
// Отправляем через ETCP // Отправляем через ETCP
if (etcp_send(conn, entry) != 0) { if (etcp_send(conn, entry) != 0) {

6
tests/Makefile.am

@ -23,6 +23,7 @@ check_PROGRAMS = \
test_route_lib \ test_route_lib \
test_bgp_route_exchange \ test_bgp_route_exchange \
test_routing_mesh \ test_routing_mesh \
test_control_server \
bench_timeout_heap \ bench_timeout_heap \
bench_uasync_timeouts bench_uasync_timeouts
@ -204,6 +205,11 @@ bench_uasync_timeouts_SOURCES = bench_uasync_timeouts.c
bench_uasync_timeouts_CFLAGS = -I$(top_srcdir)/lib bench_uasync_timeouts_CFLAGS = -I$(top_srcdir)/lib
bench_uasync_timeouts_LDADD = $(COMMON_LIBS) bench_uasync_timeouts_LDADD = $(COMMON_LIBS)
# Control Server Test - Tests the ETCP monitoring protocol
test_control_server_SOURCES = test_control_server.c
test_control_server_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tools/etcpmon
test_control_server_LDADD = $(COMMON_LIBS)
# Build tinycrypt objects before tests that need them # Build tinycrypt objects before tests that need them
BUILT_SOURCES = $(TINYCRYPT_BUILT) BUILT_SOURCES = $(TINYCRYPT_BUILT)

40
tools/etcpmon/Makefile

@ -0,0 +1,40 @@
# ETCP Monitor Makefile for MinGW
# Usage: make [CC=gcc] [CFLAGS=...] [LDFLAGS=...]
# Compiler
CC ?= gcc
# Target
TARGET = etcpmon.exe
# Source files
SRCS = etcpmon_main.c \
etcpmon_gui.c \
etcpmon_client.c \
etcpmon_graph.c
# Object files
OBJS = $(SRCS:.c=.o)
# Windows-specific libraries
LIBS = -lws2_32 -lgdi32 -lcomdlg32 -lkernel32 -luser32 -lcomctl32
# Compiler flags
CFLAGS += -Wall -Wextra
CFLAGS += -O2
CFLAGS += -D_WIN32_WINNT=0x0601
CFLAGS += -DUNICODE -D_UNICODE
CFLAGS += -I.
# Build rule
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS) $(LIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET) *.o

285
tools/etcpmon/etcpmon_graph.c

@ -0,0 +1,285 @@
/*
* etcpmon_graph.c - ETCP Monitor Graph (20 SPS, 1px=1sample, left scroll, low CPU)
*
* Изменения по твоему запросу:
* - Вертикальная линия курсора остаётся
* - Строка с значениями под курсором ("RTT-L: 45.2 ...") полностью убрана
* - Значения в блоках под графиком всегда живые (обновляются в on_metrics)
*/
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include "etcpmon_graph.h"
#include "etcpmon_client.h"
#include <stdlib.h>
#include <stdio.h>
#include "etcpmon_gui.h"
const COLORREF graph_colors[GRAPH_METRICS_COUNT] = {
RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), RGB(255,255,0),
RGB(255,0,255), RGB(0,255,255), RGB(255,128,0), RGB(128,0,255)
};
const char* graph_metric_names[GRAPH_METRICS_COUNT] = {
"RTT Last","RTT Avg10","RTT Avg100","Jitter",
"Retrns","ACKs","Inflght","Bytes/s"
};
static void RecalculateMinMax(struct metrics_history* hist, int vis, int oldest)
{
for (int m = 0; m < GRAPH_METRICS_COUNT; m++) {
float mn = 1e9f;
float mx = -1e9f;
for (int i = 0; i < vis; i++) {
float v = hist->values[m][(oldest + i) % GRAPH_HISTORY_SIZE];
if (v < mn) mn = v;
if (v > mx) mx = v;
}
if (mx - mn < 0.001f) {
if (mn > 0.0f) { mn *= 0.9f; mx *= 1.1f; }
else { mx = 1.0f; }
}
hist->min_val[m] = mn;
hist->max_val[m] = mx;
}
}
void GetSelectedChannels(struct etcpmon_app* app, int* selected, int* count) {
*count = 0;
if (!app) return;
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) {
if (app->hChannelCheck[i] &&
SendMessageA(app->hChannelCheck[i], BM_GETCHECK, 0, 0) == BST_CHECKED) {
selected[(*count)++] = i;
}
}
if (*count == 0)
for (int i = 0; i < 4 && i < GRAPH_METRICS_COUNT; i++)
selected[(*count)++] = i;
}
void DrawGraph(HDC hdc, RECT* rc, struct etcpmon_app* app)
{
if (!app || !rc) return;
int w = rc->right - rc->left;
int h = rc->bottom - rc->top;
/* Off-screen буфер */
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h);
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, bmp);
SetBkMode(memDC, TRANSPARENT);
/* Фон */
HBRUSH bg = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
FillRect(memDC, rc, bg);
DeleteObject(bg);
/* Нет данных */
if (!app->client || !etcpmon_client_get_history(app->client) ||
etcpmon_client_get_history(app->client)->count < 2)
{
SetTextColor(memDC, RGB(128, 128, 128));
DrawTextA(memDC, "Waiting for data...", -1, rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
goto end;
}
struct metrics_history* hist = etcpmon_client_get_history(app->client);
/* Выбранные каналы */
int sel[GRAPH_METRICS_COUNT];
int sc = 0;
GetSelectedChannels(app, sel, &sc);
int pad = 5;
int gl = pad + 45; /* левая граница графика */
int gr = w - pad; /* правая граница */
int gt = pad; /* верх */
int gb = h - 25; /* низ */
int gh = gb - gt;
/* === Сетка === */
HPEN grid = CreatePen(PS_DOT, 1, GetSysColor(COLOR_BTNSHADOW));
HPEN old = (HPEN)SelectObject(memDC, grid);
for (int i = 1; i < 5; i++) {
int y = pad + (h - 2 * pad) * i / 5;
MoveToEx(memDC, pad, y, NULL);
LineTo(memDC, w - pad, y);
}
SelectObject(memDC, old);
DeleteObject(grid);
/* === Оси === */
HPEN axis = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWTEXT));
old = (HPEN)SelectObject(memDC, axis);
MoveToEx(memDC, gl, gt, NULL); LineTo(memDC, gl, gb);
MoveToEx(memDC, gl, gb, NULL); LineTo(memDC, gr, gb);
SelectObject(memDC, old);
DeleteObject(axis);
/* Сколько точек видно */
int vis = (hist->count < gr - gl) ? hist->count : (gr - gl);
int oldest = (hist->head - vis + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE;
int startx = gr - vis;
/* Пересчёт min/max 5 раз в секунду */
DWORD now = GetTickCount();
if (now - hist->last_minmax_update >= 200) {
RecalculateMinMax(hist, vis, oldest);
hist->last_minmax_update = now;
}
/* Буфер точек */
POINT* points = (POINT*)malloc((vis + 1) * sizeof(POINT));
/* Рисуем графики */
for (int ch = 0; ch < sc; ch++) {
int m = sel[ch];
float mn = hist->min_val[m];
float mx = hist->max_val[m];
SelectObject(memDC, app->hGraphPens[m]);
for (int i = 0; i < vis; i++) {
float v = hist->values[m][(oldest + i) % GRAPH_HISTORY_SIZE];
int x = startx + i;
int y = gb - (int)((v - mn) / (mx - mn + 1e-9f) * (float)gh);
if (y < gt) y = gt;
if (y > gb) y = gb;
points[i].x = x;
points[i].y = y;
}
Polyline(memDC, points, vis);
}
free(points);
/* === ВЕРТИКАЛЬНАЯ ЛИНИЯ КУРСОРА (только линия, без текста) === */
if (app->graph_cursor_active) {
HPEN cp = CreatePen(PS_SOLID, 1, RGB(80, 80, 80));
old = (HPEN)SelectObject(memDC, cp);
MoveToEx(memDC, app->graph_cursor_x, gt, NULL);
LineTo(memDC, app->graph_cursor_x, gb);
SelectObject(memDC, old);
DeleteObject(cp);
}
end:
/* Копируем на экран */
BitBlt(hdc, 0, 0, w, h, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, oldBmp);
DeleteObject(bmp);
DeleteDC(memDC);
}
void UpdateChannelValues(struct etcpmon_app* app, int history_idx)
{
if (!app || !app->client) return;
struct metrics_history* h = etcpmon_client_get_history(app->client);
if (!h || history_idx < 0 || history_idx >= GRAPH_HISTORY_SIZE) {
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) {
if (app->hChannelValue[i])
SetWindowTextA(app->hChannelValue[i], "-");
}
return;
}
static char last_text[GRAPH_METRICS_COUNT][32] = {0};
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) {
if (!app->hChannelValue[i]) continue;
float val = h->values[i][history_idx];
char buf[32] = {0};
if (i == GRAPH_METRIC_BYTES_SENT) {
if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/(1024.*1024.));
else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB/s", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B/s", val);
} else if (i == GRAPH_METRIC_INFLIGHT) {
if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.);
else snprintf(buf, sizeof(buf), "%.0f B", val);
} else if (i == GRAPH_METRIC_RETRANS || i == GRAPH_METRIC_ACKS) {
snprintf(buf, sizeof(buf), "%.0f /s", val);
} else {
snprintf(buf, sizeof(buf), "%.1f ms", val);
}
if (strcmp(buf, last_text[i]) == 0)
continue;
strcpy(last_text[i], buf);
SendMessageA(app->hChannelValue[i], WM_SETREDRAW, FALSE, 0);
SetWindowTextA(app->hChannelValue[i], buf);
SendMessageA(app->hChannelValue[i], WM_SETREDRAW, TRUE, 0);
InvalidateRect(app->hChannelValue[i], NULL, FALSE);
}
}
LRESULT CALLBACK GraphWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
struct etcpmon_app* app = (struct etcpmon_app*)GetWindowLongPtrA(hWnd, GWLP_USERDATA);
switch (message) {
case WM_CREATE: {
CREATESTRUCTA* cs = (CREATESTRUCTA*)lParam;
app = cs->lpCreateParams;
SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)app);
if (app) app->hGraphWnd = hWnd;
break;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
DrawGraph(hdc, &rc, app);
EndPaint(hWnd, &ps);
return 0;
}
case WM_MOUSEMOVE: {
if (!app) break;
int x = LOWORD(lParam);
int y = HIWORD(lParam);
RECT rc;
GetClientRect(hWnd, &rc);
BOOL inside = (x >= 0 && x < rc.right && y >= 0 && y < rc.bottom);
if (inside) {
app->graph_cursor_x = x;
app->graph_cursor_active = 1;
static DWORD last_invalidate = 0;
DWORD now = GetTickCount();
if (now - last_invalidate >= 10) {
InvalidateRect(hWnd, NULL, FALSE);
last_invalidate = now;
}
} else {
app->graph_cursor_active = 0;
InvalidateRect(hWnd, NULL, FALSE);
}
break;
}
case WM_MOUSELEAVE:
if (app) {
app->graph_cursor_active = 0;
InvalidateRect(hWnd, NULL, FALSE);
}
break;
default:
return DefWindowProcA(hWnd, message, wParam, lParam);
}
return 0;
}

44
tools/etcpmon/etcpmon_graph.h

@ -0,0 +1,44 @@
/*
* etcpmon_graph.h - ETCP Monitor Graph Definitions
*/
#ifndef ETCPMON_GRAPH_H
#define ETCPMON_GRAPH_H
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Graph constants */
#define GRAPH_WIDTH 860
#define GRAPH_HEIGHT 150
#define CHANNEL_LIST_HEIGHT 80
#define GRAPH_METRICS_COUNT 8
#define GRAPH_HISTORY_SIZE 860
/* Forward declarations */
struct etcpmon_app;
struct metrics_history;
/* Graph colors for each metric */
extern const COLORREF graph_colors[GRAPH_METRICS_COUNT];
extern const char* graph_metric_names[GRAPH_METRICS_COUNT];
/* Graph functions */
LRESULT CALLBACK GraphWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void DrawGraph(HDC hdc, RECT* rc, struct etcpmon_app* app);
void GetSelectedChannels(struct etcpmon_app* app, int* selected, int* count);
void UpdateChannelValues(struct etcpmon_app* app, int cursor_sample);
#ifdef __cplusplus
}
#endif
#endif /* ETCPMON_GRAPH_H */

98
tools/etcpmon/etcpmon_gui.c

@ -49,6 +49,14 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
static void on_error(const char* msg, void* user_data); static void on_error(const char* msg, void* user_data);
/* Helper functions */ /* Helper functions */
static void SetDlgItemTextFmt(HWND hDlg, int nIDDlgItem, const char* fmt, ...) {
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
SetDlgItemTextA(hDlg, nIDDlgItem, buf);
}
int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
if (!app) return -1; if (!app) return -1;
@ -76,7 +84,7 @@ int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
wcex.lpszClassName = ETCPMON_WINDOW_CLASS; wcex.lpszClassName = ETCPMON_WINDOW_CLASS;
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassExA(&wcex)) { if (!RegisterClassExA(&wcex)) {
return -1; return -1;
} }
@ -205,10 +213,8 @@ static void CreateControls(struct etcpmon_app* app) {
WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT,
x, y + 20, 300, 120, hWnd, (HMENU)IDC_LIST_CONNECTIONS, hInst, NULL); x, y + 20, 300, 120, hWnd, (HMENU)IDC_LIST_CONNECTIONS, hInst, NULL);
/* Graph area - between Connections and ETCP Metrics */ /* Graph area */
y += 140; y += 140;
// app->hGraphWnd = CreateWindowA("STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER,
// 10, y, GRAPH_WIDTH, GRAPH_HEIGHT, hWnd, (HMENU)IDC_GRAPH, hInst, app);
app->hGraphWnd = CreateWindowExA( app->hGraphWnd = CreateWindowExA(
WS_EX_CLIENTEDGE, WS_EX_CLIENTEDGE,
@ -217,8 +223,8 @@ static void CreateControls(struct etcpmon_app* app) {
10, y, GRAPH_WIDTH, GRAPH_HEIGHT, 10, y, GRAPH_WIDTH, GRAPH_HEIGHT,
hWnd, (HMENU)IDC_GRAPH, hInst, app); hWnd, (HMENU)IDC_GRAPH, hInst, app);
/* Channel list - grid blocks below graph */ /* Channel blocks below graph (checkbox + name в одной строке, значение ниже) */
y += GRAPH_HEIGHT + 5; y += GRAPH_HEIGHT + 8;
const char* channel_short_names[] = { const char* channel_short_names[] = {
"RTT-L", "RTT10", "RTT100", "Jitter", "RTT-L", "RTT10", "RTT100", "Jitter",
@ -226,9 +232,9 @@ static void CreateControls(struct etcpmon_app* app) {
}; };
int block_w = 200; int block_w = 200;
int block_h = 28; int block_h = 48; /* компактная высота */
int cols = 4; int cols = 4;
int spacing = 5; int spacing = 6;
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { for (int i = 0; i < GRAPH_METRICS_COUNT; i++) {
int col = i % cols; int col = i % cols;
@ -236,17 +242,23 @@ static void CreateControls(struct etcpmon_app* app) {
int bx = 10 + col * (block_w + spacing); int bx = 10 + col * (block_w + spacing);
int by = y + row * (block_h + spacing); int by = y + row * (block_h + spacing);
/* Галочка слева */
app->hChannelCheck[i] = CreateWindowExA(0, "BUTTON", "", app->hChannelCheck[i] = CreateWindowExA(0, "BUTTON", "",
WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BST_CHECKED, WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX | BST_CHECKED,
bx, by + 4, 16, 14, hWnd, (HMENU)(UINT_PTR)(IDC_CH_CHECK_0 + i), hInst, NULL); bx, by, 20, 18,
hWnd, (HMENU)(UINT_PTR)(IDC_CH_CHECK_0 + i), hInst, NULL);
/* Название сразу справа от галочки */
app->hChannelName[i] = CreateWindowExA(0, "STATIC", channel_short_names[i], app->hChannelName[i] = CreateWindowExA(0, "STATIC", channel_short_names[i],
WS_CHILD | WS_VISIBLE, WS_CHILD | WS_VISIBLE | SS_LEFT,
bx + 20, by + 4, 60, 16, hWnd, (HMENU)(UINT_PTR)(IDC_CH_NAME_0 + i), hInst, NULL); bx + 24, by + 2, block_w - 30, 16,
hWnd, (HMENU)(UINT_PTR)(IDC_CH_NAME_0 + i), hInst, NULL);
/* Значение под названием */
app->hChannelValue[i] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", app->hChannelValue[i] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "",
WS_CHILD | WS_VISIBLE | ES_READONLY, WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER,
bx + 85, by + 2, 110, 20, hWnd, (HMENU)(UINT_PTR)(IDC_CH_VALUE_0 + i), hInst, NULL); bx, by + 21, block_w, 22,
hWnd, (HMENU)(UINT_PTR)(IDC_CH_VALUE_0 + i), hInst, NULL);
} }
int channel_grid_h = ((GRAPH_METRICS_COUNT + cols - 1) / cols) * (block_h + spacing) - spacing; int channel_grid_h = ((GRAPH_METRICS_COUNT + cols - 1) / cols) * (block_h + spacing) - spacing;
@ -505,34 +517,6 @@ static void OnTimer(struct etcpmon_app* app) {
InvalidateRect(app->hGraphWnd, NULL, FALSE); InvalidateRect(app->hGraphWnd, NULL, FALSE);
UpdateWindow(app->hGraphWnd); /* форсируем немедленную перерисовку */ UpdateWindow(app->hGraphWnd); /* форсируем немедленную перерисовку */
} }
/* 5. Цифры под графиком — обновляем если курсор не активен */
if (!app->graph_cursor_active) {
struct metrics_history* h = etcpmon_client_get_history(app->client);
if (h && h->count > 0) {
int last_idx = (h->head - 1 + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE;
UpdateChannelValues(app, last_idx);
}
} else {
RECT rc;
GetClientRect(app->hGraphWnd, &rc);
struct metrics_history* h = etcpmon_client_get_history(app->client);
if (h && h->count > 2) {
int w = rc.right - rc.left;
int pad = 5;
int gl = pad + 45;
int gr = w - pad;
int vis = (h->count < gr - gl) ? h->count : (gr - gl);
int oldest = (h->head - vis + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE;
int startx = gr - vis;
int x = app->graph_cursor_x;
if (x >= startx && x < startx + vis) {
int rel = x - startx;
int idx = (oldest + rel) % GRAPH_HISTORY_SIZE;
UpdateChannelValues(app, idx);
}
}
}
} }
static void UpdateUIState(struct etcpmon_app* app) { static void UpdateUIState(struct etcpmon_app* app) {
@ -545,17 +529,11 @@ static void UpdateUIState(struct etcpmon_app* app) {
} }
/* Client callbacks */ /* Client callbacks */
//static void on_connected(void* user_data) {
// struct etcpmon_app* app = (struct etcpmon_app*)user_data;
// etcpmon_gui_set_status(app, "Connected");
//}
static void on_connected(void* user_data) { static void on_connected(void* user_data) {
struct etcpmon_app* app = (struct etcpmon_app*)user_data; struct etcpmon_app* app = (struct etcpmon_app*)user_data;
app->isConnected = 1; app->isConnected = 1;
UpdateUIState(app); UpdateUIState(app);
etcpmon_gui_set_status(app, "Connected"); etcpmon_gui_set_status(app, "Connected");
// Добавьте для теста
if (app->client->log_file) { if (app->client->log_file) {
fprintf(app->client->log_file, "[DEBUG] on_connected called\n"); fprintf(app->client->log_file, "[DEBUG] on_connected called\n");
fflush(app->client->log_file); fflush(app->client->log_file);
@ -579,7 +557,6 @@ void etcpmon_gui_update_graph(struct etcpmon_app* app, struct etcpmon_rsp_metric
} }
} }
static void on_disconnected(void* user_data) { static void on_disconnected(void* user_data) {
struct etcpmon_app* app = (struct etcpmon_app*)user_data; struct etcpmon_app* app = (struct etcpmon_app*)user_data;
app->isConnected = 0; app->isConnected = 0;
@ -587,21 +564,14 @@ static void on_disconnected(void* user_data) {
etcpmon_gui_set_status(app, "Disconnected"); etcpmon_gui_set_status(app, "Disconnected");
} }
//static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) {
// struct etcpmon_app* app = (struct etcpmon_app*)user_data;
// etcpmon_gui_update_conn_list(app, list, count);
//}
static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) { static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) {
struct etcpmon_app* app = (struct etcpmon_app*)user_data; struct etcpmon_app* app = (struct etcpmon_app*)user_data;
etcpmon_gui_update_conn_list(app, list, count); etcpmon_gui_update_conn_list(app, list, count);
etcpmon_gui_set_status(app, "Connection list updated"); etcpmon_gui_set_status(app, "Connection list updated");
// Automatically select the first connection if available /* Automatically select the first connection if available */
if (count > 0 && app->hListConnections) { if (count > 0 && app->hListConnections) {
// Select the first item (index 0)
SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0); SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0);
// Trigger the selection handler to request metrics
OnConnectionSelect(app); OnConnectionSelect(app);
} }
} }
@ -614,12 +584,10 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
etcpmon_gui_update_metrics(app, metrics, links, links_count); etcpmon_gui_update_metrics(app, metrics, links, links_count);
etcpmon_client_add_to_history(app->client, metrics); etcpmon_client_add_to_history(app->client, metrics);
if (!app->graph_cursor_active) { struct metrics_history* h = etcpmon_client_get_history(app->client);
struct metrics_history* h = etcpmon_client_get_history(app->client); if (h && h->count > 0) {
if (h && h->count > 0) { int last_idx = (h->head - 1 + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE;
int last_idx = (h->head - 1 + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE; UpdateChannelValues(app, last_idx);
UpdateChannelValues(app, last_idx);
}
} }
} }
} }
@ -699,7 +667,7 @@ static BOOL UpdateEditIfChanged(HWND hDlg, int nIDDlgItem, const char* fmt, ...)
GetDlgItemTextA(hDlg, nIDDlgItem, old_text, sizeof(old_text)); GetDlgItemTextA(hDlg, nIDDlgItem, old_text, sizeof(old_text));
if (strcmp(old_text, new_text) == 0) if (strcmp(old_text, new_text) == 0)
return FALSE; /* ничего не изменилось — не трогаем */ return FALSE;
SetDlgItemTextA(hDlg, nIDDlgItem, new_text); SetDlgItemTextA(hDlg, nIDDlgItem, new_text);
InvalidateRect(GetDlgItem(hDlg, nIDDlgItem), NULL, FALSE); InvalidateRect(GetDlgItem(hDlg, nIDDlgItem), NULL, FALSE);
@ -734,7 +702,7 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_ERRS, "%u", metrics->tun.read_errors); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_ERRS, "%u", metrics->tun.read_errors);
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", metrics->tun.write_errors); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", metrics->tun.write_errors);
/* Links list (перестраивается всегда — он маленький, моргание почти незаметно) */ /* Links list */
if (app->hListLinks) { if (app->hListLinks) {
SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0);
for (uint8_t i = 0; i < links_count; i++) { for (uint8_t i = 0; i < links_count; i++) {

15
tools/etcpmon/etcpmon_gui.h

@ -62,12 +62,10 @@ extern "C" {
/* Link list control ID */ /* Link list control ID */
#define IDC_LIST_LINKS 400 #define IDC_LIST_LINKS 400
/* Graph control IDs */ /* Graph control ID */
#define IDC_GRAPH 500 #define IDC_GRAPH 500
#define IDC_STATIC_GRAPH 501
#define IDC_LIST_CHANNELS 502
/* Channel block IDs - 8 channels, 4 columns x 2 rows */ /* Channel block IDs */
#define IDC_CH_NAME_0 510 #define IDC_CH_NAME_0 510
#define IDC_CH_VALUE_0 520 #define IDC_CH_VALUE_0 520
#define IDC_CH_CHECK_0 530 #define IDC_CH_CHECK_0 530
@ -100,6 +98,7 @@ struct etcpmon_app {
HWND hWndStatus; HWND hWndStatus;
HPEN hGraphPens[GRAPH_METRICS_COUNT]; HPEN hGraphPens[GRAPH_METRICS_COUNT];
/* Connection controls */ /* Connection controls */
HWND hEditAddr; HWND hEditAddr;
HWND hEditPort; HWND hEditPort;
@ -130,19 +129,21 @@ struct etcpmon_app {
/* Links list */ /* Links list */
HWND hListLinks; HWND hListLinks;
/* Graph controls (managed by graph module) */ /* Graph controls */
HWND hGraphWnd; HWND hGraphWnd;
HWND hChannelName[GRAPH_METRICS_COUNT]; HWND hChannelName[GRAPH_METRICS_COUNT];
HWND hChannelValue[GRAPH_METRICS_COUNT]; HWND hChannelValue[GRAPH_METRICS_COUNT];
HWND hChannelCheck[GRAPH_METRICS_COUNT]; HWND hChannelCheck[GRAPH_METRICS_COUNT];
/* Cursor (вертикальная линия остаётся) */
int graph_cursor_x; int graph_cursor_x;
int graph_cursor_y; int graph_cursor_y;
int graph_cursor_active; int graph_cursor_active;
/* Client state - pointer to actual client structure */ /* Client state */
struct etcpmon_client* client; struct etcpmon_client* client;
/* Timer ID */ /* Timer */
UINT_PTR updateTimer; UINT_PTR updateTimer;
DWORD last_poll_time; DWORD last_poll_time;
int isConnected; int isConnected;

Loading…
Cancel
Save