This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
-include("records.hrl").
|
||||
-export([is_admin/1, check_role/2, get_permissions/1, is_superadmin/1]).
|
||||
-export([client_ip/1]). % ← IP
|
||||
-export([log_admin_action/5]). % ← аудит действий администратора
|
||||
-export([log_admin_action/6]). % ← аудит действий администратора
|
||||
-export([ip_to_binary/1]). % ← преобразование IP
|
||||
|
||||
%% -- существующие функции -------------------------------------------------
|
||||
@@ -71,14 +71,34 @@ ip_to_binary(_) ->
|
||||
%% EntityType – тип сущности (напр. <<"admin">>, <<"banned_word">>)
|
||||
%% EntityId – идентификатор сущности
|
||||
%% Req – cowboy_req для извлечения IP
|
||||
-spec log_admin_action(binary(), binary(), binary(), term(), cowboy_req:req()) -> ok.
|
||||
log_admin_action(AdminId, Action, EntityType, EntityId, Req) ->
|
||||
%%-spec log_admin_action(binary(), binary(), binary(), term(), cowboy_req:req()) -> ok.
|
||||
%%log_admin_action(AdminId, Action, EntityType, EntityId, Req) ->
|
||||
%% case core_admin:get_by_id(AdminId) of
|
||||
%% {ok, Admin} ->
|
||||
%% Email = Admin#admin.email,
|
||||
%% Role = atom_to_binary(Admin#admin.role, utf8),
|
||||
%% Ip = ip_to_binary(cowboy_req:peer(Req)),
|
||||
%% core_admin_audit:log(AdminId, Email, Role, Action, EntityType, EntityId, Ip),
|
||||
%% ok;
|
||||
%% _ -> ok
|
||||
%% end.
|
||||
|
||||
%% @doc Логирует действие администратора.
|
||||
%% Параметры:
|
||||
%% AdminId – ID администратора, выполнившего действие
|
||||
%% Action – бинарная строка действия (напр. <<"create">>)
|
||||
%% EntityType – тип сущности (напр. <<"admin">>, <<"banned_word">>)
|
||||
%% EntityId – идентификатор сущности
|
||||
%% Reason - причина
|
||||
%% Req – cowboy_req для извлечения IP
|
||||
-spec log_admin_action(binary(), binary(), binary(), term(), binary(), cowboy_req:req()) -> ok.
|
||||
log_admin_action(AdminId, Action, EntityType, EntityId, Reason, Req) ->
|
||||
case core_admin:get_by_id(AdminId) of
|
||||
{ok, Admin} ->
|
||||
Email = Admin#admin.email,
|
||||
Role = atom_to_binary(Admin#admin.role, utf8),
|
||||
Ip = ip_to_binary(cowboy_req:peer(Req)),
|
||||
core_admin_audit:log(AdminId, Email, Role, Action, EntityType, EntityId, Ip),
|
||||
core_admin_audit:log(AdminId, Email, Role, Action, EntityType, EntityId, Ip, Reason),
|
||||
ok;
|
||||
_ -> ok
|
||||
end.
|
||||
@@ -62,6 +62,22 @@ handle_call(init_tables, _From, State) ->
|
||||
ok = join_cluster(ExtraNodes)
|
||||
end,
|
||||
lists:foreach(fun create_table/1, ?TABLES),
|
||||
% Принудительное создание node_metric на каждом узле
|
||||
case mnesia:create_table(node_metric, [
|
||||
{disc_copies, [node()]}, % хранить на диске
|
||||
{local_content, true}, % не реплицировать
|
||||
{attributes, record_info(fields, node_metric)}
|
||||
]) of
|
||||
{atomic, ok} -> ok;
|
||||
{aborted, {already_exists, node_metric}} -> ok;
|
||||
_ -> ok
|
||||
end,
|
||||
% ГАРАНТИРУЕМ, что узел имеет локальную копию node_metric (критично для присоединяющихся узлов)
|
||||
case lists:member(node(), mnesia:table_info(node_metric, disc_copies) ++
|
||||
mnesia:table_info(node_metric, ram_copies)) of
|
||||
false -> mnesia:add_table_copy(node_metric, node(), disc_copies);
|
||||
true -> ok
|
||||
end,
|
||||
ok = create_indices(),
|
||||
ok = stats_collector:subscribe(),
|
||||
ok = start_cleanup_timer(),
|
||||
@@ -223,7 +239,7 @@ table_opts(schema_migration) -> [{disc_copies, [node()]}, {attributes, record_in
|
||||
table_opts(session) -> [{ram_copies, [node()]}, {attributes, record_info(fields, session)}];
|
||||
table_opts(verification) -> [{ram_copies, [node()]}, {attributes, record_info(fields, verification)}];
|
||||
table_opts(admin_session) -> [{ram_copies, [node()]}, {attributes, record_info(fields, admin_session)}];
|
||||
table_opts(node_metric) -> [{ram_copies, [node()]}, {local_content, true}, {attributes, record_info(fields, node_metric)}].
|
||||
table_opts(node_metric) -> [{disc_copies, [node()]}, {local_content, true}, {attributes, record_info(fields, node_metric)}].
|
||||
|
||||
%% ===================================================================
|
||||
%% Индексы
|
||||
|
||||
@@ -25,6 +25,10 @@ init([]) ->
|
||||
start => {pg, start_link, []},
|
||||
restart => permanent,
|
||||
type => worker},
|
||||
#{id => pg_global,
|
||||
start => {pg, start_link, [global]},
|
||||
restart => permanent,
|
||||
type => worker},
|
||||
#{id => stats_collector,
|
||||
start => {stats_collector, start_link, []},
|
||||
restart => permanent,
|
||||
|
||||
@@ -38,6 +38,7 @@ start_link() ->
|
||||
|
||||
-spec init(term()) -> {ok, map()}.
|
||||
init([]) ->
|
||||
mnesia:wait_for_tables([node_metric], 5000),
|
||||
% Создаём ETS-таблицу для кеширования предыдущих значений io
|
||||
ets:new(?IO_CACHE_TAB, [named_table, public, set, {keypos, 1}]),
|
||||
% Запускаем периодический сбор метрик и очистку
|
||||
@@ -58,7 +59,9 @@ handle_info(collect, State) ->
|
||||
Metric = collect_metrics(),
|
||||
core_node_metric:save(Metric),
|
||||
% Рассылаем метрику всем подписчикам группы node_metrics
|
||||
Members = pg:get_local_members(?METRICS_GROUP),
|
||||
%% Members = pg:get_local_members(?METRICS_GROUP),
|
||||
% Рассылаем метрику всем подписчикам глобальной группы node_metrics
|
||||
Members = pg:get_members(global, node_metrics),
|
||||
[Pid ! {node_metric, Metric} || Pid <- Members],
|
||||
schedule_collect(),
|
||||
{noreply, State};
|
||||
@@ -88,6 +91,8 @@ collect_metrics() ->
|
||||
Now = calendar:universal_time(),
|
||||
Mem = erlang:memory(),
|
||||
{IoIn, IoOut} = io_stats(),
|
||||
Cpu = cpu_utilization(),
|
||||
FreeMem = free_memory(),
|
||||
#node_metric{
|
||||
timestamp = Now,
|
||||
node = node(),
|
||||
@@ -104,7 +109,9 @@ collect_metrics() ->
|
||||
active_sessions = session_count(),
|
||||
ws_connections = ws_count(),
|
||||
io_in = IoIn,
|
||||
io_out = IoOut
|
||||
io_out = IoOut,
|
||||
memory_available = FreeMem,
|
||||
cpu_utilization = Cpu
|
||||
}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
@@ -165,7 +172,71 @@ io_stats() ->
|
||||
TotalOut = lists:sum([send_oct(Port) || Port <- Ports]),
|
||||
{PrevIn, PrevOut} = get_prev_io(),
|
||||
ets:insert(?IO_CACHE_TAB, [{in, TotalIn}, {out, TotalOut}]),
|
||||
{TotalIn - PrevIn, TotalOut - PrevOut}.
|
||||
DeltaIn = max(0, TotalIn - PrevIn),
|
||||
DeltaOut = max(0, TotalOut - PrevOut),
|
||||
{DeltaIn, DeltaOut}.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Возвращает объём свободной системной памяти (в байтах).
|
||||
%%% Безопасно при отсутствии поддержки в ОС.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec free_memory() -> non_neg_integer().
|
||||
free_memory() ->
|
||||
try erlang:system_info(os_free_memory) of
|
||||
Free when Free > 0 -> Free;
|
||||
_ -> read_meminfo()
|
||||
catch
|
||||
_:_ -> read_meminfo()
|
||||
end.
|
||||
|
||||
read_meminfo() ->
|
||||
try
|
||||
{ok, Binary} = file:read_file("/proc/meminfo"),
|
||||
Content = binary_to_list(Binary),
|
||||
case re:run(Content, "MemAvailable:\\s+(\\d+) kB", [{capture, [1], list}]) of
|
||||
{match, [MemKbStr]} -> list_to_integer(MemKbStr) * 1024; % кБ -> байты
|
||||
_ -> 0
|
||||
end
|
||||
catch _:_ -> 0
|
||||
end.
|
||||
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Вычисляет утилизацию CPU (в процентах) за интервал сбора.
|
||||
%%% Использует статистику /proc/stat.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-spec cpu_utilization() -> float().
|
||||
cpu_utilization() ->
|
||||
try
|
||||
{ok, Binary} = file:read_file("/proc/stat"),
|
||||
Content = binary_to_list(Binary),
|
||||
{match, [TotalStr]} = re:run(Content, "^cpu\\s+(.*)$", [{capture, [1], list}, multiline]),
|
||||
Tokens = string:tokens(TotalStr, " "),
|
||||
[User, Nice, System, Idle, Iowait, Irq, Softirq, Steal | _] = lists:map(fun list_to_integer/1, Tokens),
|
||||
Total = User + Nice + System + Idle + Iowait + Irq + Softirq + Steal,
|
||||
IdleTotal = Idle + Iowait,
|
||||
|
||||
% Получаем предыдущие значения безопасным способом
|
||||
{PrevIdle, PrevTotal} = case ets:lookup(?IO_CACHE_TAB, cpu_stats) of
|
||||
[{cpu_stats, PI, PT}] -> {PI, PT};
|
||||
[] -> {0, 0}
|
||||
end,
|
||||
ets:insert(?IO_CACHE_TAB, [{cpu_stats, IdleTotal, Total}]),
|
||||
|
||||
case PrevTotal of
|
||||
0 -> 0.0; % первый замер, нет данных для расчёта
|
||||
_ ->
|
||||
DeltaTotal = Total - PrevTotal,
|
||||
DeltaIdle = IdleTotal - PrevIdle,
|
||||
if DeltaTotal > 0 ->
|
||||
(1.0 - DeltaIdle / DeltaTotal) * 100.0;
|
||||
true -> 0.0
|
||||
end
|
||||
end
|
||||
catch
|
||||
_:_ -> 0.0
|
||||
end.
|
||||
|
||||
-spec get_prev_io() -> {non_neg_integer(), non_neg_integer()}.
|
||||
get_prev_io() ->
|
||||
|
||||
Reference in New Issue
Block a user