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

Change subject: Introduce initial metrics support
......................................................................

Introduce initial metrics support

Metrics are implemented with the "exometer" library.
A reference config to export the metrics through statsd is provided.

Change-Id: I952e198238384dca4be94f91a01d7cfff0a1471f
Related: SYS#7065
---
M config/sys.config
A include/s1gw_metrics.hrl
M rebar.config
M rebar.lock
M src/osmo_s1gw.app.src
M src/osmo_s1gw_sup.erl
M src/pfcp_peer.erl
M src/s1ap_proxy.erl
A src/s1gw_metrics.erl
M src/sctp_proxy.erl
M src/sctp_server.erl
A test/exometer_mock.erl
M test/s1ap_proxy_test.erl
13 files changed, 357 insertions(+), 8 deletions(-)

Approvals:
  Jenkins Builder: Verified
  pespin: Looks good to me, approved




diff --git a/config/sys.config b/config/sys.config
index 45a14cb..d405db8 100644
--- a/config/sys.config
+++ b/config/sys.config
@@ -35,6 +35,54 @@
                                                 {prefix, [" ", prefix, " ::"], 
""},
                                                 " ", msg,
                                                 {mfa, [" (", mfa, ":", line, 
")"], ""},
-                                                reset, "\n"]}}}}]}]}].
+                                                reset, "\n"]}}}}]}
+  ]
+ },
+ {exometer_core,
+  [{predefined,
+    [%{[erlang, memory], {function, erlang, memory, [], value, []}, []},
+     %{[erlang, system_info], {function, erlang, system_info, ['$dp'], value, 
[process_count]}, []},
+     %{[erlang, statistics], {function, erlang, statistics, ['$dp'], value, 
[run_queue]}, []},
+     %{[erlang, io], {function, erlang, statistics, [io], match, {{'_', 
input}, {'_', output}}}, []}
+    ]
+   },
+   {report,
+    [{reporters,
+      [%%{exometer_report_tty, []},
+       {exometer_report_statsd,
+        [{hostname, "127.0.4.10"},
+         {port, 8125},
+         {prefix, "s1gw"},
+         {type_map, []}
+        ]
+       }
+      ]
+     },
+     {subscribers,
+      [%%{select, {[{ {['_' | '_'],'_','_'}, [], ['$_']}],
+       %% exometer_report_tty, value, 1000, true}}
+       {select, {[{ {['_' | '_'],counter,'_'}, [], ['$_']}],
+                 exometer_report_statsd,
+                 value,
+                 1000,
+                 true,
+                 [{report_type, counter}]
+                }
+       },
+       {select, {[{ {['_' | '_'],gauge,'_'}, [], ['$_']}],
+                 exometer_report_statsd,
+                 value,
+                 1000,
+                 true,
+                 [{report_type, gauge}]
+                }
+       }
+      ]
+     }
+    ]
+   }
+  ]
+ }
+].

 %% vim:set ts=2 sw=2 et:
diff --git a/include/s1gw_metrics.hrl b/include/s1gw_metrics.hrl
new file mode 100644
index 0000000..551c136
--- /dev/null
+++ b/include/s1gw_metrics.hrl
@@ -0,0 +1,32 @@
+-define(S1GW_CTR_PFCP_ASSOC_SETUP_REQ_TX, [ctr, pfcp, assoc_setup_req, tx]).
+-define(S1GW_CTR_PFCP_ASSOC_SETUP_REQ_TIMEOUT, [ctr, pfcp, assoc_setup_req, 
timeout]).
+-define(S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX, [ctr, pfcp, assoc_setup_resp, rx]).
+-define(S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX_ACK, [ctr, pfcp, assoc_setup_resp, 
rx_ack]).
+-define(S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX_NACK, [ctr, pfcp, assoc_setup_resp, 
rx_nack]).
+-define(S1GW_CTR_S1AP_ENB_ALL_RX, [ctr, s1ap, enb, all, rx]).
+-define(S1GW_CTR_S1AP_ENB_ALL_RX_UNKNOWN_ENB, [ctr, s1ap, enb, all, 
rx_unknown_enb]).
+-define(S1GW_CTR_S1AP_PROXY_UPLINK_PACKETS_QUEUED, [ctr, s1ap, proxy, 
uplink_packets_queued]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ALL, [ctr, s1ap, proxy, in_pkt, all]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_DECODE_ERROR, [ctr, s1ap, proxy, in_pkt, 
decode_error]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR, [ctr, s1ap, proxy, in_pkt, 
proc_error]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_REQ, [ctr, s1ap, proxy, in_pkt, 
erab_setup_req]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_RSP, [ctr, s1ap, proxy, in_pkt, 
erab_setup_rsp]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_CMD, [ctr, s1ap, proxy, 
in_pkt, erab_release_cmd]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_RSP, [ctr, s1ap, proxy, 
in_pkt, erab_release_rsp]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_IND, [ctr, s1ap, proxy, 
in_pkt, erab_release_ind]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MOD_IND, [ctr, s1ap, proxy, in_pkt, 
erab_mod_ind]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_REQ, [ctr, s1ap, proxy, in_pkt, 
init_ctx_req]).
+-define(S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_RSP, [ctr, s1ap, proxy, in_pkt, 
init_ctx_rsp]).
+-define(S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL, [ctr, s1ap, proxy, out_pkt, 
forward, all]).
+-define(S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_PROC, [ctr, s1ap, proxy, out_pkt, 
forward, proc]).
+-define(S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED, [ctr, s1ap, proxy, 
out_pkt, forward, unmodified]).
+-define(S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ALL, [ctr, s1ap, proxy, out_pkt, 
reply, all]).
+-define(S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ERAB_SETUP_RSP, [ctr, s1ap, proxy, 
out_pkt, reply, erab_setup_rsp]).
+
+-define(S1GW_GAUGE_PFCP_ASSOCIATED, [gauge, pfcp, associated]).
+-define(S1GW_GAUGE_S1AP_ENB_NUM_SCTP_CONNECTIONS, [gauge, s1ap, enb, 
num_sctp_connections]).
+-define(S1GW_GAUGE_S1AP_PROXY_UPLINK_PACKETS_QUEUED, [gauge, s1ap, proxy, 
uplink_packets_queued]).
+
+%% NOTE: Remember to add new entries to s1gw_metric:init()!
+
+%% vim:set ts=4 sw=4 et:
diff --git a/rebar.config b/rebar.config
index 91bd759..ef3931d 100644
--- a/rebar.config
+++ b/rebar.config
@@ -8,7 +8,11 @@
 {deps, [{logger_color_formatter,
          {git, "https://github.com/rlipscombe/logger_color_formatter.git";, 
{tag, "0.5.0"}}},
         {pfcplib,
-         {git, "https://github.com/travelping/pfcplib.git";, {branch, 
"master"}}}
+         {git, "https://github.com/travelping/pfcplib.git";, {branch, 
"master"}}},
+        {exometer_core,
+         {git, "https://github.com/Feuerlabs/exometer_core.git";, {branch, 
"master"}}},
+        {exometer_report_statsd,
+         {git, "https://github.com/esl/exometer_report_statsd.git";, {branch, 
"master"}}}
        ]}.

 %% test deps
diff --git a/rebar.lock b/rebar.lock
index 3b99c22..c52d677 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,16 +1,33 @@
 {"1.2.0",
 [{<<"cut">>,{pkg,<<"cut">>,<<"1.0.3">>},1},
+ {<<"exometer_core">>,
+  {git,"https://github.com/Feuerlabs/exometer_core.git";,
+       {ref,"f9c7abc095edc893c9354a3d5f061715de1d9e79"}},
+  0},
+ {<<"exometer_report_statsd">>,
+  {git,"https://github.com/esl/exometer_report_statsd.git";,
+       {ref,"f1c369becb6e57871f1c7b0e491f6c3a302a65ee"}},
+  0},
+ {<<"hut">>,{pkg,<<"hut">>,<<"1.3.0">>},1},
  {<<"logger_color_formatter">>,
   {git,"https://github.com/rlipscombe/logger_color_formatter.git";,
        {ref,"f1c96f979e6350f8cd787d27fe9ff003cbf3416b"}},
   0},
+ {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.4.1">>},1},
  {<<"pfcplib">>,
   {git,"https://github.com/travelping/pfcplib.git";,
        {ref,"e505c0a4c05f5f14a2c40c7ebf36db62cc911d93"}},
-  0}]}.
+  0},
+ {<<"setup">>,{pkg,<<"setup">>,<<"2.1.0">>},1}]}.
 [
 {pkg_hash,[
- {<<"cut">>, 
<<"1577F2F3BC0F2BF3B97903B7426F8A3D79523687B6A444D0F59A095EF69A0E81">>}]},
+ {<<"cut">>, 
<<"1577F2F3BC0F2BF3B97903B7426F8A3D79523687B6A444D0F59A095EF69A0E81">>},
+ {<<"hut">>, 
<<"71F2F054E657C03F959CF1ACC43F436EA87580696528CA2A55C8AFB1B06C85E7">>},
+ {<<"parse_trans">>, 
<<"6E6AA8167CB44CC8F39441D05193BE6E6F4E7C2946CB2759F015F8C56B76E5FF">>},
+ {<<"setup">>, 
<<"05F69185A5EB71474C9BC6BA892565651EC7507791F85632B7B914DBFE130510">>}]},
 {pkg_hash_ext,[
- {<<"cut">>, 
<<"1A4A25DB2B7C5565FD28B314A4EEB898B1ED3CAFFA1AB09149345FB5731ED04B">>}]}
+ {<<"cut">>, 
<<"1A4A25DB2B7C5565FD28B314A4EEB898B1ED3CAFFA1AB09149345FB5731ED04B">>},
+ {<<"hut">>, 
<<"7E15D28555D8A1F2B5A3A931EC120AF0753E4853A4C66053DB354F35BF9AB563">>},
+ {<<"parse_trans">>, 
<<"620A406CE75DADA827B82E453C19CF06776BE266F5A67CFF34E1EF2CBB60E49A">>},
+ {<<"setup">>, 
<<"EFD072578F0CF85BEA96CAAFFC7ADB0992398272522660A136E10567377071C5">>}]}
 ].
diff --git a/src/osmo_s1gw.app.src b/src/osmo_s1gw.app.src
index 8bb89de..363d6d6 100644
--- a/src/osmo_s1gw.app.src
+++ b/src/osmo_s1gw.app.src
@@ -8,7 +8,9 @@
                        kernel,
                        stdlib,
                        logger_color_formatter,
-                       pfcplib
+                       pfcplib,
+                       exometer_core,
+                       exometer_report_statsd
                        ]},
        {modules, []},
        {mod, {osmo_s1gw_app, []}},
diff --git a/src/osmo_s1gw_sup.erl b/src/osmo_s1gw_sup.erl
index c6adb55..32272bf 100644
--- a/src/osmo_s1gw_sup.erl
+++ b/src/osmo_s1gw_sup.erl
@@ -81,6 +81,7 @@
                 worker,
                 [pfcp_peer]},

+    s1gw_metrics:init(),
     {ok, {{one_for_one, 5, 10}, [SctpServer, PfcpPeer]}}.


diff --git a/src/pfcp_peer.erl b/src/pfcp_peer.erl
index aa33e3a..d0da4bb 100644
--- a/src/pfcp_peer.erl
+++ b/src/pfcp_peer.erl
@@ -50,6 +50,7 @@

 -include_lib("kernel/include/logger.hrl").
 -include_lib("pfcplib/include/pfcp_packet.hrl").
+-include("s1gw_metrics.hrl").

 %% 3GPP TS 29.244, section 4.2 "UDP Header and Port Numbers"
 -define(PFCP_PORT, 8805).
@@ -136,6 +137,7 @@

 init([LocAddr, RemAddr]) ->
     process_flag(trap_exit, true),
+    s1gw_metrics:gauge_set(?S1GW_GAUGE_PFCP_ASSOCIATED, 0),
     {ok, Sock} = gen_udp:open(?PFCP_PORT, [binary,
                                            {ip, LocAddr},
                                            {reuseaddr, true},
@@ -158,6 +160,7 @@
 connecting(enter, OldState,
            #peer_state{} = S0) ->
     ?LOG_INFO("State change: ~p -> ~p", [OldState, ?FUNCTION_NAME]),
+    s1gw_metrics:gauge_set(?S1GW_GAUGE_PFCP_ASSOCIATED, 0),
     %% Tx PFCP Association Setup
     {ok, S1} = send_assoc_setup(S0),
     {keep_state, S1, [{state_timeout, 2_000, assoc_setup_timeout}]};
@@ -166,6 +169,7 @@
 connecting(state_timeout, assoc_setup_timeout, S) ->
     % Re-start sending PFCP Association Setup above:
     ?LOG_NOTICE("PFCP Association Setup timeout, UPF may be down, 
retrying..."),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_PFCP_ASSOC_SETUP_REQ_TIMEOUT),
     {repeat_state, S};

 %% Handle incoming PFCP PDU(s)
@@ -177,11 +181,15 @@
               ie = #{pfcp_cause := 'Request accepted',
                      recovery_time_stamp := #recovery_time_stamp{time = 
RRTS}}} ->
             ?LOG_INFO("Rx Association Setup Response (Request accepted)"),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX_ACK),
             {next_state, connected,
              S#peer_state{rem_rts = RRTS}};
         #pfcp{type = association_setup_response,
               ie = #{pfcp_cause := Cause}} ->
             ?LOG_ERROR("Rx Association Setup Response (~p)", [Cause]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX_NACK),
             {stop, {shutdown, assoc_setup_nack}};
         %% 3GPP TS 29.244, 6.2.2.2 Heartbeat Request
         %% A CP function or UP function shall be prepared to receive a 
Heartbeat Request
@@ -201,6 +209,7 @@
 %% CONNECTED state
 connected(enter, OldState, S) ->
     ?LOG_INFO("State change: ~p -> ~p", [OldState, ?FUNCTION_NAME]),
+    s1gw_metrics:gauge_set(?S1GW_GAUGE_PFCP_ASSOCIATED, 1),
     {keep_state, S};

 connected({call, From},
@@ -272,7 +281,8 @@
     ?LOG_NOTICE("Terminating in state ~p, reason ~p", [State, Reason]),
     case State of
         connected ->
-            send_assoc_release(S);
+            send_assoc_release(S),
+            s1gw_metrics:gauge_set(?S1GW_GAUGE_PFCP_ASSOCIATED, 0);
         _ ->
             nop
     end,
@@ -395,6 +405,7 @@
 send_assoc_setup(#peer_state{loc_rts = LRTS} = S) ->
     IEs = #{node_id => #node_id{id = get_node_id(S)},
             recovery_time_stamp => #recovery_time_stamp{time = LRTS}},
+    s1gw_metrics:ctr_inc(?S1GW_CTR_PFCP_ASSOC_SETUP_REQ_TX),
     send_pdu({association_setup_request, IEs}, S).


diff --git a/src/s1ap_proxy.erl b/src/s1ap_proxy.erl
index 4758484..68d0d0e 100644
--- a/src/s1ap_proxy.erl
+++ b/src/s1ap_proxy.erl
@@ -43,6 +43,8 @@

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

+-include("s1gw_metrics.hrl").
+
 -include("S1AP-PDU-Descriptions.hrl").
 -include("S1AP-PDU-Contents.hrl").
 -include("S1AP-Containers.hrl").
@@ -90,6 +92,7 @@
 %% Process an S1AP PDU
 -spec process_pdu(binary(), proxy_state()) -> {{proxy_action(), binary()}, 
proxy_state()}.
 process_pdu(OrigData, S0) ->
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL),
     case decode_pdu(OrigData) of
         {ok, PDU} ->
             ?LOG_DEBUG("Rx S1AP PDU: ~p", [PDU]),
@@ -97,13 +100,24 @@
                 {{Action, NewPDU}, S1} ->
                     {ok, NewData} = encode_pdu(NewPDU),
                     ?LOG_DEBUG("Tx (~p) S1AP PDU: ~p", [Action, NewPDU]),
+                    case Action of
+                        forward ->
+                            
s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL),
+                            
s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_PROC);
+                        reply ->
+                            
s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ALL)
+                    end,
                     {{Action, NewData}, S1};
                 {forward, S1} ->
                     ?LOG_DEBUG("Tx (forward) S1AP PDU unmodified"),
+                    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL),
+                    
s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED),
                     {{forward, OrigData}, S1}
             end;
         {error, {asn1, Error}} ->
             ?LOG_ERROR("S1AP PDU decoding failed: ~p", [Error]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_DECODE_ERROR),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED),
             {{forward, OrigData}, S0} %% XXX: forward as-is or drop?
     end.

@@ -148,6 +162,7 @@
             #'InitiatingMessage'{procedureCode = ?'id-E-RABSetup',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing E-RAB SETUP REQUEST"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_REQ),
     case handle_ies(C0#'E-RABSetupRequest'.protocolIEs,
                     ?'id-E-RABToBeSetupListBearerSUReq', S0) of
         {{ok, IEs}, S1} ->
@@ -156,7 +171,9 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process E-RAB SETUP REQUEST: ~p", [Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             PDU = build_erab_setup_response_failure(S1),
+            
s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ERAB_SETUP_RSP),
             {{reply, PDU}, S1} %% reply PDU back to sender
     end;

@@ -165,6 +182,7 @@
             #'SuccessfulOutcome'{procedureCode = ?'id-E-RABSetup',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing E-RAB SETUP RESPONSE"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_RSP),
     case handle_ies(C0#'E-RABSetupResponse'.protocolIEs,
                     ?'id-E-RABSetupListBearerSURes', S0) of
         {{ok, IEs}, S1} ->
@@ -173,6 +191,7 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process E-RAB SETUP RESPONSE: ~p", 
[Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             {forward, S1} %% XXX: forward as-is or drop?
     end;

@@ -183,6 +202,7 @@
             #'InitiatingMessage'{procedureCode = ?'id-E-RABRelease',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing E-RAB RELEASE COMMAND"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_CMD),
     case handle_ies(C0#'E-RABReleaseCommand'.protocolIEs,
                     ?'id-E-RABToBeReleasedList', S0) of
         {{ok, IEs}, S1} ->
@@ -191,6 +211,7 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process E-RAB RELEASE COMMAND: ~p", 
[Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             {forward, S1} %% XXX: forward as-is or drop?
     end;

@@ -199,6 +220,7 @@
             #'SuccessfulOutcome'{procedureCode = ?'id-E-RABRelease',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing E-RAB RELEASE RESPONSE"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_RSP),
     case handle_ies(C0#'E-RABReleaseResponse'.protocolIEs,
                     ?'id-E-RABReleaseListBearerRelComp', S0) of
         {{ok, IEs}, S1} ->
@@ -207,6 +229,7 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process E-RAB RELEASE RESPONSE: ~p", 
[Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             {forward, S1} %% XXX: forward as-is or drop?
     end;

@@ -215,6 +238,7 @@
             #'InitiatingMessage'{procedureCode = ?'id-E-RABReleaseIndication',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing E-RAB RELEASE INDICATION"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_IND),
     case handle_ies(C0#'E-RABReleaseIndication'.protocolIEs,
                     ?'id-E-RABReleasedList', S0) of
         {{ok, IEs}, S1} ->
@@ -223,6 +247,7 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process E-RAB RELEASE INDICATION: ~p", 
[Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             {forward, S1} %% XXX: forward as-is or drop?
     end;

@@ -231,6 +256,7 @@
             #'InitiatingMessage'{procedureCode = 
?'id-E-RABModificationIndication',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing E-RAB MODIFICATION INDICATION"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MOD_IND),
     IEs0 = C0#'E-RABModificationIndication'.protocolIEs,
     %% E-RAB to be Modified List
     %% TODO: handle {error, Reason}
@@ -247,6 +273,7 @@
             #'InitiatingMessage'{procedureCode = ?'id-InitialContextSetup',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP REQUEST"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_REQ),
     case handle_ies(C0#'InitialContextSetupRequest'.protocolIEs,
                     ?'id-E-RABToBeSetupListCtxtSUReq', S0) of
         {{ok, IEs}, S1} ->
@@ -255,6 +282,7 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process INITIAL CONTEXT SETUP REQUEST: ~p", 
[Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             {forward, S1} %% XXX: forward as-is or drop?
     end;

@@ -263,6 +291,7 @@
             #'SuccessfulOutcome'{procedureCode = ?'id-InitialContextSetup',
                                  value = C0} = Msg}, S0) ->
     ?LOG_DEBUG("Processing INITIAL CONTEXT SETUP RESPONSE"),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_RSP),
     case handle_ies(C0#'InitialContextSetupResponse'.protocolIEs,
                     ?'id-E-RABSetupListCtxtSURes', S0) of
         {{ok, IEs}, S1} ->
@@ -271,6 +300,7 @@
             {{forward, PDU}, S1}; %% forward patched PDU
         {{error, Reason}, S1} ->
             ?LOG_NOTICE("Failed to process INITIAL CONTEXT SETUP RESPONSE: 
~p", [Reason]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR),
             {forward, S1} %% XXX: forward as-is or drop?
     end;

diff --git a/src/s1gw_metrics.erl b/src/s1gw_metrics.erl
new file mode 100644
index 0000000..6b078e5
--- /dev/null
+++ b/src/s1gw_metrics.erl
@@ -0,0 +1,168 @@
+%% Copyright (C) 2024 by sysmocom - s.f.m.c. GmbH <[email protected]>
+%% Author: Pau Espin Pedrol <[email protected]>
+%%
+%% 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(s1gw_metrics).
+
+-export([init/0,
+         ctr_reset/1,
+         ctr_inc/1,
+         ctr_inc/2,
+         gauge_reset/1,
+         gauge_set/2,
+         gauge_inc/1,
+         gauge_inc/2,
+         gauge_dec/1]).
+
+-include_lib("kernel/include/logger.hrl").
+-include("s1gw_metrics.hrl").
+
+-define(S1GW_COUNTERS, [
+    ?S1GW_CTR_PFCP_ASSOC_SETUP_REQ_TX,
+    ?S1GW_CTR_PFCP_ASSOC_SETUP_REQ_TIMEOUT,
+    ?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX,
+    ?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX_ACK,
+    ?S1GW_CTR_PFCP_ASSOC_SETUP_RESP_RX_NACK,
+    ?S1GW_CTR_S1AP_ENB_ALL_RX,
+    ?S1GW_CTR_S1AP_ENB_ALL_RX_UNKNOWN_ENB,
+    ?S1GW_CTR_S1AP_PROXY_UPLINK_PACKETS_QUEUED,
+    %% s1ap_proxy: INcoming PDU counters
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL,                        %% received total
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_DECODE_ERROR,               %% failed to decode
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_PROC_ERROR,                 %% failed to 
process
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_REQ,             %% E-RAB SETUP.req 
PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_SETUP_RSP,             %% E-RAB SETUP.rsp 
PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_CMD,           %% E-RAB 
RELEASE.cmd PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_RSP,           %% E-RAB 
RELEASE.rsp PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_RELEASE_IND,           %% E-RAB 
RELEASE.ind PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MOD_IND,               %% E-RAB 
MODIFY.ind PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_REQ,               %% INITIAL CONTEXT 
SETUP.req PDUs
+    ?S1GW_CTR_S1AP_PROXY_IN_PKT_INIT_CTX_RSP,               %% INITIAL CONTEXT 
SETUP.rsp PDUs
+    %% s1ap_proxy: OUTgoing PDU counters
+    ?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_ALL,                   %% forwarded: total
+    ?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_PROC,                  %% forwarded: 
processed
+    ?S1GW_CTR_S1AP_PROXY_OUT_PKT_FWD_UNMODIFIED,            %% forwarded: 
unmodified
+    ?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ALL,                 %% replied: total
+    ?S1GW_CTR_S1AP_PROXY_OUT_PKT_REPLY_ERAB_SETUP_RSP       %% replied: E-RAB 
SETUP.rsp
+]).
+
+-define(S1GW_GAUGES, [
+    ?S1GW_GAUGE_PFCP_ASSOCIATED,
+    ?S1GW_GAUGE_S1AP_ENB_NUM_SCTP_CONNECTIONS,
+    ?S1GW_GAUGE_S1AP_PROXY_UPLINK_PACKETS_QUEUED
+]).
+
+-spec new_ctr(list()) -> ok.
+new_ctr(Name) ->
+    %%?LOG_INFO("New counter ~p", [Name]),
+    ok = exometer:new(Name, counter).
+
+-spec new_ctrs(list()) -> ok.
+new_ctrs([]) ->
+    ok;
+new_ctrs([Name | MoreNames]) ->
+    new_ctr(Name),
+    new_ctrs(MoreNames).
+
+-spec new_gauge(list()) -> ok.
+new_gauge(Name) ->
+    %%?LOG_INFO("New gauge ~p", [Name]),
+    ok = exometer:new(Name, gauge).
+
+-spec new_gauges(list()) -> ok.
+new_gauges([]) ->
+    ok;
+new_gauges([Name | MoreNames]) ->
+    new_gauge(Name),
+    new_gauges(MoreNames).
+
+-spec get_current_value(list()) -> integer().
+get_current_value(Name) ->
+    Result = exometer:get_value(Name, value),
+    {ok, [{value, PrevVal}]} = Result,
+    PrevVal.
+
+%% ------------------------------------------------------------------
+%% public API
+%% ------------------------------------------------------------------
+
+init() ->
+    ?LOG_INFO("Initiating metrics"),
+    new_ctrs(?S1GW_COUNTERS),
+    new_gauges(?S1GW_GAUGES).
+
+%%%%%%%%%%%%%
+%% CTR APIs
+%%%%%%%%%%%%%
+-spec ctr_reset(list()) -> ok | {error, any()}.
+ctr_reset(Name) ->
+    ?LOG_DEBUG("ctr_reset(~p)", [Name]),
+    exometer:reset(Name).
+
+-spec ctr_inc(list(), integer()) -> ok | {error, any()}.
+ctr_inc(Name, Value) ->
+    ?LOG_DEBUG("ctr_inc(~p, ~p)", [Name, Value]),
+    exometer:update(Name, Value).
+
+-spec ctr_inc(list()) -> ok | {error, any()}.
+ctr_inc(Name) ->
+    ctr_inc(Name, 1).
+
+%%%%%%%%%%%%%
+%% GAUGE APIs
+%%%%%%%%%%%%%
+-spec gauge_reset(list()) -> ok | {error, any()}.
+gauge_reset(Name) ->
+    ?LOG_DEBUG("gauge_reset(~p)", [Name]),
+    exometer:reset(Name).
+
+-spec gauge_set(list(), integer()) -> ok | {error, any()}.
+gauge_set(Name, Value) ->
+    exometer:update(Name, Value).
+
+-spec gauge_inc(list(), integer()) -> ok | {error, any()}.
+gauge_inc(Name, Value) ->
+    PrevVal = get_current_value(Name),
+    ?LOG_DEBUG("gauge_inc(~p, ~p): pre_val=~p", [Name, Value, PrevVal]),
+    exometer:update(Name, Value + PrevVal).
+
+-spec gauge_inc(list()) -> ok | {error, any()}.
+gauge_inc(Name) ->
+    gauge_inc(Name, 1).
+
+-spec gauge_dec(list()) -> ok | {error, any()}.
+gauge_dec(Name) ->
+    gauge_inc(Name, -1).
+
+
+%% vim:set ts=4 sw=4 et:
diff --git a/src/sctp_proxy.erl b/src/sctp_proxy.erl
index b37bead..280e408 100644
--- a/src/sctp_proxy.erl
+++ b/src/sctp_proxy.erl
@@ -49,6 +49,8 @@
 -include_lib("kernel/include/inet.hrl").
 -include_lib("kernel/include/inet_sctp.hrl").

+-include("s1gw_metrics.hrl").
+
 %% ------------------------------------------------------------------
 %% public API
 %% ------------------------------------------------------------------
@@ -104,6 +106,8 @@
 %% Handle an eNB -> MME data forwarding request (queue)
 connecting(cast, {send_data, Data},
            #{tx_queue := Pending} = S) ->
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_PROXY_UPLINK_PACKETS_QUEUED),
+    s1gw_metrics:gauge_inc(?S1GW_GAUGE_S1AP_PROXY_UPLINK_PACKETS_QUEUED),
     {keep_state, S#{tx_queue := [Data | Pending]}};

 %% Handle an #sctp_assoc_change event (connection state)
@@ -254,6 +258,7 @@

 sctp_send_pending([Data | Pending], S0) ->
     S1 = sctp_send(Data, S0),
+    s1gw_metrics:gauge_dec(?S1GW_CTR_S1AP_PROXY_UPLINK_PACKETS_QUEUED),
     sctp_send_pending(Pending, S1);

 sctp_send_pending([], S) ->
diff --git a/src/sctp_server.erl b/src/sctp_server.erl
index 8ea311f..58d91ca 100644
--- a/src/sctp_server.erl
+++ b/src/sctp_server.erl
@@ -48,6 +48,7 @@
 -include_lib("kernel/include/inet.hrl").
 -include_lib("kernel/include/inet_sctp.hrl").

+-include("s1gw_metrics.hrl").
 -include("s1ap.hrl").

 -record(server_state, {sock, clients, mme_addr_port}).
@@ -170,12 +171,14 @@
 sctp_recv(State, {FromAddr, FromPort,
                   [#sctp_sndrcvinfo{assoc_id = Aid}], Data}) ->
     ?LOG_DEBUG("eNB connection (id=~p, ~p:~p) -> MME: ~p", [Aid, FromAddr, 
FromPort, Data]),
+    s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_ENB_ALL_RX),
     case dict:find(Aid, State#server_state.clients) of
         {ok, #client_state{pid = Pid}} ->
             sctp_proxy:send_data(Pid, Data);
         error ->
             ?LOG_ERROR("eNB connection (id=~p, ~p:~p) is not known to us?!?",
-                       [Aid, FromAddr, FromPort])
+                       [Aid, FromAddr, FromPort]),
+            s1gw_metrics:ctr_inc(?S1GW_CTR_S1AP_ENB_ALL_RX_UNKNOWN_ENB)
     end,
     State;

@@ -190,6 +193,7 @@
 client_add(Clients, Aid, FromAddr, FromPort, {MmeAddr, MmePort}) ->
     {ok, Pid} = sctp_proxy:start_link(Aid, MmeAddr, MmePort),
     NewClient = #client_state{addr_port = {FromAddr, FromPort}, pid = Pid},
+    s1gw_metrics:gauge_inc(?S1GW_GAUGE_S1AP_ENB_NUM_SCTP_CONNECTIONS),
     dict:store(Aid, NewClient, Clients).


@@ -200,6 +204,7 @@
             %% the proxy process might be already dead, so we guard
             %% against exceptions like noproc or {nodedown,Node}.
             catch sctp_proxy:shutdown(Client#client_state.pid),
+            s1gw_metrics:gauge_dec(?S1GW_GAUGE_S1AP_ENB_NUM_SCTP_CONNECTIONS),
             dict:erase(Aid, Clients);
         error ->
             Clients
diff --git a/test/exometer_mock.erl b/test/exometer_mock.erl
new file mode 100644
index 0000000..5a09692
--- /dev/null
+++ b/test/exometer_mock.erl
@@ -0,0 +1,24 @@
+-module(exometer_mock).
+
+-export([mock_all/0,
+         unmock_all/0]).
+
+
+%% ------------------------------------------------------------------
+%% public API
+%% ------------------------------------------------------------------
+
+%% mock all pfcp_peer module functions
+mock_all() ->
+    meck:new(exometer),
+    meck:expect(exometer, new, fun(_Name, _Type) -> ok end),
+    meck:expect(exometer, reset, fun(_Name) -> ok end),
+    meck:expect(exometer, update, fun(_Name, _Value) -> ok end),
+    meck:expect(exometer, get_value, fun(_Name, DataPoint) -> 
{ok,[{DataPoint,0}]} end).
+
+%% unmock all pfcp_peer module functions
+unmock_all() ->
+    true = meck:validate(exometer),
+    meck:unload(exometer).
+
+%% vim:set ts=4 sw=4 et:
diff --git a/test/s1ap_proxy_test.erl b/test/s1ap_proxy_test.erl
index 04d52df..28fe209 100644
--- a/test/s1ap_proxy_test.erl
+++ b/test/s1ap_proxy_test.erl
@@ -15,10 +15,12 @@

 start() ->
     pfcp_mock:mock_all(),
+    exometer_mock:mock_all(),
     s1ap_proxy:init().


 stop(S) ->
+    exometer_mock:unmock_all(),
     pfcp_mock:unmock_all(),
     s1ap_proxy:deinit(S).


--
To view, visit https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/37955?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: I952e198238384dca4be94f91a01d7cfff0a1471f
Gerrit-Change-Number: 37955
Gerrit-PatchSet: 8
Gerrit-Owner: pespin <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <[email protected]>
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>

Reply via email to