Добавлена оперативная и историческая статистика нод и узлов mnesia #22
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик исторических метрик узлов.
|
||||
%%% GET /v1/admin/nodes/metrics – возвращает массив метрик за период.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_node_metrics).
|
||||
-behaviour(cowboy_handler).
|
||||
-export([init/2]).
|
||||
-export([trails/0]).
|
||||
|
||||
-include("records.hrl").
|
||||
|
||||
%%% cowboy_handler callback
|
||||
-spec init(cowboy_req:req(), any()) -> {ok, binary(), cowboy_req:req()}.
|
||||
init(Req, _Opts) ->
|
||||
case cowboy_req:method(Req) of
|
||||
<<"GET">> -> get_metrics(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[#{path => <<"/v1/admin/nodes/metrics">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get historical node metrics">>,
|
||||
tags => [<<"Node Metrics">>],
|
||||
parameters => [
|
||||
#{name => <<"node">>, in => <<"query">>, schema => #{type => string}, description => <<"Node name, default current">>},
|
||||
#{name => <<"from">>, in => <<"query">>, required => true, schema => #{type => string, format => <<"date-time">>}},
|
||||
#{name => <<"to">>, in => <<"query">>, required => true, schema => #{type => string, format => <<"date-time">>}}
|
||||
],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Array of node metrics">>,
|
||||
content => #{<<"application/json">> => #{schema => #{
|
||||
type => array,
|
||||
items => metric_schema()
|
||||
}}}
|
||||
},
|
||||
400 => #{description => <<"Missing required parameters">>},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
}}
|
||||
].
|
||||
|
||||
metric_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"timestamp">> => #{
|
||||
type => string,
|
||||
format => <<"date-time">>,
|
||||
description => <<"Metric collection timestamp (ISO8601)">>
|
||||
},
|
||||
<<"node">> => #{
|
||||
type => string,
|
||||
description => <<"Erlang node name">>
|
||||
},
|
||||
<<"memory_total">> => #{
|
||||
type => integer,
|
||||
description => <<"Total memory allocated by the Erlang VM (bytes)">>
|
||||
},
|
||||
<<"memory_processes">> => #{
|
||||
type => integer,
|
||||
description => <<"Memory used by processes (bytes)">>
|
||||
},
|
||||
<<"memory_atom">> => #{
|
||||
type => integer,
|
||||
description => <<"Memory used by atoms (bytes)">>
|
||||
},
|
||||
<<"memory_binary">> => #{
|
||||
type => integer,
|
||||
description => <<"Memory used by binaries (bytes)">>
|
||||
},
|
||||
<<"memory_ets">> => #{
|
||||
type => integer,
|
||||
description => <<"Memory used by ETS tables (bytes)">>
|
||||
},
|
||||
<<"process_count">> => #{
|
||||
type => integer,
|
||||
description => <<"Number of live Erlang processes">>
|
||||
},
|
||||
<<"run_queue">> => #{
|
||||
type => integer,
|
||||
description => <<"Length of the scheduler run queue">>
|
||||
},
|
||||
<<"mnesia_commits">> => #{
|
||||
type => integer,
|
||||
description => <<"Number of successful Mnesia transactions since node start">>
|
||||
},
|
||||
<<"mnesia_failures">> => #{
|
||||
type => integer,
|
||||
description => <<"Number of failed Mnesia transactions since node start">>
|
||||
},
|
||||
<<"table_sizes">> => #{
|
||||
type => object,
|
||||
description => <<"Map of Mnesia table names to their sizes (number of records)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"active_sessions">> => #{
|
||||
type => integer,
|
||||
description => <<"Total number of active user and admin sessions">>
|
||||
},
|
||||
<<"ws_connections">> => #{
|
||||
type => integer,
|
||||
description => <<"Number of open WebSocket connections">>
|
||||
},
|
||||
<<"io_in">> => #{
|
||||
type => integer,
|
||||
description => <<"Bytes received on sockets since last collection (delta)">>
|
||||
},
|
||||
<<"io_out">> => #{
|
||||
type => integer,
|
||||
description => <<"Bytes sent over sockets since last collection (delta)">>
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc Возвращает исторические метрики по заданному периоду и узлу.
|
||||
-spec get_metrics(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_metrics(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Qs = cowboy_req:parse_qs(Req1),
|
||||
From = handler_utils:parse_datetime_qs(proplists:get_value(<<"from">>, Qs)),
|
||||
To = handler_utils:parse_datetime_qs(proplists:get_value(<<"to">>, Qs)),
|
||||
Node = proplists:get_value(<<"node">>, Qs, node()),
|
||||
case {From, To} of
|
||||
{undefined, _} -> handler_utils:send_error(Req1, 400, <<"Missing from">>);
|
||||
{_, undefined} -> handler_utils:send_error(Req1, 400, <<"Missing to">>);
|
||||
{F, T} ->
|
||||
Metrics = core_node_metric:get_by_timerange(F, T, Node),
|
||||
Json = [metric_to_map(M) || M <- Metrics],
|
||||
handler_utils:send_json(Req1, 200, Json)
|
||||
end;
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
%% @private Преобразует запись метрики в JSON-совместимую карту.
|
||||
-spec metric_to_map(#node_metric{}) -> map().
|
||||
metric_to_map(M) ->
|
||||
#{
|
||||
<<"timestamp">> => handler_utils:datetime_to_iso8601(M#node_metric.timestamp),
|
||||
<<"node">> => atom_to_binary(M#node_metric.node, utf8),
|
||||
<<"memory_total">> => M#node_metric.memory_total,
|
||||
<<"memory_processes">> => M#node_metric.memory_processes,
|
||||
<<"memory_atom">> => M#node_metric.memory_atom,
|
||||
<<"memory_binary">> => M#node_metric.memory_binary,
|
||||
<<"memory_ets">> => M#node_metric.memory_ets,
|
||||
<<"process_count">> => M#node_metric.process_count,
|
||||
<<"run_queue">> => M#node_metric.run_queue,
|
||||
<<"mnesia_commits">> => M#node_metric.mnesia_commits,
|
||||
<<"mnesia_failures">> => M#node_metric.mnesia_failures,
|
||||
<<"table_sizes">> => M#node_metric.table_sizes,
|
||||
<<"active_sessions">> => M#node_metric.active_sessions,
|
||||
<<"ws_connections">> => M#node_metric.ws_connections,
|
||||
<<"io_in">> => M#node_metric.io_in,
|
||||
<<"io_out">> => M#node_metric.io_out
|
||||
}.
|
||||
@@ -1,11 +1,13 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный WebSocket-обработчик.
|
||||
%%% Устанавливает WebSocket-соединение после проверки JWT-токена
|
||||
%%% и подписывает администратора на каналы уведомлений.
|
||||
%%% и подписывает администратора на каналы уведомлений, включая
|
||||
%%% канал метрик узлов `node_metrics`.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_ws_handler).
|
||||
-behaviour(cowboy_websocket).
|
||||
-include("records.hrl").
|
||||
|
||||
-export([init/2]).
|
||||
-export([websocket_init/1]).
|
||||
@@ -30,7 +32,7 @@ init(Req, _Opts) ->
|
||||
Resp = cowboy_req:reply(401, #{}, <<"Missing token">>, Req),
|
||||
{ok, Resp, undefined};
|
||||
Token ->
|
||||
io:format("[ADMIN_WS] Token received: ~s...~n", [binary_part(Token, 0, 30)]),
|
||||
io:format("[ADMIN_WS] Token received: ~s...~n", [safe_slice(Token, 30)]),
|
||||
case logic_auth:verify_jwt(Token) of
|
||||
{ok, UserId, Role} ->
|
||||
io:format("[ADMIN_WS] UserId: ~s, Role: ~s~n", [UserId, Role]),
|
||||
@@ -57,8 +59,11 @@ init(Req, _Opts) ->
|
||||
%% @doc Вызывается после установки WebSocket-соединения.
|
||||
-spec websocket_init(#state{}) -> {ok, #state{}}.
|
||||
websocket_init(State) ->
|
||||
core_counters:inc(admin_ws_connections),
|
||||
io:format("[ADMIN_WS] WebSocket initialized for admin ~s~n", [State#state.admin_id]),
|
||||
pg:join(eventhub_admin_ws, self()),
|
||||
% Подписываемся на метрики узлов
|
||||
pg:join(node_metrics, self()),
|
||||
{ok, State}.
|
||||
|
||||
%% @doc Обрабатывает входящие текстовые сообщения (subscribe/unsubscribe/ping).
|
||||
@@ -84,7 +89,7 @@ websocket_handle({text, Msg}, State) ->
|
||||
websocket_handle(_Frame, State) ->
|
||||
{ok, State}.
|
||||
|
||||
%% @doc Отправляет административное уведомление через WebSocket.
|
||||
%% @doc Отправляет административное уведомление или метрику узла через WebSocket.
|
||||
-spec websocket_info(term(), #state{}) -> {reply, {text, binary()}, #state{}} | {ok, #state{}}.
|
||||
websocket_info({admin_notification, Type, Data}, State) ->
|
||||
Msg = jsx:encode(#{
|
||||
@@ -93,6 +98,12 @@ websocket_info({admin_notification, Type, Data}, State) ->
|
||||
timestamp => os:system_time(seconds)
|
||||
}),
|
||||
{reply, {text, Msg}, State};
|
||||
websocket_info({node_metric, Metric}, State) ->
|
||||
JSON = jsx:encode(#{
|
||||
<<"type">> => <<"node_metric">>,
|
||||
<<"data">> => metric_to_map(Metric)
|
||||
}),
|
||||
{reply, {text, JSON}, State};
|
||||
websocket_info(_Info, State) ->
|
||||
{ok, State}.
|
||||
|
||||
@@ -100,4 +111,37 @@ websocket_info(_Info, State) ->
|
||||
-spec terminate(term(), cowboy_req:req(), #state{}) -> ok.
|
||||
terminate(_Reason, _Req, _State) ->
|
||||
pg:leave(eventhub_admin_ws, self()),
|
||||
ok.
|
||||
core_counters:dec(admin_ws_connections),
|
||||
ok.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Вспомогательные функции
|
||||
%%%-------------------------------------------------------------------
|
||||
|
||||
%% Безопасное получение первых Max байт бинарной строки
|
||||
safe_slice(Bin, Max) ->
|
||||
case byte_size(Bin) > Max of
|
||||
true -> binary_part(Bin, 0, Max);
|
||||
false -> Bin
|
||||
end.
|
||||
|
||||
%% Преобразование #node_metric{} в карту для JSON
|
||||
metric_to_map(M) ->
|
||||
#{
|
||||
<<"timestamp">> => handler_utils:datetime_to_iso8601(M#node_metric.timestamp),
|
||||
<<"node">> => atom_to_binary(M#node_metric.node, utf8),
|
||||
<<"memory_total">> => M#node_metric.memory_total,
|
||||
<<"memory_processes">> => M#node_metric.memory_processes,
|
||||
<<"memory_atom">> => M#node_metric.memory_atom,
|
||||
<<"memory_binary">> => M#node_metric.memory_binary,
|
||||
<<"memory_ets">> => M#node_metric.memory_ets,
|
||||
<<"process_count">> => M#node_metric.process_count,
|
||||
<<"run_queue">> => M#node_metric.run_queue,
|
||||
<<"mnesia_commits">> => M#node_metric.mnesia_commits,
|
||||
<<"mnesia_failures">> => M#node_metric.mnesia_failures,
|
||||
<<"table_sizes">> => M#node_metric.table_sizes,
|
||||
<<"active_sessions">> => M#node_metric.active_sessions,
|
||||
<<"ws_connections">> => M#node_metric.ws_connections,
|
||||
<<"io_in">> => M#node_metric.io_in,
|
||||
<<"io_out">> => M#node_metric.io_out
|
||||
}.
|
||||
Reference in New Issue
Block a user