|
|
|
|
@ -25,7 +25,7 @@
|
|
|
|
|
|
|
|
|
|
#define WINDOW_WIDTH 900 |
|
|
|
|
#define WINDOW_HEIGHT 900 |
|
|
|
|
#define UPDATE_INTERVAL 100 /* 100ms */ |
|
|
|
|
#define UPDATE_INTERVAL 10 /* 50ms → 20 samples per second */ |
|
|
|
|
|
|
|
|
|
/* Global app pointer for callbacks */ |
|
|
|
|
static struct etcpmon_app* g_app = NULL; |
|
|
|
|
@ -49,14 +49,6 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
|
|
|
|
|
static void on_error(const char* msg, void* user_data); |
|
|
|
|
|
|
|
|
|
/* 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) { |
|
|
|
|
if (!app) return -1; |
|
|
|
|
@ -140,6 +132,7 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
|
|
|
|
|
|
|
|
|
|
/* Create all controls */ |
|
|
|
|
CreateControls(app); |
|
|
|
|
SetTimer(app->hWndMain, IDC_TIMER_UPDATE, UPDATE_INTERVAL, NULL); |
|
|
|
|
|
|
|
|
|
/* Set default values */ |
|
|
|
|
SetDlgItemTextA(app->hWndMain, IDC_EDIT_ADDR, "127.0.0.1"); |
|
|
|
|
@ -155,6 +148,10 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
|
|
|
|
|
app); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { |
|
|
|
|
app->hGraphPens[i] = CreatePen(PS_SOLID, 2, graph_colors[i]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
UpdateUIState(app); |
|
|
|
|
|
|
|
|
|
return app->hWndMain; |
|
|
|
|
@ -229,7 +226,7 @@ static void CreateControls(struct etcpmon_app* app) {
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
int block_w = 200; |
|
|
|
|
int block_h = 50; |
|
|
|
|
int block_h = 28; |
|
|
|
|
int cols = 4; |
|
|
|
|
int spacing = 5; |
|
|
|
|
|
|
|
|
|
@ -239,17 +236,17 @@ static void CreateControls(struct etcpmon_app* app) {
|
|
|
|
|
int bx = 10 + col * (block_w + spacing); |
|
|
|
|
int by = y + row * (block_h + spacing); |
|
|
|
|
|
|
|
|
|
app->hChannelCheck[i] = CreateWindowExA(0, "BUTTON", "", |
|
|
|
|
WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BST_CHECKED, |
|
|
|
|
bx, by + 4, 16, 14, hWnd, (HMENU)(UINT_PTR)(IDC_CH_CHECK_0 + i), hInst, NULL); |
|
|
|
|
|
|
|
|
|
app->hChannelName[i] = CreateWindowExA(0, "STATIC", channel_short_names[i], |
|
|
|
|
WS_CHILD | WS_VISIBLE, |
|
|
|
|
bx, by, block_w, 16, hWnd, (HMENU)(UINT_PTR)(IDC_CH_NAME_0 + i), hInst, NULL); |
|
|
|
|
bx + 20, by + 4, 60, 16, hWnd, (HMENU)(UINT_PTR)(IDC_CH_NAME_0 + i), hInst, NULL); |
|
|
|
|
|
|
|
|
|
app->hChannelValue[i] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", |
|
|
|
|
WS_CHILD | WS_VISIBLE | ES_READONLY, |
|
|
|
|
bx, by + 16, block_w, 18, hWnd, (HMENU)(UINT_PTR)(IDC_CH_VALUE_0 + i), hInst, NULL); |
|
|
|
|
|
|
|
|
|
app->hChannelCheck[i] = CreateWindowExA(0, "BUTTON", "", |
|
|
|
|
WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BST_CHECKED, |
|
|
|
|
bx, by + 34, 16, 14, hWnd, (HMENU)(UINT_PTR)(IDC_CH_CHECK_0 + i), hInst, NULL); |
|
|
|
|
bx + 85, by + 2, 110, 20, 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; |
|
|
|
|
@ -415,18 +412,19 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l
|
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case WM_TIMER: |
|
|
|
|
if (app && wParam == IDC_TIMER_UPDATE) { |
|
|
|
|
OnTimer(app); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case WM_SIZE: |
|
|
|
|
if (app && app->hWndStatus) { |
|
|
|
|
SendMessage(app->hWndStatus, WM_SIZE, 0, 0); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case WM_TIMER: |
|
|
|
|
if (wParam == IDC_TIMER_UPDATE && app) { |
|
|
|
|
OnTimer(app); |
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case WM_DESTROY: |
|
|
|
|
PostQuitMessage(0); |
|
|
|
|
break; |
|
|
|
|
@ -453,8 +451,6 @@ static void OnConnect(struct etcpmon_app* app) {
|
|
|
|
|
etcpmon_gui_set_status(app, "Connecting..."); |
|
|
|
|
|
|
|
|
|
if (etcpmon_client_connect(app->client, addr, port) == 0) { |
|
|
|
|
/* Start update timer */ |
|
|
|
|
app->updateTimer = SetTimer(app->hWndMain, IDC_TIMER_UPDATE, UPDATE_INTERVAL, NULL); |
|
|
|
|
app->isConnected = 1; |
|
|
|
|
UpdateUIState(app); |
|
|
|
|
etcpmon_gui_set_status(app, "Connected"); |
|
|
|
|
@ -466,11 +462,6 @@ static void OnConnect(struct etcpmon_app* app) {
|
|
|
|
|
static void OnDisconnect(struct etcpmon_app* app) { |
|
|
|
|
if (!app || !app->client) return; |
|
|
|
|
|
|
|
|
|
if (app->updateTimer) { |
|
|
|
|
KillTimer(app->hWndMain, app->updateTimer); |
|
|
|
|
app->updateTimer = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
etcpmon_client_disconnect(app->client); |
|
|
|
|
app->isConnected = 0; |
|
|
|
|
UpdateUIState(app); |
|
|
|
|
@ -494,21 +485,54 @@ static void OnConnectionSelect(struct etcpmon_app* app) {
|
|
|
|
|
static void OnTimer(struct etcpmon_app* app) { |
|
|
|
|
if (!app || !app->client) return; |
|
|
|
|
|
|
|
|
|
/* Process any incoming data */ |
|
|
|
|
// int result =
|
|
|
|
|
/* 1. Сеть */ |
|
|
|
|
etcpmon_client_process(app->client); |
|
|
|
|
|
|
|
|
|
/* If newly connected, request connection list */ |
|
|
|
|
/* 2. Первый запрос списка после подключения */ |
|
|
|
|
static int first_connect = 1; |
|
|
|
|
if (first_connect && app->isConnected) { |
|
|
|
|
etcpmon_client_request_list(app->client); |
|
|
|
|
first_connect = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Request metrics periodically if a connection is selected */ |
|
|
|
|
/* 3. Запрос метрик */ |
|
|
|
|
if (app->client->selected_peer_id != 0) { |
|
|
|
|
etcpmon_client_request_metrics(app->client); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 4. График — 20 раз в секунду */ |
|
|
|
|
if (app->hGraphWnd) { |
|
|
|
|
InvalidateRect(app->hGraphWnd, NULL, FALSE); |
|
|
|
|
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) { |
|
|
|
|
@ -589,6 +613,14 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
|
|
|
|
|
if (app) { |
|
|
|
|
etcpmon_gui_update_metrics(app, metrics, links, links_count); |
|
|
|
|
etcpmon_client_add_to_history(app->client, metrics); |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -608,15 +640,14 @@ int etcpmon_gui_run(struct etcpmon_app* app) {
|
|
|
|
|
TranslateMessage(&msg); |
|
|
|
|
DispatchMessageA(&msg); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return (int)msg.wParam; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void etcpmon_gui_cleanup(struct etcpmon_app* app) { |
|
|
|
|
if (!app) return; |
|
|
|
|
|
|
|
|
|
if (app->updateTimer) { |
|
|
|
|
KillTimer(app->hWndMain, app->updateTimer); |
|
|
|
|
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { |
|
|
|
|
if (app->hGraphPens[i]) DeleteObject(app->hGraphPens[i]); |
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app->client) { |
|
|
|
|
@ -655,73 +686,82 @@ void etcpmon_gui_update_conn_list(struct etcpmon_app* app,
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Обновляет EDIT только если текст действительно изменился → убирает моргание */ |
|
|
|
|
static BOOL UpdateEditIfChanged(HWND hDlg, int nIDDlgItem, const char* fmt, ...) |
|
|
|
|
{ |
|
|
|
|
char new_text[128]; |
|
|
|
|
va_list args; |
|
|
|
|
va_start(args, fmt); |
|
|
|
|
vsnprintf(new_text, sizeof(new_text), fmt, args); |
|
|
|
|
va_end(args); |
|
|
|
|
|
|
|
|
|
char old_text[128] = {0}; |
|
|
|
|
GetDlgItemTextA(hDlg, nIDDlgItem, old_text, sizeof(old_text)); |
|
|
|
|
|
|
|
|
|
if (strcmp(old_text, new_text) == 0) |
|
|
|
|
return FALSE; /* ничего не изменилось — не трогаем */ |
|
|
|
|
|
|
|
|
|
SetDlgItemTextA(hDlg, nIDDlgItem, new_text); |
|
|
|
|
InvalidateRect(GetDlgItem(hDlg, nIDDlgItem), NULL, FALSE); |
|
|
|
|
return TRUE; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void etcpmon_gui_update_metrics(struct etcpmon_app* app, |
|
|
|
|
struct etcpmon_rsp_metrics* metrics, |
|
|
|
|
struct etcpmon_link_metrics* links, |
|
|
|
|
uint8_t links_count) { |
|
|
|
|
uint8_t links_count) |
|
|
|
|
{ |
|
|
|
|
if (!app || !metrics) return; |
|
|
|
|
|
|
|
|
|
/* Update ETCP metrics */ |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RTT_LAST, "%u us",
|
|
|
|
|
metrics->etcp.rtt_last * 100); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG10, "%u us", |
|
|
|
|
metrics->etcp.rtt_avg_10 * 100); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG100, "%u us", |
|
|
|
|
metrics->etcp.rtt_avg_100 * 100); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_JITTER, "%u us", |
|
|
|
|
metrics->etcp.jitter * 100); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_BYTES_SENT, "%llu", |
|
|
|
|
(unsigned long long)metrics->etcp.bytes_sent_total); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RETRANS, "%u", |
|
|
|
|
metrics->etcp.retrans_count); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_ACKS, "%u", |
|
|
|
|
metrics->etcp.ack_count); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_INFLIGHT, "%u bytes", |
|
|
|
|
metrics->etcp.unacked_bytes); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_LINKS, "%u", |
|
|
|
|
metrics->etcp.links_count); |
|
|
|
|
|
|
|
|
|
/* Update TUN metrics */ |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_READ_BYTES, "%llu", |
|
|
|
|
(unsigned long long)metrics->tun.bytes_read); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_WRITE_BYTES, "%llu", |
|
|
|
|
(unsigned long long)metrics->tun.bytes_written); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_READ_PKTS, "%u", |
|
|
|
|
metrics->tun.packets_read); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_WRITE_PKTS, "%u", |
|
|
|
|
metrics->tun.packets_written); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_READ_ERRS, "%u", |
|
|
|
|
metrics->tun.read_errors); |
|
|
|
|
SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", |
|
|
|
|
metrics->tun.write_errors); |
|
|
|
|
|
|
|
|
|
/* Update links list */ |
|
|
|
|
HWND hMain = app->hWndMain; |
|
|
|
|
|
|
|
|
|
/* ETCP Metrics — обновляем ТОЛЬКО при изменении */ |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RTT_LAST, "%u us", metrics->etcp.rtt_last * 100); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RTT_AVG10, "%u us", metrics->etcp.rtt_avg_10 * 100); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RTT_AVG100, "%u us", metrics->etcp.rtt_avg_100 * 100); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_JITTER, "%u us", metrics->etcp.jitter * 100); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_BYTES_SENT, "%llu", (unsigned long long)metrics->etcp.bytes_sent_total); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RETRANS, "%u", metrics->etcp.retrans_count); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_ACKS, "%u", metrics->etcp.ack_count); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_INFLIGHT, "%u bytes", metrics->etcp.unacked_bytes); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_LINKS, "%u", metrics->etcp.links_count); |
|
|
|
|
|
|
|
|
|
/* TUN Metrics */ |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_BYTES, "%llu", (unsigned long long)metrics->tun.bytes_read); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_BYTES, "%llu", (unsigned long long)metrics->tun.bytes_written); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_PKTS, "%u", metrics->tun.packets_read); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_PKTS, "%u", metrics->tun.packets_written); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_ERRS, "%u", metrics->tun.read_errors); |
|
|
|
|
UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", metrics->tun.write_errors); |
|
|
|
|
|
|
|
|
|
/* Links list (перестраивается всегда — он маленький, моргание почти незаметно) */ |
|
|
|
|
if (app->hListLinks) { |
|
|
|
|
SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); |
|
|
|
|
for (uint8_t i = 0; i < links_count; i++) { |
|
|
|
|
char display[256]; |
|
|
|
|
snprintf(display, sizeof(display), |
|
|
|
|
"Link %u: Status=%s, EncErrors=%u, DecErrors=%u, " |
|
|
|
|
"SendErr=%u, RecvErr=%u, BytesEnc=%llu, BytesDec=%llu, " |
|
|
|
|
"BW=%u Kbps, NATChg=%u", |
|
|
|
|
links[i].local_link_id, |
|
|
|
|
links[i].status ? "UP" : "DOWN", |
|
|
|
|
links[i].encrypt_errors, |
|
|
|
|
links[i].decrypt_errors, |
|
|
|
|
links[i].send_errors, |
|
|
|
|
links[i].recv_errors, |
|
|
|
|
(unsigned long long)links[i].total_encrypted, |
|
|
|
|
(unsigned long long)links[i].total_decrypted, |
|
|
|
|
links[i].bandwidth, |
|
|
|
|
links[i].nat_changes_count); |
|
|
|
|
"Link %u: Status=%s, EncErrors=%u, DecErrors=%u, " |
|
|
|
|
"SendErr=%u, RecvErr=%u, BytesEnc=%llu, BytesDec=%llu, " |
|
|
|
|
"BW=%u Kbps, NATChg=%u", |
|
|
|
|
links[i].local_link_id, |
|
|
|
|
links[i].status ? "UP" : "DOWN", |
|
|
|
|
links[i].encrypt_errors, |
|
|
|
|
links[i].decrypt_errors, |
|
|
|
|
links[i].send_errors, |
|
|
|
|
links[i].recv_errors, |
|
|
|
|
(unsigned long long)links[i].total_encrypted, |
|
|
|
|
(unsigned long long)links[i].total_decrypted, |
|
|
|
|
links[i].bandwidth, |
|
|
|
|
links[i].nat_changes_count); |
|
|
|
|
|
|
|
|
|
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)display); |
|
|
|
|
} |
|
|
|
|
InvalidateRect(app->hListLinks, NULL, FALSE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* График */ |
|
|
|
|
if (app->hGraphWnd) { |
|
|
|
|
InvalidateRect(app->hGraphWnd, NULL, FALSE); |
|
|
|
|
UpdateWindow(app->hGraphWnd); // Force immediate paint
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|