pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/38019?usp=email )
Change subject: WIP: hss: Initial validation of Prometheus metrics ......................................................................
WIP: hss: Initial validation of Prometheus metrics
Change-Id: I001e7ae9a06e6f765965a5d4d442a53d5c5dffad --- M hss/HSS_Tests.default M hss/HSS_Tests.ttcn M hss/gen_links.sh M hss/open5gs-hss.yaml M hss/regen_makefile.sh M library/Misc_Helpers.ttcn A library/Prometheus_Checker.ttcn 7 files changed, 202 insertions(+), 2 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/19/38019/1
diff --git a/hss/HSS_Tests.default b/hss/HSS_Tests.default index 4c0b502..3a78848 100644 --- a/hss/HSS_Tests.default +++ b/hss/HSS_Tests.default @@ -1,6 +1,8 @@ [LOGGING]
[TESTPORT_PARAMETERS] +*.TCP.noDelay := "yes" // turn off nagle +*.HTTP.use_notification_ASPs := "yes"
[MODULE_PARAMETERS]
diff --git a/hss/HSS_Tests.ttcn b/hss/HSS_Tests.ttcn index a5503a7..de0e9f6 100644 --- a/hss/HSS_Tests.ttcn +++ b/hss/HSS_Tests.ttcn @@ -10,11 +10,16 @@ import from DIAMETER_ts29_272_Templates all; import from DIAMETER_Emulation all;
+import from Prometheus_Checker all; + type record of hexstring SubscriberConfigs;
modulepar { charstring mp_hss_hostname := "127.0.0.4"; integer mp_hss_port := 3868; + charstring mp_hss_prometheus_hostname := "127.0.0.5"; + integer mp_hss_prometheus_port := c_prometheus_default_http_port; + integer mp_hss_prometheus_stats_interval := 1; /* in seconds, match open5gs yml cfg. */ charstring mp_diam_local_hostname := "127.0.0.1"; integer mp_diam_local_port := 3868; charstring mp_diam_orig_realm := "localdomain"; @@ -78,11 +83,12 @@ }
/* per-session component; we typically have 1..N per testcase */ -type component Cli_Session_CT { +type component Cli_Session_CT extends Prometheus_Checker_CT { var SessionPars g_pars;
port DIAMETER_Conn_PT S6a; port DIAMETEREM_PROC_PT S6a_PROC; + } function f_diam_connhldr_expect_eteid(UINT32 ete_id) runs on Cli_Session_CT { S6a_PROC.call(DIAMETEREM_register_eteid:{ete_id, null}) { @@ -181,6 +187,7 @@ private function f_handler_init(void_fn fn, SessionPars pars) runs on Cli_Session_CT { g_pars := valueof(pars); + f_prometheus_init(mp_hss_prometheus_hostname, mp_hss_prometheus_port); fn.apply(); }
@@ -228,6 +235,13 @@ );
f_dia_ulr_ula(sub_data); + + var template (value) PrometheusExpects expects := { + ts_PrometheusExpect("s6a_rx_ulr", COUNTER, min := 1, max := 1), + ts_PrometheusExpect("s6a_tx_ula", COUNTER, min := 1, max := 1) + } + f_sleep(int2float(mp_hss_prometheus_stats_interval) + 0.1); + f_prometheus_snapshot(); setverdict(pass); } testcase TC_ulr_ula() runs on MTC_CT { diff --git a/hss/gen_links.sh b/hss/gen_links.sh index 276eb1e..d06175a 100755 --- a/hss/gen_links.sh +++ b/hss/gen_links.sh @@ -21,11 +21,20 @@ FILES="DIAMETER_EncDec.cc" gen_links $DIR $FILES
+DIR=$BASEDIR/titan.TestPorts.Common_Components.Abstract_Socket/src +FILES="Abstract_Socket.cc Abstract_Socket.hh " +gen_links $DIR $FILES + +DIR=$BASEDIR/titan.TestPorts.HTTPmsg/src +FILES="HTTPmsg_MessageLen.ttcn HTTPmsg_MessageLen_Function.cc HTTPmsg_PT.cc HTTPmsg_PT.hh HTTPmsg_PortType.ttcn HTTPmsg_Types.ttcn " +gen_links $DIR $FILES + DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc " FILES+="DIAMETER_Types.ttcn DIAMETER_CodecPort.ttcn DIAMETER_CodecPort_CtrlFunct.ttcn DIAMETER_CodecPort_CtrlFunctDef.cc DIAMETER_Emulation.ttcn " FILES+="DIAMETER_Templates.ttcn DIAMETER_ts29_272_Templates.ttcn " FILES+="SCTP_Templates.ttcn " +FILES+="HTTP_Adapter.ttcn Prometheus_Checker.ttcn " gen_links $DIR $FILES
ignore_pp_results diff --git a/hss/open5gs-hss.yaml b/hss/open5gs-hss.yaml index 6b90bdc..c536cd8 100644 --- a/hss/open5gs-hss.yaml +++ b/hss/open5gs-hss.yaml @@ -11,7 +11,11 @@
hss: freeDiameter: freediameter.conf - + diameter_stats_interval: 1 + metrics: + server: + - address: 127.0.0.5 + port: 9090 parameter:
max: diff --git a/hss/regen_makefile.sh b/hss/regen_makefile.sh index 23fbd73..ac05766 100755 --- a/hss/regen_makefile.sh +++ b/hss/regen_makefile.sh @@ -4,8 +4,11 @@
FILES=" *.ttcn + Abstract_Socket.cc DIAMETER_CodecPort_CtrlFunctDef.cc DIAMETER_EncDec.cc + HTTPmsg_MessageLen_Function.cc + HTTPmsg_PT.cc IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc diff --git a/library/Misc_Helpers.ttcn b/library/Misc_Helpers.ttcn index a742b0b..967c8d1 100644 --- a/library/Misc_Helpers.ttcn +++ b/library/Misc_Helpers.ttcn @@ -79,6 +79,16 @@ return count; }
+/* Return true if str starts exactly with token: */ +function f_str_startswith(charstring str, charstring token) return boolean +{ + if (lengthof(str) < lengthof(token)) { + return false; + } + var charstring str_start := substr(str, 0, lengthof(token)); + return str_start == token; +} + /* Return true if str ends exactly with token: */ function f_str_endswith(charstring str, charstring token) return boolean { diff --git a/library/Prometheus_Checker.ttcn b/library/Prometheus_Checker.ttcn new file mode 100644 index 0000000..83d7e79 --- /dev/null +++ b/library/Prometheus_Checker.ttcn @@ -0,0 +1,158 @@ +module Prometheus_Checker { + +/* (C) 2024 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All rights reserved. + * + * Author: Pau Espin Pedrol pespin@sysmocom.de + * + * Released under the terms of GNU General Public License, Version 2 or + * (at your option) any later version. + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +import from Misc_Helpers all; +import from Socket_API_Definitions all; + +import from General_Types all; +import from Osmocom_Types all; + +import from HTTP_Adapter all; +import from HTTPmsg_Types all; + +const integer c_prometheus_default_http_port := 9090; + +type enumerated PrometheusMetricType { + COUNTER, + GAUGE +}; + +type record PrometheusMetricKey { + charstring name, + PrometheusMetricType mtype +}; +type set of PrometheusMetricKey PrometheusMetricKeys; + +type record PrometheusMetric { + PrometheusMetricKey key, + integer val +}; +type set of PrometheusMetric PrometheusMetrics; + +type record PrometheusExpect { + PrometheusMetricKey key, + integer min, + integer max +}; +type set of PrometheusExpect PrometheusExpects; + +modulepar { + boolean mp_enable_stats := true +} + +type component Prometheus_Checker_CT extends http_CT { + var float g_tout_http := 5.0; +}; + +template (value) PrometheusMetricKey +ts_PrometheusMetricKey(template (value) charstring name, + template (value) PrometheusMetricType mtype) := { + name := name, + mtype := mtype +}; + +template (value) PrometheusMetric +ts_PrometheusMetric(template (value) charstring name, + template (value) PrometheusMetricType mtype, + template (value) integer val) := { + key := ts_PrometheusMetricKey(name, mtype), + val := val +}; + +template (value) PrometheusExpect +ts_PrometheusExpect(template (value) charstring name, + template (value) PrometheusMetricType mtype, + template (value) integer min, + template (value) integer max) := { + key := ts_PrometheusMetricKey(name, mtype), + min := min, + max := max +}; + +function f_prometheus_init(charstring http_host, integer http_port := c_prometheus_default_http_port) runs on Prometheus_Checker_CT { + var HTTP_Adapter_Params http_adapter_pars := { + http_host := http_host, + http_port := http_port, + use_ssl := false + }; + f_http_init(http_adapter_pars); +} + +private function f_prometheus_metric_mtype_from_string(charstring str) return PrometheusMetricType +{ + var PrometheusMetricType mtype; + if (str == "counter") { + mtype := COUNTER; + } else if (str == "gauge") { + mtype:= GAUGE; + } else { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Unknown Prometheus metric type: ", str)); + } + return mtype; +} + +private function f_prometheus_parse_http_response(charstring body) return PrometheusMetrics +{ + var PrometheusMetrics metrics := {}; + var Misc_Helpers.ro_charstring lines := f_str_split(body, "\n"); + log("PESPIN lines: ", lines); + for (var integer i := 0; i + 2 < lengthof(lines); i := i + 3) { + var PrometheusMetric it; + /* HELP line, example: "# HELP cx_rx_unknown Received Cx unknown messages" */ + if (not f_str_startswith(lines[i], "# HELP ")) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Failed parsing Prometheus HTTP response line: ", lines[i])); + } + + /* TYPE line, example: "# TYPE cx_rx_unknown counter" */ + if (not f_str_startswith(lines[i + 1], "# TYPE ")) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Failed parsing Prometheus HTTP response line: ", lines[i + 1])); + } + var Misc_Helpers.ro_charstring type_tokens := f_str_split(lines[i + 1], " "); + if (lengthof(type_tokens) < 4) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Failed parsing Prometheus HTTP response line: ", type_tokens)); + } + it.key.name := type_tokens[2]; + it.key.mtype := f_prometheus_metric_mtype_from_string(type_tokens[3]); + + /* Value line, example: "cx_rx_unknown 0" */ + if (not f_str_startswith(lines[i + 2], it.key.name)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Failed parsing Prometheus HTTP response line: ", lines[i + 2])); + } + var Misc_Helpers.ro_charstring value_tokens := f_str_split(lines[i + 2], " "); + it.val := str2int(value_tokens[1]); + + metrics := metrics & { it }; + } + return metrics; +} + +function f_prometheus_snapshot() runs on Prometheus_Checker_CT return PrometheusMetrics { + var HTTPMessage http_resp; + var PrometheusMetrics metrics := {}; + + if (not mp_enable_stats) { + return metrics; + } + f_http_tx_request(url := "/metrics", method := "GET", tout := g_tout_http); + http_resp := f_http_rx_response(tr_HTTP_Resp(200), tout := g_tout_http); + + metrics := f_prometheus_parse_http_response(http_resp.response.body); + + return metrics; +} + +}