fixeria has uploaded this change for review.

View Change

enb_registry: track eNB uptime

Now that we have the global eNB registry, it makes more sense to
perform uptime tracking there instead of spawning a separate
enb_uptime process for each eNB connection.

Change-Id: I94fd06e559ae52d4d9c8b22e618e48dff718b53c
Related: SYS#7594, SYS#7066
---
M src/enb_registry.erl
D src/enb_uptime.erl
M src/s1ap_proxy.erl
3 files changed, 59 insertions(+), 109 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/35/41035/1
diff --git a/src/enb_registry.erl b/src/enb_registry.erl
index edd9f3d..9f9ac3f 100644
--- a/src/enb_registry.erl
+++ b/src/enb_registry.erl
@@ -51,6 +51,11 @@

-include_lib("kernel/include/logger.hrl").

+-include("s1gw_metrics.hrl").
+
+%% Heartbeat interval
+-define(HEARTBEAT_INTERVAL, 5_000).
+

-type enb_handle() :: non_neg_integer().

@@ -72,6 +77,7 @@
mon_ref := reference(), %% monitor() reference
state := enb_state(), %% connection state
reg_time := integer(), %% registration time (monotonic)
+ uptime := non_neg_integer(), %% seconds since reg_time
genb_id_str => string(), %% Global-eNB-ID
enb_id => s1ap_proxy:enb_id(), %% eNB-ID
plmn_id => s1ap_proxy:plmn_id(), %% PLMN-ID
@@ -142,6 +148,7 @@
%% ------------------------------------------------------------------

init([]) ->
+ spawn_link(fun() -> heartbeat(?HEARTBEAT_INTERVAL) end),
{ok, #state{enbs = maps:new(),
pids = maps:new(),
next_handle = 0}}.
@@ -163,7 +170,8 @@
EnbInfo = #{pid => Pid,
mon_ref => MonRef,
state => connecting,
- reg_time => erlang:monotonic_time()},
+ reg_time => erlang:monotonic_time(),
+ uptime => 0},
?LOG_INFO("eNB (handle=~p) registered", [Handle]),
{reply, {ok, Handle}, S#state{enbs = ENBs#{Handle => EnbInfo},
pids = PIDs#{Pid => Handle},
@@ -178,6 +186,7 @@
{ok, #{pid := Pid,
mon_ref := MonRef}} ->
erlang:demonitor(MonRef, [flush]),
+ enb_metrics_reset(maps:get(Handle, ENBs)),
?LOG_INFO("eNB (handle=~p) unregistered", [Handle]),
{reply, ok, S#state{enbs = maps:remove(Handle, ENBs),
pids = maps:remove(Pid, PIDs)}};
@@ -216,12 +225,19 @@
{ok, EnbInfo0} ->
?LOG_INFO("eNB (handle=~p) event: ~p", [Handle, Event]),
EnbInfo1 = enb_handle_event(EnbInfo0, Event),
+ enb_metrics_register(EnbInfo1),
{noreply, S#state{enbs = maps:update(Handle, EnbInfo1, ENBs)}};
error ->
?LOG_ERROR("eNB (handle=~p) is *not* registered", [Handle]),
{noreply, S}
end;

+handle_cast(heartbeat,
+ #state{enbs = ENBs} = S) ->
+ T = erlang:monotonic_time(),
+ Fun = fun(Handle, EnbInfo) -> enb_report_uptime(Handle, EnbInfo, T) end,
+ {noreply, S#state{enbs = maps:map(Fun, ENBs)}};
+
handle_cast(Info, S) ->
?LOG_ERROR("unknown ~p(): ~p", [?FUNCTION_NAME, Info]),
{noreply, S}.
@@ -234,6 +250,7 @@
case maps:find(Pid, PIDs) of
{ok, Pid} ->
Handle = maps:get(Pid, PIDs),
+ enb_metrics_reset(maps:get(Handle, ENBs)),
?LOG_INFO("eNB (handle=~p) has been unregistered", [Handle]),
{noreply, S#state{enbs = maps:remove(Handle, ENBs),
pids = maps:remove(Pid, PIDs)}};
@@ -256,6 +273,20 @@
%% private API
%% ------------------------------------------------------------------

+-spec enb_metrics_register(enb_info()) -> term().
+enb_metrics_register(#{genb_id_str := GlobalENBId}) ->
+ catch exometer:new(?S1GW_CTR_ENB_UPTIME(GlobalENBId), counter);
+
+enb_metrics_register(_) -> ok.
+
+
+-spec enb_metrics_reset(enb_info()) -> term().
+enb_metrics_reset(#{genb_id_str := GlobalENBId}) ->
+ s1gw_metrics:ctr_reset(?S1GW_CTR_ENB_UPTIME(GlobalENBId));
+
+enb_metrics_reset(_) -> ok.
+
+
-spec enb_handle_event(enb_info(), enb_event()) -> enb_info().
enb_handle_event(EnbInfo, {connecting, ConnInfo}) ->
EnbInfo#{state => connecting,
@@ -305,4 +336,28 @@
maps:get(Field, M, undefined) =:= Value.


+-spec enb_report_uptime(Handle, EnbInfo, T1) -> enb_info()
+ when Handle :: enb_handle(),
+ EnbInfo :: enb_info(),
+ T1 :: integer().
+enb_report_uptime(Handle, #{reg_time := T0} = EnbInfo, T1) ->
+ Uptime = erlang:convert_time_unit(T1 - T0, native, second),
+ ?LOG_DEBUG("eNB (handle=~p) uptime ~p s", [Handle, Uptime]),
+ %% update ?S1GW_CTR_ENB_UPTIME
+ case EnbInfo of
+ #{genb_id_str := GlobalENBId} ->
+ Current = s1gw_metrics:get_current_value(?S1GW_CTR_ENB_UPTIME(GlobalENBId)),
+ s1gw_metrics:ctr_inc(?S1GW_CTR_ENB_UPTIME(GlobalENBId), Uptime - Current);
+ _ -> nop
+ end,
+ EnbInfo#{uptime => Uptime}.
+
+
+-spec heartbeat(timeout()) -> no_return().
+heartbeat(Tval) ->
+ timer:sleep(Tval),
+ gen_server:cast(?MODULE, ?FUNCTION_NAME),
+ heartbeat(Tval). %% keep going
+
+
%% vim:set ts=4 sw=4 et:
diff --git a/src/enb_uptime.erl b/src/enb_uptime.erl
deleted file mode 100644
index ac71ebd..0000000
--- a/src/enb_uptime.erl
+++ /dev/null
@@ -1,100 +0,0 @@
-%% Copyright (C) 2025 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
-%% Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
-%%
-%% All Rights Reserved
-%%
-%% SPDX-License-Identifier: AGPL-3.0-or-later
-%%
-%% This program is free software; you can redistribute it and/or modify
-%% it under the terms of the GNU Affero General Public License as
-%% published by the Free Software Foundation; either version 3 of the
-%% License, or (at your option) any later version.
-%%
-%% This program is distributed in the hope that it will be useful,
-%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-%% GNU General Public License for more details.
-%%
-%% You should have received a copy of the GNU Affero General Public License
-%% along with this program. If not, see <https://www.gnu.org/licenses/>.
-%%
-%% Additional Permission under GNU AGPL version 3 section 7:
-%%
-%% If you modify this Program, or any covered work, by linking or
-%% combining it with runtime libraries of Erlang/OTP as released by
-%% Ericsson on https://www.erlang.org (or a modified version of these
-%% libraries), containing parts covered by the terms of the Erlang Public
-%% License (https://www.erlang.org/EPLICENSE), the licensors of this
-%% Program grant you additional permission to convey the resulting work
-%% without the need to license the runtime libraries of Erlang/OTP under
-%% the GNU Affero General Public License. Corresponding Source for a
-%% non-source form of such a combination shall include the source code
-%% for the parts of the runtime libraries of Erlang/OTP used as well as
-%% that of the covered work.
-
--module(enb_uptime).
-
--export([start_link/0,
- genb_id_ind/2,
- shutdown/1]).
-
--include_lib("kernel/include/logger.hrl").
-
--include("s1gw_metrics.hrl").
-
--define(INTERVAL, 5_000).
-
-
-%% ------------------------------------------------------------------
-%% public API
-%% ------------------------------------------------------------------
-
--spec start_link() -> pid().
-start_link() ->
- T0 = erlang:monotonic_time(),
- spawn_link(fun() -> loop(#{time => T0}) end).
-
-
--spec genb_id_ind(pid(), string()) -> ok.
-genb_id_ind(Pid, GlobalENBId) ->
- Pid ! {?FUNCTION_NAME, GlobalENBId},
- ok.
-
-
--spec shutdown(pid()) -> ok.
-shutdown(Pid) ->
- Pid ! stop,
- ok.
-
-
-%% ------------------------------------------------------------------
-%% private API
-%% ------------------------------------------------------------------
-
-loop(#{genb_id := GlobalENBId, time := T0} = S) ->
- receive
- stop ->
- s1gw_metrics:ctr_reset(?S1GW_CTR_ENB_UPTIME(GlobalENBId)),
- ok;
- Event ->
- ?LOG_ERROR("Rx unexpected event: ~p", [Event])
- after ?INTERVAL ->
- T1 = erlang:monotonic_time(),
- Diff = erlang:convert_time_unit(T1 - T0, native, second),
- s1gw_metrics:ctr_inc(?S1GW_CTR_ENB_UPTIME(GlobalENBId), Diff),
- loop(S#{time => T1})
- end;
-
-loop(#{} = S) ->
- receive
- {genb_id_ind, GlobalENBId} ->
- catch exometer:new(?S1GW_CTR_ENB_UPTIME(GlobalENBId), counter),
- loop(S#{genb_id => GlobalENBId});
- stop ->
- ok;
- Event ->
- ?LOG_ERROR("Rx unexpected event: ~p", [Event])
- end.
-
-
-%% vim:set ts=4 sw=4 et:
diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl
index e41d1aa..d3aa65b 100644
--- a/src/s1ap_proxy.erl
+++ b/src/s1ap_proxy.erl
@@ -82,8 +82,7 @@
mme_ue_id :: undefined | mme_ue_id(),
enb_ue_id :: undefined | enb_ue_id(),
erab_id :: undefined | erab_id(),
- path :: [s1ap_ie_id()],
- enb_uptime :: pid()
+ path :: [s1ap_ie_id()]
}).

-type proxy_state() :: #proxy_state{}.
@@ -147,8 +146,7 @@

init([EnbHandle, ConnInfo]) ->
process_flag(trap_exit, true),
- {ok, #proxy_state{enb_uptime = enb_uptime:start_link(),
- enb_handle = EnbHandle,
+ {ok, #proxy_state{enb_handle = EnbHandle,
conn_info = ConnInfo,
erabs = dict:new(),
path = []}}.
@@ -193,8 +191,7 @@
{noreply, S}.


-terminate(Reason, #proxy_state{} = S) ->
- enb_uptime:shutdown(S#proxy_state.enb_uptime),
+terminate(Reason, #proxy_state{}) ->
?LOG_NOTICE("Terminating, reason ~p", [Reason]),
ok.

@@ -407,8 +404,6 @@
enb_registry:enb_event(S#proxy_state.enb_handle,
{s1setup, enb_info(S)}),
gtpu_kpi_enb_register(S),
- enb_uptime:genb_id_ind(S#proxy_state.enb_uptime,
- S#proxy_state.genb_id_str),
%% there's nothing to patch in this PDU, so we forward it as-is
{forward, S};


To view, visit change 41035. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: I94fd06e559ae52d4d9c8b22e618e48dff718b53c
Gerrit-Change-Number: 41035
Gerrit-PatchSet: 1
Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de>