This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
daniel gerrit-no-reply at lists.osmocom.orgdaniel has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/19581 ) Change subject: Add parsing and checking of StatsD metrics ...................................................................... Add parsing and checking of StatsD metrics Change-Id: Icd1317b5f192d98e6cdc6635788d450501992bf1 Related: SYS#4877 --- A library/StatsD_Checker.ttcn A library/StatsD_CodecPort.ttcn A library/StatsD_CodecPort_CtrlFunct.ttcn A library/StatsD_CodecPort_CtrlFunctdef.cc A library/StatsD_Types.ttcn 5 files changed, 488 insertions(+), 0 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/81/19581/1 diff --git a/library/StatsD_Checker.ttcn b/library/StatsD_Checker.ttcn new file mode 100644 index 0000000..9328d21 --- /dev/null +++ b/library/StatsD_Checker.ttcn @@ -0,0 +1,264 @@ +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 at sysmocom.de> + * All rights reserved. + * + * Author: Daniel Willmann <dwillmann at 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 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 := false; +} + +type record StatsDExpect { + MetricName name, + MetricType mtype, + MetricValue min, + MetricValue max +}; + +type record of StatsDExpect StatsDExpects; + +type record StatsDExpectPriv { + StatsDExpect expect, + integer seen +} + +type record 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; + + while (not mp_enable_stats) { + log("StatsD checker disabled by modulepar"); + f_sleep(3600.0); + } + + map(self:STATS, system:STATS); + StatsD_CodecPort_CtrlFunct.f_IPL4_listen(STATS, statsd_host, statsd_port, { udp := {} }, {}); + + /* 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, exp.expect); + result := { + kind := e_Mismatched, + idx := i + }; + break; + } else { + log("EXP match: ", metric, 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; +} + +/* 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_CodecPort.ttcn b/library/StatsD_CodecPort.ttcn new file mode 100644 index 0000000..e7396b5 --- /dev/null +++ b/library/StatsD_CodecPort.ttcn @@ -0,0 +1,57 @@ +module StatsD_CodecPort { + +import from StatsD_Types all; +import from IPL4asp_PortType all; +import from IPL4asp_Types all; + +type record StatsD_RecvFrom { + ConnectionId connId, + HostName remName, + PortNumber remPort, + HostName locName, + PortNumber locPort, + StatsDMessage msg +} + +template StatsD_RecvFrom tr_StatsD_RecvFrom(template ConnectionId cid, template StatsDMessage msg) := { + connId := cid, + remName := ?, + remPort := ?, + locName := ?, + locPort := ?, + msg := msg +} + +type record StatsD_Send { + ConnectionId connId, + StatsDMessage msg +} + +private function IPL4_to_StatsD_RecvFrom(in ASP_RecvFrom pin, out StatsD_RecvFrom pout) { + pout.connId := pin.connId; + pout.remName := pin.remName; + pout.remPort := pin.remPort; + pout.locName := pin.locName; + pout.locPort := pin.locPort; + pout.msg := dec_StatsDMessage(oct2char(pin.msg)); +} with { extension "prototype(fast)" }; + +private function StatsD_to_IPL4_Send(in StatsD_Send pin, out ASP_Send pout) { + pout.connId := pin.connId; + pout.proto := { udp := {} }; + pout.msg := char2oct(enc_StatsDMessage(pin.msg)); +} with { extension "prototype(fast)" }; + +type port STATSD_CODEC_PT message { + out StatsD_Send; + in StatsD_RecvFrom, + ASP_ConnId_ReadyToRelease, + ASP_Event; +} with { extension "user IPL4asp_PT + out(StatsD_Send -> ASP_Send: function(StatsD_to_IPL4_Send)) + in(ASP_RecvFrom -> StatsD_RecvFrom: function(IPL4_to_StatsD_RecvFrom); + ASP_ConnId_ReadyToRelease -> ASP_ConnId_ReadyToRelease: simple; + ASP_Event -> ASP_Event: simple)" +} + +} diff --git a/library/StatsD_CodecPort_CtrlFunct.ttcn b/library/StatsD_CodecPort_CtrlFunct.ttcn new file mode 100644 index 0000000..b2927c3 --- /dev/null +++ b/library/StatsD_CodecPort_CtrlFunct.ttcn @@ -0,0 +1,43 @@ +module StatsD_CodecPort_CtrlFunct { + +import from StatsD_CodecPort all; +import from IPL4asp_Types all; + +external function f_IPL4_listen( + inout STATSD_CODEC_PT portRef, + in HostName locName, + in PortNumber locPort, + in ProtoTuple proto, + in OptionList options := {} +) return Result; + +external function f_IPL4_connect( + inout STATSD_CODEC_PT portRef, + in HostName remName, + in PortNumber remPort, + in HostName locName, + in PortNumber locPort, + in ConnectionId connId, + in ProtoTuple proto, + in OptionList options := {} +) return Result; + +external function f_IPL4_close( + inout STATSD_CODEC_PT portRef, + in ConnectionId id, + in ProtoTuple proto := { unspecified := {} } +) return Result; + +external function f_IPL4_setUserData( + inout STATSD_CODEC_PT portRef, + in ConnectionId id, + in UserData userData +) return Result; + +external function f_IPL4_getUserData( + inout STATSD_CODEC_PT portRef, + in ConnectionId id, + out UserData userData +) return Result; + +} diff --git a/library/StatsD_CodecPort_CtrlFunctdef.cc b/library/StatsD_CodecPort_CtrlFunctdef.cc new file mode 100644 index 0000000..1b78a7e --- /dev/null +++ b/library/StatsD_CodecPort_CtrlFunctdef.cc @@ -0,0 +1,55 @@ +#include "IPL4asp_PortType.hh" +#include "IPL4asp_PT.hh" +#include "StatsD_CodecPort.hh" + +namespace StatsD__CodecPort__CtrlFunct { + + IPL4asp__Types::Result f__IPL4__listen( + StatsD__CodecPort::STATSD__CODEC__PT& portRef, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) + { + return f__IPL4__PROVIDER__listen(portRef, locName, locPort, proto, options); + } + + IPL4asp__Types::Result f__IPL4__connect( + StatsD__CodecPort::STATSD__CODEC__PT& portRef, + const IPL4asp__Types::HostName& remName, + const IPL4asp__Types::PortNumber& remPort, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) + { + return f__IPL4__PROVIDER__connect(portRef, remName, remPort, + locName, locPort, connId, proto, options); + } + + IPL4asp__Types::Result f__IPL4__close( + StatsD__CodecPort::STATSD__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto) + { + return f__IPL4__PROVIDER__close(portRef, connId, proto); + } + + IPL4asp__Types::Result f__IPL4__setUserData( + StatsD__CodecPort::STATSD__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::UserData& userData) + { + return f__IPL4__PROVIDER__setUserData(portRef, connId, userData); + } + + IPL4asp__Types::Result f__IPL4__getUserData( + StatsD__CodecPort::STATSD__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + IPL4asp__Types::UserData& userData) + { + return f__IPL4__PROVIDER__getUserData(portRef, connId, userData); + } + +} diff --git a/library/StatsD_Types.ttcn b/library/StatsD_Types.ttcn new file mode 100644 index 0000000..71023c4 --- /dev/null +++ b/library/StatsD_Types.ttcn @@ -0,0 +1,69 @@ +module StatsD_Types { + +/* Definition of abstract types for the StatsD protocol. USes the TITAN "TEXT" + * codec to auto-generate encoder/decoder functions + * + * (C) 2020 by sysmocom s.f.m.c. GmbH <info at sysmocom.de> + * All rights reserved. + * + * Author: Daniel Willmann <dwillmann at 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 + */ + +type charstring MetricName with { + variant "END(':')"; +}; + +type integer MetricValue with { + variant "END('|', '\|#(1)')"; +}; + +type charstring MetricType (pattern "(g|c|ms|h|m)"); + +type charstring MetricSampleRate (pattern "\d.\d+") with { + variant "BEGIN('|@')" +}; + +type record StatsDMetric { + MetricName name, + MetricValue val, + MetricType mtype, + MetricSampleRate srate optional +}; + +type record of StatsDMetric StatsDMessage with { + variant "SEPARATOR('\n')"; +}; + +external function enc_StatsDMessage(in StatsDMessage id) return charstring +with { extension "prototype(convert) encode(TEXT)"}; + +external function dec_StatsDMessage(in charstring id) return StatsDMessage +with { extension "prototype(convert) decode(TEXT)"}; + +template StatsDMessage tr_StatsDMsg1(template StatsDMetric metric) := { + [0] := metric +} + +template StatsDMetric tr_StatsDMetric(template MetricName name, template MetricValue val := ?, template MetricType mtype) := { + name := name, + val := val, + mtype := mtype +} + +template StatsDMetric tr_StatsDMetricCounter(template MetricName name, template MetricValue val := ?) := { + name := name, + val := val, + mtype := "c" +} + +template StatsDMetric tr_StatsDMetricGauge(template MetricName name, template MetricValue val := ?) := { + name := name, + val := val, + mtype := "g" +} + +} with { encode "TEXT" } -- To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/19581 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-ttcn3-hacks Gerrit-Branch: master Gerrit-Change-Id: Icd1317b5f192d98e6cdc6635788d450501992bf1 Gerrit-Change-Number: 19581 Gerrit-PatchSet: 1 Gerrit-Owner: daniel <dwillmann at sysmocom.de> Gerrit-MessageType: newchange -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200812/433a8c6a/attachment.htm>