pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/37956?usp=email )
Change subject: StatsD_Checker: Allow building and running without VTY support ......................................................................
StatsD_Checker: Allow building and running without VTY support
Some programs may support exporting to statsd, but may not support the Osmocom VTY set of commands to send reports, or not have a VTY at all.
New features are added to the public API of StatsD_Checker which make it possible to use it...
* without "stats reset": Feature to take snapshots (f_statsd_snapshot()) which can later be used to validate expectancies with values relative to the snapshot, using API f_statsd_expect_from_snapshot(). This way, one can do: """ var StatsDExpects statsd_exp := { /* relative expectancies here... */ }; var StatsDMetrics statsd_snapshot := f_statsd_snapshot(f_statsd_keys_from_expect(statsd_exp)); /* do some test stuff here changing the state of the IUT... */ f_statsd_expect_from_snapshot(statsd_exp, snapshot := statsd_snapshot); """
* without polling ("stats report"), aka with periodict reporting: New parameter wait_converge in f_statsd_expect(), which allows overcoming race conditions with StatsD server processing older incoming metrics due to periodic reporting. This feature also allows a test to wait until a state changes in the IUT.
Change-Id: I5421c76e4f303fd16d4db945a1c69910e40ac820 --- M bsc/gen_links.sh M bsc/regen_makefile.sh M hnbgw/gen_links.sh M hnbgw/regen_makefile.sh M hnodeb/gen_links.sh M hnodeb/regen_makefile.sh D library/StatsD_Checker.ttcn A library/StatsD_Checker.ttcnpp M library/StatsD_Types.ttcn M mgw/gen_links.sh M mgw/regen_makefile.sh M ns/gen_links.sh M ns/regen_makefile.sh M pcu/gen_links.sh M pcu/regen_makefile.sh M upf/gen_links.sh M upf/regen_makefile.sh 17 files changed, 483 insertions(+), 290 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/56/37956/1
diff --git a/bsc/gen_links.sh b/bsc/gen_links.sh index cb9f12f..05c1e51 100755 --- a/bsc/gen_links.sh +++ b/bsc/gen_links.sh @@ -70,7 +70,7 @@ FILES="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn GSM_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPA_Types.ttcn IPA_CodecPort.ttcn IPA_CodecPort_CtrlFunct.ttcn IPA_CodecPort_CtrlFunctDef.cc IPA_Emulation.ttcnpp L3_Templates.ttcn BSSMAP_Templates.ttcn RAN_Emulation.ttcnpp RLCMAC_CSN1_Templates.ttcn RLCMAC_CSN1_Types.ttcn GSM_RR_Types.ttcn RSL_Types.ttcn RSL_Emulation.ttcn MGCP_Emulation.ttcn SDP_Templates.ttcn MGCP_Types.ttcn MGCP_Templates.ttcn MGCP_CodecPort.ttcn MGCP_CodecPort_CtrlFunct.ttcn MGCP_CodecPort_CtrlFunctDef.cc BSSAP_CodecPort.ttcn RAN_Adapter.ttcnpp Osmocom_CTRL_Types.ttcn Osmocom_CTRL_Functions.ttcn Osmocom_CTRL_Adapter.ttcn RTP_CodecPort.ttcn RTP_CodecPort_CtrlFunct.ttcn RTP_CodecPort_CtrlFunctDef.cc RTP_Emulation.ttcn IuUP_Types.ttcn IuUP_EncDec.cc IuUP_Emulation.ttcn SCCP_Templates.ttcn IPA_Testing.ttcn GSM_SystemInformation.ttcn GSM_RestOctets.ttcn " FILES+="CBSP_Types.ttcn CBSP_Templates.ttcn " FILES+="CBSP_CodecPort.ttcn CBSP_CodecPort_CtrlFunct.ttcn CBSP_CodecPort_CtrlFunctdef.cc CBSP_Adapter.ttcn " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="BSSAP_LE_CodecPort.ttcn BSSAP_LE_Emulation.ttcn BSSAP_LE_Types.ttcn BSSAP_LE_Adapter.ttcn BSSLAP_Types.ttcn BSSMAP_LE_Templates.ttcn " FILES+="AbisOML_Types.ttcn"
diff --git a/bsc/regen_makefile.sh b/bsc/regen_makefile.sh index fc53e30..6683f29 100755 --- a/bsc/regen_makefile.sh +++ b/bsc/regen_makefile.sh @@ -32,6 +32,7 @@ -DRAN_EMULATION_BSSAP -DRAN_EMULATION_CTRL -DRAN_EMULATION_MGCP + -DSTATSD_HAVE_VTY -DUSE_MTP3_DISTRIBUTOR "
diff --git a/hnbgw/gen_links.sh b/hnbgw/gen_links.sh index e11b915..51ce602 100755 --- a/hnbgw/gen_links.sh +++ b/hnbgw/gen_links.sh @@ -102,7 +102,7 @@ FILES+="RAN_Adapter.ttcnpp RAN_Emulation.ttcnpp BSSAP_CodecPort.ttcn SCCP_Templates.ttcn " FILES+="PFCP_CodecPort.ttcn PFCP_CodecPort_CtrlFunct.ttcn PFCP_CodecPort_CtrlFunctDef.cc PFCP_Emulation.ttcn PFCP_Templates.ttcn " FILES+="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn GSM_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPA_Types.ttcn IPA_CodecPort.ttcn IPA_CodecPort_CtrlFunct.ttcn IPA_CodecPort_CtrlFunctDef.cc IPA_Emulation.ttcnpp Osmocom_CTRL_Types.ttcn Osmocom_CTRL_Functions.ttcn Osmocom_CTRL_Adapter.ttcn RTP_CodecPort.ttcn RTP_CodecPort_CtrlFunct.ttcn RTP_CodecPort_CtrlFunctDef.cc RTP_Emulation.ttcn IuUP_Types.ttcn IuUP_EncDec.cc IuUP_Emulation.ttcn " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="L3_Templates.ttcn L3_Common.ttcn " FILES+="SCTP_Templates.ttcn " gen_links $DIR $FILES diff --git a/hnbgw/regen_makefile.sh b/hnbgw/regen_makefile.sh index 13db985..c67323e 100755 --- a/hnbgw/regen_makefile.sh +++ b/hnbgw/regen_makefile.sh @@ -33,6 +33,7 @@ export CPPFLAGS_TTCN3=" -DIPA_EMULATION_CTRL -DRAN_EMULATION_RANAP + -DSTATSD_HAVE_VTY -DUSE_MTP3_DISTRIBUTOR "
diff --git a/hnodeb/gen_links.sh b/hnodeb/gen_links.sh index 35aa039..71265b1 100755 --- a/hnodeb/gen_links.sh +++ b/hnodeb/gen_links.sh @@ -62,7 +62,7 @@ FILES="HNBLLIF_Types.ttcn HNBLLIF_Templates.ttcn HNBLLIF_CodecPort.ttcn " FILES+="Iuh_Types.ttcn Iuh_CodecPort.ttcn Iuh_CodecPort_CtrlFunctDef.cc Iuh_CodecPort_CtrlFunct.ttcn Iuh_Emulation.ttcn DNS_Helpers.ttcn " FILES+="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPA_Types.ttcn IPA_CodecPort.ttcn IPA_CodecPort_CtrlFunct.ttcn IPA_CodecPort_CtrlFunctDef.cc IPA_Emulation.ttcnpp Osmocom_CTRL_Types.ttcn Osmocom_CTRL_Functions.ttcn Osmocom_CTRL_Adapter.ttcn RTP_CodecPort.ttcn RTP_CodecPort_CtrlFunct.ttcn RTP_CodecPort_CtrlFunctDef.cc RTP_Emulation.ttcn IuUP_Types.ttcn IuUP_EncDec.cc IuUP_Emulation.ttcn " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="GTPv1C_CodecPort.ttcn GTPv1C_CodecPort_CtrlFunct.ttcn GTPv1C_CodecPort_CtrlFunctDef.cc GTPv1C_Templates.ttcn " FILES+="GTPv1U_CodecPort.ttcn GTPv1U_CodecPort_CtrlFunct.ttcn GTPv1U_CodecPort_CtrlFunctDef.cc GTPv1U_Templates.ttcn " FILES+="GTP_Emulation.ttcn IPCP_Types.ttcn GSM_Types.ttcn " diff --git a/hnodeb/regen_makefile.sh b/hnodeb/regen_makefile.sh index cf5fed3..be7dec5 100755 --- a/hnodeb/regen_makefile.sh +++ b/hnodeb/regen_makefile.sh @@ -33,6 +33,7 @@
export CPPFLAGS_TTCN3=" -DIPA_EMULATION_CTRL + -DSTATSD_HAVE_VTY "
../regen-makefile.sh -e $NAME $FILES diff --git a/library/StatsD_Checker.ttcn b/library/StatsD_Checker.ttcn deleted file mode 100644 index 2ad4c9f..0000000 --- a/library/StatsD_Checker.ttcn +++ /dev/null @@ -1,280 +0,0 @@ -module StatsD_Checker { - -/* Verifies that StatsD metrics in a test match the expected values - * Uses StatsD_CodecPort to receive the statsd messages from the DUT - * and a separate VTY connection to reset and trigger the stats. - * - * When using this you should configure your stats reporter to disable - * interval-based reports and always send all metrics: - * > stats interval 0 - * > stats reporter statsd - * > remote-ip a.b.c.d - * > remote-port 8125 - * > level subscriber - * > flush-period 1 - * > mtu 1024 - * > enable - * - * (C) 2020 by sysmocom s.f.m.c. GmbH info@sysmocom.de - * All rights reserved. - * - * Author: Daniel Willmann dwillmann@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 StatsD_Types all; -import from StatsD_CodecPort all; -import from StatsD_CodecPort_CtrlFunct all; - -import from Osmocom_Types all; -import from Osmocom_VTY_Functions all; -import from TELNETasp_PortType all; - -modulepar { - /* Whether to test stats values */ - boolean mp_enable_stats := true; -} - -type record StatsDExpect { - MetricName name, - MetricType mtype, - MetricValue min, - MetricValue max -}; - -type set of StatsDExpect StatsDExpects; - -type record StatsDExpectPriv { - StatsDExpect expect, - integer seen -} - -type set of StatsDExpectPriv StatsDExpectPrivs; - -type enumerated StatsDResultType { - e_Matched, - e_Mismatched, - e_NotFound -} - -type record StatsDExpectResult { - StatsDResultType kind, - integer idx -} - -type component StatsD_Checker_CT { - port TELNETasp_PT STATSVTY; - port STATSD_PROC_PT STATSD_PROC; - port STATSD_CODEC_PT STATS; - timer T_statsd := 5.0; -} - -type component StatsD_ConnHdlr { - port STATSD_PROC_PT STATSD_PROC; -} - -signature STATSD_reset(); -signature STATSD_expect(in StatsDExpects expects) return boolean; - -type port STATSD_PROC_PT procedure { - inout STATSD_reset, STATSD_expect; -} with {extension "internal"}; - -/* Expect templates and functions */ - - -/* StatsD checker component */ -function main(charstring statsd_host, integer statsd_port) runs on StatsD_Checker_CT { - var StatsD_ConnHdlr vc_conn; - var StatsDExpects expects; - var Result res; - - while (not mp_enable_stats) { - log("StatsD checker disabled by modulepar"); - f_sleep(3600.0); - } - - map(self:STATS, system:STATS); - res := StatsD_CodecPort_CtrlFunct.f_IPL4_listen(STATS, statsd_host, statsd_port, { udp := {} }, {}); - if (not ispresent(res.connId)) { - Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, - "Could not bind StatsD socket, check your configuration"); - } - - /* Connect to VTY and reset stats */ - map(self:STATSVTY, system:STATSVTY); - f_vty_set_prompts(STATSVTY); - f_vty_transceive(STATSVTY, "enable"); - - /* Reset the stats system at start */ - f_vty_transceive(STATSVTY, "stats reset"); - - while (true) { - alt { - [] STATSD_PROC.getcall(STATSD_reset:{}) -> sender vc_conn { - f_vty_transceive(STATSVTY, "stats reset"); - STATSD_PROC.reply(STATSD_reset:{}) to vc_conn; - } - [] STATSD_PROC.getcall(STATSD_expect:{?}) -> param(expects) sender vc_conn { - var boolean success := f_statsd_checker_expect(expects); - STATSD_PROC.reply(STATSD_expect:{expects} value success) to vc_conn; - } - } - } -} - - -/* Return false if the expectation doesn't match the metric, otherwise return true */ -private function f_compare_expect(StatsDMetric metric, StatsDExpect expect) return boolean { - if ((metric.name == expect.name) and (metric.mtype == expect.mtype) - and (metric.val >= expect.min) and (metric.val <= expect.max)) { - return true; - } else { - return false; - } -} - -private function f_statsd_checker_metric_expects(StatsDExpectPrivs exp_seen, StatsDMetric metric) -return StatsDExpectResult { - var StatsDExpectResult result := { - kind := e_NotFound, - idx := -1 - }; - - for (var integer i := 0; i < lengthof(exp_seen); i := i + 1) { - var StatsDExpectPriv exp := exp_seen[i]; - if (exp.expect.name != metric.name) { - continue; - } - if (not f_compare_expect(metric, exp.expect)) { - log("EXP mismatch: ", metric, " vs ", exp.expect); - result := { - kind := e_Mismatched, - idx := i - }; - break; - } else { - log("EXP match: ", metric, " vs ", exp.expect); - result := { - kind := e_Matched, - idx := i - }; - break; - } - } - return result; -} - -template StatsDExpectPriv t_statsd_expect_priv(template StatsDExpect expect) := { - expect := expect, - seen := 0 -} - -private function f_statsd_checker_expect(StatsDExpects expects) runs on StatsD_Checker_CT return boolean { - var default t; - var StatsDMessage msg; - var StatsDExpectResult res; - var StatsDExpectPrivs exp_seen := {}; - - for (var integer i := 0; i < lengthof(expects); i := i + 1) { - exp_seen := exp_seen & {valueof(t_statsd_expect_priv(expects[i]))}; - } - - /* Dismiss any messages we might have skipped from the last report */ - STATS.clear; - - f_vty_transceive(STATSVTY, "stats report"); - - var boolean seen_all := false; - T_statsd.start; - while (not seen_all) { - var StatsD_RecvFrom rf; - alt { - [] STATS.receive(tr_StatsD_RecvFrom(?, ?)) -> value rf { - msg := rf.msg; - } - [] T_statsd.timeout { - for (var integer i := 0; i < lengthof(exp_seen); i := i + 1) { - /* We're still missing some expects, keep looking */ - if (exp_seen[i].seen == 0) { - log("Timeout waiting for ", exp_seen[i].expect.name, " (min: ", exp_seen[i].expect.min, - ", max: ", exp_seen[i].expect.max, ")"); - } - } - setverdict(fail, "Timeout waiting for metrics"); - return false; - } - } - - for (var integer i := 0; i < lengthof(msg); i := i + 1) { - var StatsDMetric metric := msg[i]; - - res := f_statsd_checker_metric_expects(exp_seen, metric); - if (res.kind == e_NotFound) { - continue; - } - - if (res.kind == e_Mismatched) { - log("Metric: ", metric); - log("Expect: ", exp_seen[res.idx].expect); - setverdict(fail, "Metric failed expectation ", metric, " vs ", exp_seen[res.idx].expect); - return false; - } else if (res.kind == e_Matched) { - exp_seen[res.idx].seen := exp_seen[res.idx].seen + 1; - } - } - - /* Check if all expected metrics were received */ - seen_all := true; - for (var integer i := 0; i < lengthof(exp_seen); i := i + 1) { - /* We're still missing some expects, keep looking */ - if (exp_seen[i].seen == 0) { - seen_all := false; - break; - } - } - } - - T_statsd.stop; - return seen_all; -} - -function f_init_statsd(charstring id, inout StatsD_Checker_CT vc_STATSD, charstring local_addr, integer local_port) { - id := id & "-STATS"; - - vc_STATSD := StatsD_Checker_CT.create(id); - vc_STATSD.start(StatsD_Checker.main(local_addr, local_port)); -} - - -/* StatsD connhdlr */ -function f_statsd_reset() runs on StatsD_ConnHdlr { - if (not mp_enable_stats) { - return; - } - - STATSD_PROC.call(STATSD_reset:{}) { - [] STATSD_PROC.getreply(STATSD_reset:{}) {} - } -} - -function f_statsd_expect(StatsDExpects expects) runs on StatsD_ConnHdlr return boolean { - var boolean res; - - if (not mp_enable_stats) { - return true; - } - - STATSD_PROC.call(STATSD_expect:{expects}) { - [] STATSD_PROC.getreply(STATSD_expect:{expects}) -> value res; - } - return res; -} - -} diff --git a/library/StatsD_Checker.ttcnpp b/library/StatsD_Checker.ttcnpp new file mode 100644 index 0000000..ecadee3 --- /dev/null +++ b/library/StatsD_Checker.ttcnpp @@ -0,0 +1,444 @@ +module StatsD_Checker { + +/* Verifies that StatsD metrics in a test match the expected values + * Uses StatsD_CodecPort to receive the statsd messages from the DUT + * and a separate VTY connection to reset and trigger the stats. + * + * When using this you should configure your stats reporter to disable + * interval-based reports and always send all metrics: + * > stats interval 0 + * > stats reporter statsd + * > remote-ip a.b.c.d + * > remote-port 8125 + * > level subscriber + * > flush-period 1 + * > mtu 1024 + * > enable + * + * (C) 2020 by sysmocom s.f.m.c. GmbH info@sysmocom.de + * All rights reserved. + * + * Author: Daniel Willmann dwillmann@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 StatsD_Types all; +import from StatsD_CodecPort all; +import from StatsD_CodecPort_CtrlFunct all; + +import from General_Types all; +import from Osmocom_Types all; +#ifdef STATSD_HAVE_VTY +import from Osmocom_VTY_Functions all; +import from TELNETasp_PortType all; +#endif + +modulepar { + /* Whether to test stats values */ + boolean mp_enable_stats := true; +} + +type record StatsDMetricKey { + MetricName name, + MetricType mtype +}; +type set of StatsDMetricKey StatsDMetricKeys; + +template (value) StatsDMetricKey ts_StatsDMetricKey(template (value) MetricName name, template (value) MetricType mtype) := { + name := name, + mtype := mtype +} + +type record StatsDExpect { + MetricName name, + MetricType mtype, + MetricValue min, + MetricValue max +}; +type set of StatsDExpect StatsDExpects; + +type enumerated StatsDResultType { + e_Matched, + e_Mismatched, + e_NotFound +} + +type record StatsDExpectResult { + StatsDResultType kind, + integer idx +} + +type component StatsD_Checker_CT { +#ifdef STATSD_HAVE_VTY + port TELNETasp_PT STATSVTY; +#endif + port STATSD_PROC_PT STATSD_PROC; + port STATSD_CODEC_PT STATS; + timer T_statsd := 5.0; +} + +type component StatsD_ConnHdlr { + port STATSD_PROC_PT STATSD_PROC; +} + +signature STATSD_reset(); +signature STATSD_snapshot(in StatsDMetricKeys keys, in boolean since_last_snapshot) return StatsDMetrics; +signature STATSD_expect(in StatsDExpects expects, in boolean wait_converge, in boolean use_snapshot, in StatsDMetrics snapshot) return boolean; + +type port STATSD_PROC_PT procedure { + inout STATSD_reset, STATSD_snapshot, STATSD_expect; +} with {extension "internal"}; + +/* Expect templates and functions */ + + +/* StatsD checker component */ +function main(charstring statsd_host, integer statsd_port) runs on StatsD_Checker_CT { + var StatsD_ConnHdlr vc_conn; + var StatsDMetricKeys keys; + var boolean since_last_snapshot; + var StatsDExpects expects; + var boolean wait_converge; + var boolean use_snapshot; + var StatsDMetrics snapshot; + var Result res; + + while (not mp_enable_stats) { + log("StatsD checker disabled by modulepar"); + f_sleep(3600.0); + } + + map(self:STATS, system:STATS); + res := StatsD_CodecPort_CtrlFunct.f_IPL4_listen(STATS, statsd_host, statsd_port, { udp := {} }, {}); + if (not ispresent(res.connId)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + "Could not bind StatsD socket, check your configuration"); + } + +#ifdef STATSD_HAVE_VTY + /* Connect to VTY and reset stats */ + map(self:STATSVTY, system:STATSVTY); + f_vty_set_prompts(STATSVTY); + f_vty_transceive(STATSVTY, "enable"); + + /* Reset the stats system at start */ + f_vty_transceive(STATSVTY, "stats reset"); +#endif + + while (true) { + alt { + [] STATSD_PROC.getcall(STATSD_reset:{}) -> sender vc_conn { +#ifdef STATSD_HAVE_VTY + f_vty_transceive(STATSVTY, "stats reset"); + STATSD_PROC.reply(STATSD_reset:{}) to vc_conn; +#else + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + "STATSD_reset not supported, StatsD_Checker was built without VTY support"); +#endif + } + [] STATSD_PROC.getcall(STATSD_snapshot:{?, ?}) -> param(keys, since_last_snapshot) sender vc_conn { + snapshot := f_statsd_checker_snapshot(keys, since_last_snapshot); + STATSD_PROC.reply(STATSD_snapshot:{keys, since_last_snapshot} value snapshot) to vc_conn; + } + [] STATSD_PROC.getcall(STATSD_expect:{?, ?, ?, ?}) -> param(expects, wait_converge, use_snapshot, snapshot) sender vc_conn { + var boolean success := f_statsd_checker_expect(expects, wait_converge, use_snapshot, snapshot); + STATSD_PROC.reply(STATSD_expect:{expects, wait_converge, use_snapshot, snapshot} value success) to vc_conn; + } + } + } +} + +/* Updates "metrics" & "seen" with content from "it". Returns true if the metric becomes known (for first time) as a result. */ +private function f_statsd_metrics_update_value(inout StatsDMetrics metrics, inout Booleans seen, StatsDMetric it) return boolean +{ + for (var integer i := 0; i < lengthof(metrics); i := i + 1) { + log("Rx new metric ", it); + if (it.name != metrics[i].name or it.mtype != metrics[i].mtype) { + log("PESPIN: metric[",i,"] ", it, " doesn't match ", metrics[i]); + continue; + } + metrics[i] := it; + log("PESPIN: metric[",i,"] seen =", seen[i]); + if (seen[i]) { + return false; + } else { + seen[i] := true; + return true; + } + } + return false; +} + + +private function f_statsd_checker_snapshot(StatsDMetricKeys keys, boolean since_last_snapshot := true) runs on StatsD_Checker_CT return StatsDMetrics { + var default t; + var StatsDMessage msg; + var StatsDMetrics metrics := {}; + var Booleans seen := {}; + var integer seen_remain := 0; + + for (var integer i := 0; i < lengthof(keys); i := i + 1) { + metrics := metrics & {valueof(ts_StatsDMetric(keys[i].name, 0, keys[i].mtype))}; + seen := seen & {false}; + seen_remain := seen_remain + 1; + } + + if (not since_last_snapshot) { + STATS.clear; + } + + T_statsd.start; + while (seen_remain > 0) { + var StatsD_RecvFrom rf; + alt { + [] STATS.receive(tr_StatsD_RecvFrom(?, ?)) -> value rf { + msg := rf.msg; + } + [] T_statsd.timeout { + for (var integer i := 0; i < lengthof(metrics); i := i + 1) { + /* We're still missing some expects, keep looking */ + if (not seen[i]) { + log("Timeout waiting for ", metrics[i].name); + } + } + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Timeout waiting for metrics: ", keys, seen)); + } + } + + for (var integer i := 0; i < lengthof(msg); i := i + 1) { + var StatsDMetric metric := msg[i]; + if (f_statsd_metrics_update_value(metrics, seen, metric)) { + seen_remain := seen_remain - 1; + } + } + } + T_statsd.stop; + + return metrics; +} + +private function get_val_from_snapshot(inout integer val, StatsDMetric metric, StatsDMetrics snapshot) return boolean +{ + for (var integer i := 0; i < lengthof(snapshot); i := i + 1) { + if (metric.name != snapshot[i].name or metric.mtype != snapshot[i].mtype) { + continue; + } + val := snapshot[i].val; + return true; + } + return false; +} + +/* Return false if the expectation doesn't match the metric, otherwise return true */ +private function f_compare_expect(StatsDMetric metric, + StatsDExpect expect, + boolean use_snapshot := false, + StatsDMetrics snapshot := {}) return boolean { + var integer val := 0; + if ((metric.name != expect.name) or (metric.mtype != expect.mtype)) { + return false; + } + if (use_snapshot) { + var integer prev_val := 0; + if (not get_val_from_snapshot(prev_val, metric, snapshot)) { + Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, + log2str("Metric ", metric.name, " not found in snapshot ", snapshot)); + } + val := metric.val - prev_val; + } else { + val := metric.val; + } + + if ((val < expect.min) or (val > expect.max)) { + return false; + } + return true; +} + +private function f_statsd_checker_metric_expects(StatsDExpects expects, + StatsDMetric metric, + boolean use_snapshot := false, + StatsDMetrics snapshot := {}) +return StatsDExpectResult { + var StatsDExpectResult result := { + kind := e_NotFound, + idx := -1 + }; + + for (var integer i := 0; i < lengthof(expects); i := i + 1) { + var StatsDExpect exp := expects[i]; + if (exp.name != metric.name) { + continue; + } + if (not f_compare_expect(metric, exp, use_snapshot, snapshot)) { + log("EXP mismatch: ", metric, " vs exp ", exp, " | use_snapshot=", use_snapshot, ", snapshot=", snapshot); + result := { + kind := e_Mismatched, + idx := i + }; + break; + } else { + log("EXP match: ", metric, " vs exp ", exp); + result := { + kind := e_Matched, + idx := i + }; + break; + } + } + return result; +} + +private function f_statsd_checker_expect(StatsDExpects expects, + boolean wait_converge := false, + boolean use_snapshot := false, + StatsDMetrics snapshot := {}) runs on StatsD_Checker_CT return boolean { + var default t; + var StatsDMessage msg; + var StatsDExpectResult res; + var Booleans matched := {}; + var integer matched_remain := 0; + + for (var integer i := 0; i < lengthof(expects); i := i + 1) { + matched := matched & {false}; + matched_remain := matched_remain + 1; + } + + /* Dismiss any messages we might have skipped from the last report */ + STATS.clear; + + if (not use_snapshot) { +#ifdef STATSD_HAVE_VTY + f_vty_transceive(STATSVTY, "stats report"); +#else + /* Assume caller knows previous state, eg. gauges may have been 0 due to IUT being reset */ +#endif + } + + T_statsd.start; + while (matched_remain > 0) { + var StatsD_RecvFrom rf; + alt { + [] STATS.receive(tr_StatsD_RecvFrom(?, ?)) -> value rf { + msg := rf.msg; + } + [] T_statsd.timeout { + for (var integer i := 0; i < lengthof(expects); i := i + 1) { + /* We're still missing some expects, keep looking */ + if (not matched[i]) { + log("Timeout waiting for ", expects[i].name, + " (min: ", expects[i].min, ", max: ", expects[i].max, ")"); + } + } + setverdict(fail, "Timeout waiting for metrics"); + return false; + } + } + + for (var integer i := 0; i < lengthof(msg); i := i + 1) { + var StatsDMetric metric := msg[i]; + res := f_statsd_checker_metric_expects(expects, metric, use_snapshot, snapshot); + if (res.kind == e_NotFound) { + continue; + } + if (res.kind == e_Mismatched) { + if (wait_converge and not matched[res.idx]) { + log("Waiting convergence: Ignoring metric mismatch metric=", metric, " expect=", expects[res.idx]) + continue; + } + log("Metric: ", metric); + log("Expect: ", expects[res.idx]); + setverdict(fail, "Metric failed expectation ", metric, " vs ", expects[res.idx]); + return false; + } + if (res.kind == e_Matched) { + if (not matched[res.idx]) { + matched[res.idx] := true; + matched_remain := matched_remain - 1; + } + continue; + } + } + } + T_statsd.stop; + return true; +} + +function f_init_statsd(charstring id, inout StatsD_Checker_CT vc_STATSD, charstring local_addr, integer local_port) { + id := id & "-STATS"; + + vc_STATSD := StatsD_Checker_CT.create(id); + vc_STATSD.start(StatsD_Checker.main(local_addr, local_port)); +} + + +/* StatsD connhdlr */ +function f_statsd_reset() runs on StatsD_ConnHdlr { + if (not mp_enable_stats) { + return; + } + + STATSD_PROC.call(STATSD_reset:{}) { + [] STATSD_PROC.getreply(STATSD_reset:{}) {} + } +} + +/* Useful to automatically generate param for f_statsd_snapshot() from StatsDExpects used in f_statsd_expect_from_snapshot() */ +function f_statsd_keys_from_expect(StatsDExpects expects) return StatsDMetricKeys +{ + var StatsDMetricKeys keys := {} + for (var integer i := 0; i < lengthof(expects); i := i + 1) { + keys := keys & {valueof(ts_StatsDMetricKey(expects[i].name, expects[i].mtype))} + } + return keys; +} + +/* Retrieve current values obtained at statsd server. +* If since_last_snapshot is false, then clear the received packets in port. */ +function f_statsd_snapshot(StatsDMetricKeys keys, boolean since_last_snapshot := true) runs on StatsD_ConnHdlr return StatsDMetrics { + var StatsDMetrics snapshot; + if (not mp_enable_stats) { + return {}; + } + + STATSD_PROC.call(STATSD_snapshot:{keys, since_last_snapshot}) { + [] STATSD_PROC.getreply(STATSD_snapshot:{keys, since_last_snapshot}) -> value snapshot; + } + return snapshot; +} + +function f_statsd_expect(StatsDExpects expects, boolean wait_converge := false) runs on StatsD_ConnHdlr return boolean { + var boolean res; + + if (not mp_enable_stats) { + return true; + } + + STATSD_PROC.call(STATSD_expect:{expects, wait_converge, false, {}}) { + [] STATSD_PROC.getreply(STATSD_expect:{expects, wait_converge, false, {}}) -> value res; + } + return res; +} + +function f_statsd_expect_from_snapshot(StatsDExpects expects, boolean wait_converge := false, + StatsDMetrics snapshot := {}) runs on StatsD_ConnHdlr return boolean { + var boolean res; + + if (not mp_enable_stats) { + return true; + } + + STATSD_PROC.call(STATSD_expect:{expects, wait_converge, true, snapshot}) { + [] STATSD_PROC.getreply(STATSD_expect:{expects, wait_converge, true, snapshot}) -> value res; + } + return res; +} + +} diff --git a/library/StatsD_Types.ttcn b/library/StatsD_Types.ttcn index 8938571..e60b8e8 100644 --- a/library/StatsD_Types.ttcn +++ b/library/StatsD_Types.ttcn @@ -33,6 +33,7 @@ MetricType mtype, MetricSampleRate srate optional }; +type set of StatsDMetric StatsDMetrics;
type record of StatsDMetric StatsDMessage with { variant "SEPARATOR('\n')"; @@ -48,7 +49,8 @@ [0] := metric }
-template (present) StatsDMetric tr_StatsDMetric(template (present) MetricName name, template (present) MetricValue val := ?, +template (present) StatsDMetric tr_StatsDMetric(template (present) MetricName name, + template (present) MetricValue val := ?, template (present) MetricType mtype) := { name := name, val := val, @@ -56,10 +58,30 @@ srate := * }
-template (present) StatsDMetric tr_StatsDMetricCounter(template (present) MetricName name, template (present) MetricValue val := ?) := +template (present) StatsDMetric tr_StatsDMetricCounter(template (present) MetricName name, + template (present) MetricValue val := ?) := tr_StatsDMetric(name, val, "c");
-template (present) StatsDMetric tr_StatsDMetricGauge(template (present) MetricName name, template (present) MetricValue val := ?) := +template (present) StatsDMetric tr_StatsDMetricGauge(template (present) MetricName name, + template (present) MetricValue val := ?) := tr_StatsDMetric(name, val, "g");
+template (value) StatsDMetric ts_StatsDMetric(template (value) MetricName name, + template (value) MetricValue val := 0, + template (value) MetricType mtype := "c", + template (omit) MetricSampleRate srate := omit) := { + name := name, + val := val, + mtype := mtype, + srate := srate +} + +template (value) StatsDMetric ts_StatsDMetricCounter(template (value) MetricName name, + template (value) MetricValue val := 0) := + ts_StatsDMetric(name, val, "c"); + +template (value) StatsDMetric ts_StatsDMetricGauge(template (value) MetricName name, + template (value) MetricValue val := 0) := + ts_StatsDMetric(name, val, "g"); + } with { encode "TEXT" } diff --git a/mgw/gen_links.sh b/mgw/gen_links.sh index 2fd2b0d..7662b55 100755 --- a/mgw/gen_links.sh +++ b/mgw/gen_links.sh @@ -46,7 +46,7 @@ FILES+="Native_Functions.ttcn Native_FunctionDefs.cc IPCP_Types.ttcn " FILES+="Osmocom_VTY_Functions.ttcn " FILES+="RTP_CodecPort_CtrlFunct.ttcn RTP_CodecPort_CtrlFunctDef.cc " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="IPA_Types.ttcn IPA_Emulation.ttcnpp IPA_CodecPort.ttcn IPA_CodecPort_CtrlFunct.ttcn IPA_CodecPort_CtrlFunctDef.cc " FILES+="Osmocom_CTRL_Types.ttcn Osmocom_CTRL_Functions.ttcn Osmocom_CTRL_Adapter.ttcn "
diff --git a/mgw/regen_makefile.sh b/mgw/regen_makefile.sh index 921956c..2b78cf0 100755 --- a/mgw/regen_makefile.sh +++ b/mgw/regen_makefile.sh @@ -25,6 +25,7 @@
export CPPFLAGS_TTCN3=" -DIPA_EMULATION_CTRL + -DSTATSD_HAVE_VTY "
../regen-makefile.sh -e $NAME $FILES diff --git a/ns/gen_links.sh b/ns/gen_links.sh index 8d78ddf..fde0a67 100755 --- a/ns/gen_links.sh +++ b/ns/gen_links.sh @@ -51,7 +51,7 @@
DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc GSM_Types.ttcn Osmocom_Types.ttcn " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="RAW_NS.ttcnpp NS_Provider_IPL4.ttcn NS_Provider_FR.ttcn NS_Emulation.ttcnpp " FILES+="BSSGP_Emulation.ttcnpp Osmocom_Gb_Types.ttcn " FILES+="LLC_Templates.ttcn " diff --git a/ns/regen_makefile.sh b/ns/regen_makefile.sh index ad86d71..57dde2d 100755 --- a/ns/regen_makefile.sh +++ b/ns/regen_makefile.sh @@ -22,6 +22,7 @@ export CPPFLAGS_TTCN3=" -DBSSGP_EM_L3 -DNS_EMULATION_FR + -DSTATSD_HAVE_VTY "
../regen-makefile.sh -e $NAME $FILES diff --git a/pcu/gen_links.sh b/pcu/gen_links.sh index 556f02a..146f538 100755 --- a/pcu/gen_links.sh +++ b/pcu/gen_links.sh @@ -50,7 +50,7 @@
DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc GSM_Types.ttcn GSM_RR_Types.ttcn GSM_RestOctets.ttcn Osmocom_Types.ttcn RLCMAC_Templates.ttcn RLCMAC_Types.ttcn RLCMAC_CSN1_Templates.ttcn RLCMAC_CSN1_Types.ttcn RLCMAC_EncDec.cc " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="RAW_NS.ttcnpp NS_Provider_IPL4.ttcn NS_Emulation.ttcnpp " FILES+="BSSGP_Emulation.ttcnpp Osmocom_Gb_Types.ttcn " FILES+="LLC_Templates.ttcn L3_Templates.ttcn L3_Common.ttcn " diff --git a/pcu/regen_makefile.sh b/pcu/regen_makefile.sh index 006c6ba..9cb1554 100755 --- a/pcu/regen_makefile.sh +++ b/pcu/regen_makefile.sh @@ -22,6 +22,7 @@ export CPPFLAGS_TTCN3=" -DBSSGP_EM_L3 -DIPA_EMULATION_CTRL + -DSTATSD_HAVE_VTY "
../regen-makefile.sh -e $NAME $FILES diff --git a/upf/gen_links.sh b/upf/gen_links.sh index 3865110..6c710ee 100755 --- a/upf/gen_links.sh +++ b/upf/gen_links.sh @@ -29,7 +29,7 @@
DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn Osmocom_Types.ttcn Osmocom_VTY_Functions.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPA_Types.ttcn IPA_CodecPort.ttcn IPA_CodecPort_CtrlFunct.ttcn IPA_CodecPort_CtrlFunctDef.cc IPA_Emulation.ttcnpp Osmocom_CTRL_Types.ttcn Osmocom_CTRL_Functions.ttcn Osmocom_CTRL_Adapter.ttcn " -FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcn " +FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp " FILES+="PFCP_CodecPort.ttcn PFCP_CodecPort_CtrlFunct.ttcn PFCP_CodecPort_CtrlFunctDef.cc PFCP_Emulation.ttcn PFCP_Templates.ttcn" gen_links $DIR $FILES
diff --git a/upf/regen_makefile.sh b/upf/regen_makefile.sh index 953e10f..1368db1 100755 --- a/upf/regen_makefile.sh +++ b/upf/regen_makefile.sh @@ -19,6 +19,7 @@
export CPPFLAGS_TTCN3=" -DIPA_EMULATION_CTRL + -DSTATSD_HAVE_VTY "
../regen-makefile.sh -e $NAME $FILES