laforge has submitted this change. ( 
https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41035?usp=email )

Change subject: enb_registry: track eNB uptime
......................................................................

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(-)

Approvals:
  Jenkins Builder: Verified
  laforge: Looks good to me, approved
  pespin: Looks good to me, but someone else must approve




diff --git a/src/enb_registry.erl b/src/enb_registry.erl
index 4495d44..f2be559 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().

@@ -73,6 +78,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
@@ -141,6 +147,7 @@
 %% ------------------------------------------------------------------

 init([]) ->
+    spawn_link(fun() -> heartbeat(?HEARTBEAT_INTERVAL) end),
     {ok, #state{enbs = maps:new(),
                 pids = maps:new(),
                 next_handle = 0}}.
@@ -163,7 +170,8 @@
                         pid => Pid,
                         mon_ref => MonRef,
                         state => connecting,
-                        reg_time => erlang:monotonic_time()},
+                        reg_time => erlang:monotonic_time(),
+                        uptime => 0},
             ?LOG_INFO("eNB (handle=~p, pid ~p) registered", [Handle, Pid]),
             {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)}};
@@ -227,12 +236,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}.
@@ -245,6 +261,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, pid=~p) has been unregistered", 
[Handle, Pid]),
             {noreply, S#state{enbs = maps:remove(Handle, ENBs),
                               pids = maps:remove(Pid, PIDs)}};
@@ -267,6 +284,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,
@@ -316,4 +347,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 <i...@sysmocom.de>
-%% Author: Vadim Yanitskiy <vyanits...@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 252e237..bcba952 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 https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41035?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: I94fd06e559ae52d4d9c8b22e618e48dff718b53c
Gerrit-Change-Number: 41035
Gerrit-PatchSet: 5
Gerrit-Owner: fixeria <vyanits...@sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: jolly <andr...@eversberg.eu>
Gerrit-Reviewer: laforge <lafo...@osmocom.org>
Gerrit-Reviewer: pespin <pes...@sysmocom.de>

Reply via email to