bagofix
Test & Deploy to Snapdeploy / build-test-deploy (push) Failing after 24m46s

This commit is contained in:
2026-06-05 23:20:03 +03:00
parent 2497fe951b
commit 17b2e617d7
41 changed files with 365 additions and 194 deletions
+24 -4
View File
@@ -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.
+17 -1
View File
@@ -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)}].
%% ===================================================================
%% Индексы
+4
View File
@@ -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,
+74 -3
View File
@@ -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() ->