%% Copyright (C) 2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
%% Author: Pau Espin Pedrol <pespin@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(s1gw_metrics).

-export([init/0,
         get_current_value/1,
         enb_metric/2,
         ctr_reset/1,
         ctr_inc/1,
         ctr_inc/2,
         ctr_list/0,
         gauge_reset/1,
         gauge_set/2,
         gauge_inc/1,
         gauge_inc/2,
         gauge_dec/1,
         gauge_list/0]).

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

-define(S1GW_COUNTERS, [
    ?S1GW_CTR_PFCP_HEARTBEAT_REQ_TX,
    ?S1GW_CTR_PFCP_HEARTBEAT_REQ_TIMEOUT,
    ?S1GW_CTR_PFCP_HEARTBEAT_RESP_RX,
    ?S1GW_CTR_PFCP_HEARTBEAT_REQ_RX,
    ?S1GW_CTR_PFCP_HEARTBEAT_RESP_TX,
    ?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_PFCP_UNEXPECTED_PDU,
    ?S1GW_CTR_S1AP_ENB_ALL_RX,
    ?S1GW_CTR_S1AP_ENB_ALL_RX_UNKNOWN_ENB,
    ?S1GW_CTR_S1AP_PROXY_EXCEPTION,                         %% exception(s) occurred
    %% s1ap_proxy: INcoming PDU counters
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ALL,                        %% received total
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_DROP_ALL,                   %% dropped 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_MODIFY_REQ,            %% E-RAB MODIFY.req PDUs
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_ERAB_MODIFY_RSP,            %% E-RAB MODIFY.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_ERAB_MOD_CNF,               %% E-RAB MODIFY.cnf 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
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_RELEASE_CTX_REQ,            %% UE CONTEXT RELEASE.req PDUs
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_RELEASE_CTX_CMD,            %% UE CONTEXT RELEASE.cmd PDUs
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_RELEASE_CTX_COMPL,          %% UE CONTEXT RELEASE.compl PDUs
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_HANDOVER_CMD,               %% HANDOVER COMMAND PDUs
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_HANDOVER_REQ,               %% HANDOVER REQUEST PDUs
    ?S1GW_CTR_S1AP_PROXY_IN_PKT_HANDOVER_REQ_ACK,           %% HANDOVER REQUEST ACKNOWLEDGE 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

    %% SCTP related counters
    ?S1GW_CTR_SCTP_ERROR_ALL,                               %% total number of SCTP errors
    ?S1GW_CTR_SCTP_ERROR_SEND_FAILED,                       %% send operation failed
    ?S1GW_CTR_SCTP_ERROR_PDAPI_EVENT,                       %% partial delivery failure
    ?S1GW_CTR_SCTP_ERROR_REMOTE_ERROR                       %% remote error
]).

-define(S1GW_GAUGES, [
    ?S1GW_GAUGE_PFCP_ASSOCIATED,
    ?S1GW_GAUGE_S1AP_ENB_NUM_SCTP_CONNECTIONS
]).

-type counter() :: [ctr | _].
-type gauge() :: [gauge | _].
-type metric() :: counter() | gauge().

-export_type([metric/0,
              counter/0,
              gauge/0]).


-spec register_all(Type, List) -> Result
    when Type :: exometer:type(),
         List :: list(exometer:name()),
         Result :: list(exometer:name()).
register_all(Type, List) ->
    lists:filter(fun(Name) -> exometer:new(Name, Type) =/= ok end, List).


%% ------------------------------------------------------------------
%% public API
%% ------------------------------------------------------------------

init() ->
    ?LOG_INFO("Initiating metrics"),
    logger:set_module_level(exometer_report_statsd, info),
    [] = register_all(counter, ?S1GW_COUNTERS),
    [] = register_all(gauge, ?S1GW_GAUGES).


-spec get_current_value(metric()) -> integer().
get_current_value(Name) ->
    Result = exometer:get_value(Name, value),
    {ok, [{value, Value}]} = Result,
    Value.


-spec enb_metric(M0, GlobalENBId) -> M1
    when M0 :: metric(),
         M1 :: metric(),
         GlobalENBId :: string().
enb_metric([Type | Name], GlobalENBId) ->
    [Type, enb, GlobalENBId | Name].


%%%%%%%%%%%%%
%% CTR APIs
%%%%%%%%%%%%%
-spec ctr_reset(counter()) -> ok | {error, any()}.
ctr_reset(Name) ->
    ?LOG_DEBUG("ctr_reset(~p)", [Name]),
    exometer:reset(Name).

-spec ctr_inc(counter(), integer()) -> ok | {error, any()}.
ctr_inc(Name, Value) ->
    ?LOG_DEBUG("ctr_inc(~p, ~p)", [Name, Value]),
    exometer:update(Name, Value).

-spec ctr_inc(counter()) -> ok | {error, any()}.
ctr_inc(Name) ->
    ctr_inc(Name, 1).

-spec ctr_list() -> [counter()].
ctr_list() -> ?S1GW_COUNTERS.

%%%%%%%%%%%%%
%% GAUGE APIs
%%%%%%%%%%%%%
-spec gauge_reset(gauge()) -> ok | {error, any()}.
gauge_reset(Name) ->
    ?LOG_DEBUG("gauge_reset(~p)", [Name]),
    exometer:reset(Name).

-spec gauge_set(gauge(), integer()) -> ok | {error, any()}.
gauge_set(Name, Value) ->
    exometer:update(Name, Value).

-spec gauge_inc(gauge(), 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(gauge()) -> ok | {error, any()}.
gauge_inc(Name) ->
    gauge_inc(Name, 1).

-spec gauge_dec(gauge()) -> ok | {error, any()}.
gauge_dec(Name) ->
    gauge_inc(Name, -1).

-spec gauge_list() -> [gauge()].
gauge_list() -> ?S1GW_GAUGES.


%% vim:set ts=4 sw=4 et:
