From 9f2636716abf0d6cc188fed380ba77a621a370c1 Mon Sep 17 00:00:00 2001 From: Kienan Stewart Date: Thu, 28 Sep 2023 16:54:42 -0400 Subject: [PATCH] tests: Replace babelstats.pl with bt2 plugins MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Observed Issue ============== `tests/regression/tools/filtering/test_valid_filters` is a long running test, especially when running as root and exercising the tests across the kernel domain. I observed that a sizable amount of time was being spent in the analysis of the results using `babelstats.pl`. Solution ======== Instead of using a script to parse the pretty output of babeltrace2, I decided to write two C++ plugins to replicate the behaviour of the `babelstats.pl` script. I measured the time using `sudo -E time ./path/to/test` | Test | Time with `babelstats.pl` | Time with bt2 plugins | | test_tracefile_count | 13.04s | 11.73s | | test_exclusion | 22.75s | 22.07s | | test_valid_filter | 301.04s | 144.41s | The switch to using babeltrace2 plugins reduces the runtime of the `test_valid_filter` test (when running with kernel tests) by half. The runtime changes to the other tests that were modified are not significant. Known drawbacks =============== The field_stats plugin behaviour differs from `babelstats.pl` with regards to enumeration fields ("container" in `babelstats.pl`). However, no tests depend on that behaviour to pass. The field_stats sink plugin doesn't perform a lot of run-time error-checking of functions it invokes, and doesn't fully clean up all the references it allocates though the babeltrace2 API. As the intended usage is for short lived invocations with relatively small traces, the principal drawback of this approach is that errors in the plugin may be harder to debug. Building tests of lttng-tools will now depend on having the babeltrace2 development headers and libraries available. Change-Id: Ie8ebdd255b6901a7d0d7c4cd584a02096cccd4fb Signed-off-by: Kienan Stewart Signed-off-by: Jérémie Galarneau --- CONTRIBUTING.md | 4 +- README.adoc | 4 +- configure.ac | 10 + .../regression/tools/exclusion/test_exclusion | 7 +- .../tools/filtering/test_valid_filter | 9 +- .../tracefile-limits/test_tracefile_count | 4 +- tests/utils/Makefile.am | 6 +- tests/utils/babelstats.pl | 152 ------ tests/utils/bt2_plugins/Makefile.am | 28 ++ tests/utils/bt2_plugins/README.md | 23 + .../utils/bt2_plugins/event_name/Makefile.am | 13 + .../bt2_plugins/event_name/event_name.cpp | 220 +++++++++ .../bt2_plugins/event_name/event_name.hpp | 34 ++ .../utils/bt2_plugins/field_stats/Makefile.am | 13 + .../bt2_plugins/field_stats/field_stats.cpp | 452 ++++++++++++++++++ .../bt2_plugins/field_stats/field_stats.hpp | 27 ++ tests/utils/bt2_plugins/fmt.hpp | 131 +++++ tests/utils/bt2_plugins/lttngtest-plugin.cpp | 39 ++ 18 files changed, 1005 insertions(+), 171 deletions(-) delete mode 100755 tests/utils/babelstats.pl create mode 100644 tests/utils/bt2_plugins/Makefile.am create mode 100644 tests/utils/bt2_plugins/README.md create mode 100644 tests/utils/bt2_plugins/event_name/Makefile.am create mode 100644 tests/utils/bt2_plugins/event_name/event_name.cpp create mode 100644 tests/utils/bt2_plugins/event_name/event_name.hpp create mode 100644 tests/utils/bt2_plugins/field_stats/Makefile.am create mode 100644 tests/utils/bt2_plugins/field_stats/field_stats.cpp create mode 100644 tests/utils/bt2_plugins/field_stats/field_stats.hpp create mode 100644 tests/utils/bt2_plugins/fmt.hpp create mode 100644 tests/utils/bt2_plugins/lttngtest-plugin.cpp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8b15bea0f..fbb48cd5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,8 @@ control. The upstream Git repository URL is: See CodingStyle for guidelines style and design guidelines. -Although the LTTng-tools code base is primarily written in C, it does -contain shell, Perl, and Python code as well. There is no official coding +Although the LTTng-tools code base is primarily written in C++, it does +contain C, shell, and Python code as well. There is no official coding standard for these languages. However, using a style consistent with the rest of the code written in that language is strongly encouraged. diff --git a/README.adoc b/README.adoc index d66b4ff38..6ec60856a 100644 --- a/README.adoc +++ b/README.adoc @@ -72,15 +72,13 @@ The following dependencies are optional: of the https://lttng.org/man/1/lttng-view/[`lttng view`] command, `make{nbsp}check` and tests. + -Debian/Ubuntu package: `babeltrace2` +Debian/Ubuntu package: `babeltrace2 libbabeltrace2-dev` * **https://lttng.org/[LTTng{nbh}UST]** (same minor version as {lt}): LTTng user space tracing (applications and libraries). + Debian/Ubuntu package: `liblttng{nbh}ust{nbh}dev` -* **Perl**: `make{nbsp}check` and tests. - * **https://www.python.org/[Python]{nbsp}≥{nbsp}3.4**: `make{nbsp}check` and tests. + diff --git a/configure.ac b/configure.ac index 836ff15b8..b479c7350 100644 --- a/configure.ac +++ b/configure.ac @@ -1139,6 +1139,13 @@ test "x$enable_bin_lttng_sessiond" != "xno"], ) AM_CONDITIONAL([BUILD_TESTS], [test x$build_tests = xyes]) + +AS_IF([test "x$build_tests" = "xyes"], + [ + PKG_CHECK_MODULES([babeltrace2], [babeltrace2]) + ] +) + AM_CONDITIONAL([BUILD_EXTRAS], [test x$enable_extras != xno]) # Export libraries build conditions. @@ -1290,6 +1297,9 @@ AC_CONFIG_FILES([ tests/unit/ini_config/Makefile tests/perf/Makefile tests/utils/Makefile + tests/utils/bt2_plugins/Makefile + tests/utils/bt2_plugins/event_name/Makefile + tests/utils/bt2_plugins/field_stats/Makefile tests/utils/lttngtest/Makefile tests/utils/tap/Makefile tests/utils/testapp/Makefile diff --git a/tests/regression/tools/exclusion/test_exclusion b/tests/regression/tools/exclusion/test_exclusion index 47deede06..6b3a14fd4 100755 --- a/tests/regression/tools/exclusion/test_exclusion +++ b/tests/regression/tools/exclusion/test_exclusion @@ -9,7 +9,8 @@ TEST_DESC="Event exclusion" CURDIR=$(dirname $0)/ TESTDIR=$CURDIR/../../.. -STATS_BIN="$TESTDIR/utils/babelstats.pl" +BT2_PLUGINS_DIR="${TESTDIR}/utils/bt2_plugins" + SESSION_NAME="test-exclusion" TESTAPP_PATH="$TESTDIR/utils/testapp" TESTAPP_NAME="gen-ust-nevents" @@ -86,7 +87,7 @@ function test_exclusion # Destroy session destroy_lttng_session_ok $SESSION_NAME - stats=`"$BABELTRACE_BIN" $trace_path | $STATS_BIN --tracepoint "$event_name_expected_to_be_missing" | grep -v index 2> /dev/null` + stats=$("$BABELTRACE_BIN" --plugin-path "${BT2_PLUGINS_DIR}" "${trace_path}" -c filter.lttngtest.event_name -p "names=[\"${event_name_expected_to_be_missing}\"]" -c sink.lttngtest.field_stats | grep -v index 2> /dev/null) if [ ! -z "$stats" ]; then fail "Excluded event \"$event_name_expected_to_be_missing\" was found in trace!" else @@ -139,7 +140,7 @@ function test_exclusion_tracing_started # Destroy session destroy_lttng_session_ok $SESSION_NAME - stats=$("$BABELTRACE_BIN" $trace_path | $STATS_BIN --tracepoint "$event_name_expected_to_be_missing" | grep -v index 2> /dev/null) + stats=$("$BABELTRACE_BIN" --plugin-path "${BT2_PLUGINS_DIR}" "${trace_path}" -c filter.lttngtest.event_name -p "names=[\"${event_name_expected_to_be_missing}\"]" -c sink.lttngtest.field_stats | grep -v index 2> /dev/null) if [ ! -z "$stats" ]; then fail "Excluded event \"$event_name_expected_to_be_missing\" was found in trace!" else diff --git a/tests/regression/tools/filtering/test_valid_filter b/tests/regression/tools/filtering/test_valid_filter index 1d7b0db47..79a9d85f2 100755 --- a/tests/regression/tools/filtering/test_valid_filter +++ b/tests/regression/tools/filtering/test_valid_filter @@ -9,7 +9,8 @@ TEST_DESC="Filtering - Valid filters" CURDIR=$(dirname $0)/ TESTDIR=$CURDIR/../../.. -STATS_BIN="$TESTDIR/utils/babelstats.pl" +BT2_PLUGINS_DIR="${TESTDIR}/utils/bt2_plugins" + SESSION_NAME="valid_filter" NR_ITER=100 NUM_GLOBAL_TESTS=2 @@ -112,14 +113,10 @@ function test_valid_filter # Destroy session destroy_lttng_session_ok $SESSION_NAME --no-wait - stats=`"$BABELTRACE_BIN" $trace_path | $STATS_BIN --tracepoint $event_name` - - rm -rf $trace_path + stats=$("${BABELTRACE_BIN}" --plugin-path "${BT2_PLUGINS_DIR}" "${trace_path}" -c filter.lttngtest.event_name -p "names=[\"${event_name}\"]" -c sink.lttngtest.field_stats) $validator "$stats" - ok $? "Validate trace filter output" - rm -rf $trace_path } diff --git a/tests/regression/tools/tracefile-limits/test_tracefile_count b/tests/regression/tools/tracefile-limits/test_tracefile_count index df32b4039..9253d3ec2 100755 --- a/tests/regression/tools/tracefile-limits/test_tracefile_count +++ b/tests/regression/tools/tracefile-limits/test_tracefile_count @@ -9,12 +9,12 @@ TEST_DESC="Tracefile count limits" CURDIR=$(dirname "$0")/ TESTDIR=$CURDIR/../../.. +BT2_PLUGINS_DIR="${TESTDIR}/utils/bt2_plugins" TESTAPP_PATH="$TESTDIR/utils/testapp" TESTAPP_NAME="gen-ust-events" TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME" -STATS_BIN="$TESTDIR/utils/babelstats.pl" NUM_TESTS=74 PAGE_SIZE=$(getconf PAGE_SIZE) @@ -140,7 +140,7 @@ function test_tracefile_count_limit () [ "$(get_stream_file_count "$trace_path" "$stream_pattern")" -eq "$count_limit" ] ok $? "Stream meets the trace file limit of $count_limit" - stats=`"$BABELTRACE_BIN" $trace_path | $STATS_BIN --tracepoint $event_name` + stats=$("$BABELTRACE_BIN" --plugin-path "${BT2_PLUGINS_DIR}" convert $trace_path -c filter.lttngtest.event_name -p "names=[\"${event_name}\"]" -c sink.lttngtest.field_stats) validate_min_max "$stats" "intfield" "[0-9]+" "$expected_max" ok $? "Trace validation - intfield" diff --git a/tests/utils/Makefile.am b/tests/utils/Makefile.am index 941476e31..455e749a2 100644 --- a/tests/utils/Makefile.am +++ b/tests/utils/Makefile.am @@ -1,10 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-only -SUBDIRS = . tap testapp xml-utils lttngtest +SUBDIRS = . tap testapp xml-utils lttngtest bt2_plugins -EXTRA_DIST = utils.sh test_utils.py babelstats.pl warn_processes.sh \ +EXTRA_DIST = utils.sh test_utils.py warn_processes.sh \ parse-callstack.py -dist_noinst_SCRIPTS = utils.sh test_utils.py babelstats.pl tap-driver.sh +dist_noinst_SCRIPTS = utils.sh test_utils.py tap-driver.sh noinst_LTLIBRARIES = libtestutils.la libtestutils_la_SOURCES = \ diff --git a/tests/utils/babelstats.pl b/tests/utils/babelstats.pl deleted file mode 100755 index 13c710bc6..000000000 --- a/tests/utils/babelstats.pl +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env perl - -# Copyright (C) 2012 Christian Babeux -# -# SPDX-License-Identifier: GPL-2.0-only -# - -use strict; -use warnings; - -use Getopt::Long; - -my $opt_tracepoint; - -GetOptions('tracepoint=s' => \$opt_tracepoint) - or die("Invalid command-line option\n"); - -defined($opt_tracepoint) - or die("Missing tracepoint, use --tracepoint "); - -# Parse an array string. -# The format is as follow: [ [index] = value, ... ] -sub parse_array -{ - my ($arr_str) = @_; - my @array = (); - - # Strip leading and ending brackets, remove whitespace - $arr_str =~ s/^\[//; - $arr_str =~ s/\]$//; - $arr_str =~ s/\s//g; - - my @entries = split(',', $arr_str); - - foreach my $entry (@entries) { - if ($entry =~ /^\[(\d+)\]=(\d+)$/) { - my $index = $1; - my $value = $2; - splice @array, $index, 0, $value; - } - } - - return \@array; -} - -# Parse fields values. -# Format can either be a name = array or a name = value pair. -sub parse_fields -{ - my ($fields_str) = @_; - my %fields_hash; - - my $field_name = '[\w\d_]+'; - my $field_value = '[\w\d_\\\*"]+'; - my $array = '\[(?:\s\[\d+\]\s=\s\d+,)*\s\[\d+\]\s=\s\d+\s\]'; - - # Split the various fields - my @fields = ($fields_str =~ /$field_name\s=\s(?:$array|$field_value)/g); - - foreach my $field (@fields) { - if ($field =~ /($field_name)\s=\s($array)/) { - my $name = $1; - my $value = parse_array($2); - $fields_hash{$name} = $value; - } - - if ($field =~ /($field_name)\s=\s($field_value)/) { - my $name = $1; - my $value = $2; - $fields_hash{$name} = $value; - } - } - - return \%fields_hash; -} - -# Using an event array, merge all the fields -# of a particular tracepoint. -sub merge_fields -{ - my ($events_ref) = @_; - my %merged; - - foreach my $event (@{$events_ref}) { - my $tp_event = $event->{'tp_event'}; - my $tracepoint = "${tp_event}"; - - foreach my $key (keys %{$event->{'fields'}}) { - my $val = $event->{'fields'}->{$key}; - - # TODO: Merge of array is not implemented. - next if (ref($val) eq 'ARRAY'); - $merged{$tracepoint}{$key}{$val} = undef; - } - } - - return \%merged; -} - -# Print the minimum and maximum of each fields -# for a particular tracepoint. -sub print_fields_stats -{ - my ($merged_ref, $tracepoint) = @_; - - return unless ($tracepoint && exists $merged_ref->{$tracepoint}); - - foreach my $field (keys %{$merged_ref->{$tracepoint}}) { - my @sorted; - my @val = keys %{$merged_ref->{$tracepoint}->{$field}}; - - if ($val[0] =~ /^\d+$/) { - # Sort numerically - @sorted = sort { $a <=> $b } @val; - } elsif ($val[0] =~ /^0x[\da-f]+$/i) { - # Convert the hex values and sort numerically - @sorted = sort { hex($a) <=> hex($b) } @val; - } else { - # Fallback, alphabetical sort - @sorted = sort { lc($a) cmp lc($b) } @val; - } - - my $min = $sorted[0]; - my $max = $sorted[-1]; - - print "$field $min $max\n"; - } -} - -my @events; - -while (<>) -{ - my $timestamp = '\[(?:.*)\]'; - my $elapsed = '\((?:.*)\)'; - my $hostname = '(?:.*)'; - my $tp_event = '(.*)'; - my $pkt_context = '(?:\{[^}]*\},\s)*'; - my $fields = '\{(.*)\}$'; - - # Parse babeltrace text output format - if (/$timestamp\s$elapsed\s$hostname\s$tp_event:\s$pkt_context$fields/) { - my %event_hash; - $event_hash{'tp_event'} = $1; - $event_hash{'fields'} = parse_fields($2); - - push @events, \%event_hash; - } -} - -my %merged_fields = %{merge_fields(\@{events})}; -print_fields_stats(\%merged_fields, $opt_tracepoint); diff --git a/tests/utils/bt2_plugins/Makefile.am b/tests/utils/bt2_plugins/Makefile.am new file mode 100644 index 000000000..59fdbcb83 --- /dev/null +++ b/tests/utils/bt2_plugins/Makefile.am @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-only + +SUBDIRS = event_name field_stats + +AM_CPPFLAGS += -I$(srcdir) +AM_CXXFLAGS += $(babeltrace2_CFLAGS) \ + $(WARN_FLAGS) + +noinst_LTLIBRARIES = lttngtest.la + +# lttng-tools uses -fvisibility=hidden by default, but to +# produce a loadable plugin some of the symbols must not be +# hidden. Override the `-fvisibility` for this shared object. +lttngtest_la_CXXFLAGS = \ + $(AM_CXXFLAGS) \ + -fvisibility=default + +lttngtest_la_SOURCES = \ + lttngtest-plugin.cpp + +lttngtest_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(LD_NO_UNDEFINED) \ + -rpath / -avoid-version -module $(LD_NOTEXT) + +lttngtest_la_LIBADD = \ + event_name/event_name.la \ + field_stats/field_stats.la diff --git a/tests/utils/bt2_plugins/README.md b/tests/utils/bt2_plugins/README.md new file mode 100644 index 000000000..5c0186f14 --- /dev/null +++ b/tests/utils/bt2_plugins/README.md @@ -0,0 +1,23 @@ +The `filter.lttngtest.event_name` plugin only has a single input and output port. +This means that it cannot be connected directly to a `source.ctf.fs` plugin, as +those have multiple output ports for the different event streams. + +A `filter.utils.muxer` plugin must be placed between any multi-output port plugin +and the `filter.lttngtest.event_name` plugin. This is done automatically with in +the architecture created by `babeltrace2 convert`. + +Example with `babeltrace2 convert`: + +``` +SOURCE_PATH=/tmp/tmp.1J5DueCziG +EVENT_NAME=tp:the_string +babeltrace2 --plugin-path=.libs/ convert "${SOURCE_PATH}" -c filter.lttngtest.event_name -p "names=[\"$EVENT_NAME\"]" -c sink.lttngtest.field_stats +``` + +Example with `babeltrace2 run`: + +``` +SOURCE_PATH=/tmp/tmp.1J5DueCziG +EVENT_NAME=tp:the_string +babeltrace2 --plugin-path=.libs/ run -c A:source.ctf.fs -p "inputs=[\"$SOURCE_PATH\"]" -c muxer:filter.utils.muxer -c B:filter.lttngtest.event_name -p "names=[\"$EVENT_NAME\"]" -c C:sink.lttngtest.field_stats -x A:muxer -x muxer:B -x B:C +``` diff --git a/tests/utils/bt2_plugins/event_name/Makefile.am b/tests/utils/bt2_plugins/event_name/Makefile.am new file mode 100644 index 000000000..d4dc2f1a1 --- /dev/null +++ b/tests/utils/bt2_plugins/event_name/Makefile.am @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only + +AM_CXXFLAGS += $(babeltrace2_CFLAGS) \ + $(WARN_FLAGS) + +noinst_LTLIBRARIES = event_name.la +event_name_la_SOURCES = \ + event_name.cpp \ + event_name.hpp +event_name_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(LD_NO_UNDEFINED) \ + -avoid-version -module $(LD_NOTEXT) diff --git a/tests/utils/bt2_plugins/event_name/event_name.cpp b/tests/utils/bt2_plugins/event_name/event_name.cpp new file mode 100644 index 000000000..6c8e1c5b2 --- /dev/null +++ b/tests/utils/bt2_plugins/event_name/event_name.cpp @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2023 Kienan Stewart + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include "event_name.hpp" + +#include +#include +#include +#include +#include +#include + +struct event_name { + std::unordered_set names; + const bt_value *names_value; + /* weak reference */ + bt_self_component_port_input *input_port; +}; + +struct event_name_iterator_data { + struct event_name *event_name; + bt_message_iterator *iterator; +}; + +bt_component_class_initialize_method_status +event_name_initialize(bt_self_component_filter *self_comp, + bt_self_component_filter_configuration *, + const bt_value *params, + void *) +{ + bt_component_class_initialize_method_status status; + bt_self_component_port_input *input_port; + struct event_name *event_name; + auto self = bt_self_component_filter_as_self_component(self_comp); + if (bt_self_component_filter_add_input_port(self_comp, "in", nullptr, &input_port) != + BT_SELF_COMPONENT_ADD_PORT_STATUS_OK) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT(self, + "Failed to add input port"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto end; + } + + if (bt_self_component_filter_add_output_port(self_comp, "out", nullptr, nullptr) != + BT_SELF_COMPONENT_ADD_PORT_STATUS_OK) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT(self, + "Failed to add output port"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto end; + } + + event_name = new (std::nothrow) struct event_name; + if (event_name == nullptr) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + self, "Failed to allocate memory for private component data"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_MEMORY_ERROR; + goto end; + } + + event_name->input_port = input_port; + event_name->names_value = bt_value_map_borrow_entry_value_const(params, "names"); + if (event_name->names_value == nullptr) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + self, "'names' parameter is required"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto err_free; + } + if (bt_value_get_type(event_name->names_value) != BT_VALUE_TYPE_ARRAY) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + self, "'names' parameter must be an array"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto err_free; + } + if (bt_value_array_is_empty(event_name->names_value)) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_filter_as_self_component(self_comp), + "'names' parameter must not be empty"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto err_free; + } + for (uint64_t index = 0; index < bt_value_array_get_length(event_name->names_value); + index++) { + const bt_value *names_entry = bt_value_array_borrow_element_by_index_const( + event_name->names_value, index); + if (bt_value_get_type(names_entry) != BT_VALUE_TYPE_STRING) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + self, "All members of the 'names' parameter array must be strings"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto err_free; + } + event_name->names.emplace(bt_value_string_get(names_entry)); + } + bt_value_get_ref(event_name->names_value); + bt_self_component_set_data(self, event_name); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_OK; + goto end; + +err_free: + delete event_name; +end: + return status; +} + +void event_name_finalize(bt_self_component_filter *self_comp) +{ + struct event_name *event_name = (struct event_name *) bt_self_component_get_data( + bt_self_component_filter_as_self_component(self_comp)); + bt_value_put_ref(event_name->names_value); + delete event_name; +} + +bt_message_iterator_class_initialize_method_status +event_name_message_iterator_initialize(bt_self_message_iterator *self_message_iterator, + bt_self_message_iterator_configuration *, + bt_self_component_port_output *) +{ + struct event_name *event_name = (struct event_name *) bt_self_component_get_data( + bt_self_message_iterator_borrow_component(self_message_iterator)); + assert(event_name); + + struct event_name_iterator_data *iter_data = + (struct event_name_iterator_data *) malloc(sizeof(struct event_name_iterator_data)); + + if (iter_data == nullptr) { + return BT_MESSAGE_ITERATOR_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + } + iter_data->event_name = event_name; + + if (bt_message_iterator_create_from_message_iterator( + self_message_iterator, event_name->input_port, &iter_data->iterator) != + BT_MESSAGE_ITERATOR_CREATE_FROM_MESSAGE_ITERATOR_STATUS_OK) { + free(iter_data); + return BT_MESSAGE_ITERATOR_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + } + + bt_self_message_iterator_set_data(self_message_iterator, iter_data); + + return BT_MESSAGE_ITERATOR_CLASS_INITIALIZE_METHOD_STATUS_OK; +} + +void event_name_message_iterator_finalize(bt_self_message_iterator *self_message) +{ + struct event_name_iterator_data *iter_data = + (struct event_name_iterator_data *) bt_self_message_iterator_get_data(self_message); + + assert(iter_data); + bt_message_iterator_put_ref(iter_data->iterator); + free(iter_data); +} + +static bool message_passes(const bt_message *message, const std::unordered_set& names) +{ + if (bt_message_get_type(message) != BT_MESSAGE_TYPE_EVENT) { + return true; + } + + const bt_event *event = bt_message_event_borrow_event_const(message); + const bt_event_class *event_class = bt_event_borrow_class_const(event); + const char *event_name = bt_event_class_get_name(event_class); + + if (event_name == nullptr) { + return false; + } + + if (names.find(event_name) != names.end()) { + return true; + } + + return false; +} + +bt_message_iterator_class_next_method_status +event_name_message_iterator_next(bt_self_message_iterator *self_message_iterator, + bt_message_array_const messages, + uint64_t, + uint64_t *count) +{ + bt_message_array_const upstream_messages; + uint64_t upstream_message_count; + uint64_t index = 0; + bt_message_iterator_next_status next_status; + bt_message_iterator_class_next_method_status status = + BT_MESSAGE_ITERATOR_CLASS_NEXT_METHOD_STATUS_OK; + struct event_name_iterator_data *iter_data = + (struct event_name_iterator_data *) bt_self_message_iterator_get_data( + self_message_iterator); + struct event_name *event_name = (struct event_name *) bt_self_component_get_data( + bt_self_message_iterator_borrow_component(self_message_iterator)); + + assert(event_name); + assert(iter_data); + + while (index == 0) { + next_status = bt_message_iterator_next( + iter_data->iterator, &upstream_messages, &upstream_message_count); + if (next_status != BT_MESSAGE_ITERATOR_NEXT_STATUS_OK) { + status = static_cast( + next_status); + goto end; + } + + for (uint64_t upstream_index = 0; upstream_index < upstream_message_count; + upstream_index++) { + const bt_message *upstream_message = upstream_messages[upstream_index]; + if (message_passes(upstream_message, event_name->names)) { + messages[index] = upstream_message; + index++; + } else { + bt_message_put_ref(upstream_message); + } + } + } + + *count = index; +end: + return status; +} diff --git a/tests/utils/bt2_plugins/event_name/event_name.hpp b/tests/utils/bt2_plugins/event_name/event_name.hpp new file mode 100644 index 000000000..65513b286 --- /dev/null +++ b/tests/utils/bt2_plugins/event_name/event_name.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 Kienan Stewart + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef EVENT_NAME_H +#define EVENT_NAME_H + +#include + +bt_component_class_initialize_method_status +event_name_initialize(bt_self_component_filter *self_comp, + bt_self_component_filter_configuration *config, + const bt_value *params, + void *init_data); + +void event_name_finalize(bt_self_component_filter *self_comp); + +bt_message_iterator_class_initialize_method_status +event_name_message_iterator_initialize(bt_self_message_iterator *self_message_iterator, + bt_self_message_iterator_configuration *config, + bt_self_component_port_output *self_port); + +void event_name_message_iterator_finalize(bt_self_message_iterator *self_message_iterator); + +bt_message_iterator_class_next_method_status +event_name_message_iterator_next(bt_self_message_iterator *self_message_iterator, + bt_message_array_const messages, + uint64_t capacity, + uint64_t *count); + +#endif /* EVENT_NAME_H */ diff --git a/tests/utils/bt2_plugins/field_stats/Makefile.am b/tests/utils/bt2_plugins/field_stats/Makefile.am new file mode 100644 index 000000000..93413564d --- /dev/null +++ b/tests/utils/bt2_plugins/field_stats/Makefile.am @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only + +AM_CXXFLAGS += $(babeltrace2_CFLAGS) \ + $(WARN_FLAGS) + +noinst_LTLIBRARIES = field_stats.la +field_stats_la_SOURCES = \ + field_stats.cpp \ + field_stats.hpp +field_stats_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + $(LD_NO_UNDEFINED) \ + -avoid-version -module $(LD_NOTEXT) diff --git a/tests/utils/bt2_plugins/field_stats/field_stats.cpp b/tests/utils/bt2_plugins/field_stats/field_stats.cpp new file mode 100644 index 000000000..0dc59fcac --- /dev/null +++ b/tests/utils/bt2_plugins/field_stats/field_stats.cpp @@ -0,0 +1,452 @@ +/** + * Copyright (C) 2023 Kienan Stewart + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include "../fmt.hpp" +#include "field_stats.hpp" + +#include +#include +#include +#include +#include + +struct field_stats { + bt_message_iterator *iterator; + bt_value *stats_value; + const bt_event_class *event_class; +}; + +bt_component_class_initialize_method_status +field_stats_initialize(bt_self_component_sink *self_component_sink, + bt_self_component_sink_configuration *, + const bt_value *, + void *) +{ + bt_component_class_initialize_method_status status; + struct field_stats *field_stats = nullptr; + + if (bt_self_component_sink_add_input_port(self_component_sink, "in", nullptr, nullptr) != + BT_SELF_COMPONENT_ADD_PORT_STATUS_OK) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "Failed to add input port"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto error; + } + + field_stats = (struct field_stats *) malloc(sizeof(*field_stats)); + if (field_stats == nullptr) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "Failed to allocate memory for private data"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_MEMORY_ERROR; + goto error; + } + + field_stats->iterator = nullptr; + field_stats->stats_value = bt_value_map_create(); + field_stats->event_class = nullptr; + if (field_stats->stats_value == nullptr) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "Failed to allocate memory for field_stats.stats map"); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_ERROR; + goto error; + } + bt_self_component_set_data(bt_self_component_sink_as_self_component(self_component_sink), + field_stats); + status = BT_COMPONENT_CLASS_INITIALIZE_METHOD_STATUS_OK; + goto end; + +error: + if (field_stats) { + free(field_stats); + } +end: + return status; +} + +static bt_value_map_foreach_entry_const_func_status +stats_value_print_summary(const char *key, const bt_value *value, void *) +{ + assert(bt_value_is_map(value)); + + const bt_value *min = bt_value_map_borrow_entry_value_const(value, "min"); + const bt_value *max = bt_value_map_borrow_entry_value_const(value, "max"); + const bt_value *display_base = bt_value_map_borrow_entry_value_const(value, "display_base"); + enum bt_field_class_integer_preferred_display_base display_base_value = + BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL; + + if (display_base != nullptr) { + display_base_value = (enum bt_field_class_integer_preferred_display_base) + bt_value_integer_unsigned_get(display_base); + } + assert(min != nullptr); + assert(max != nullptr); + + if (bt_value_is_string(min)) { + fmt::print("{} \"{}\" \"{}\"\n", + key, + bt_value_string_get(min), + bt_value_string_get(max)); + } else if (bt_value_is_unsigned_integer(min)) { + switch (display_base_value) { + case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL: + fmt::print("{} 0x{:X} 0x{:X}\n", + key, + bt_value_integer_unsigned_get(min), + bt_value_integer_unsigned_get(max)); + break; + default: + fmt::print("{} {} {}\n", + key, + bt_value_integer_unsigned_get(min), + bt_value_integer_unsigned_get(max)); + break; + } + } else if (bt_value_is_signed_integer(min)) { + switch (display_base_value) { + case BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL: + fmt::print("{} 0x{:X} 0x{:X}\n", + key, + (uint64_t) bt_value_integer_signed_get(min), + (uint64_t) bt_value_integer_signed_get(max)); + break; + default: + fmt::print("{} {} {}\n", + key, + bt_value_integer_signed_get(min), + bt_value_integer_signed_get(max)); + break; + } + } else if (bt_value_is_real(min)) { + fmt::print("{} {:0g} {:0g}\n", key, bt_value_real_get(min), bt_value_real_get(max)); + } else { + assert(BT_FALSE); + } + return BT_VALUE_MAP_FOREACH_ENTRY_CONST_FUNC_STATUS_OK; +} + +void field_stats_finalize(bt_self_component_sink *self_component_sink) +{ + struct field_stats *field_stats = (struct field_stats *) bt_self_component_get_data( + bt_self_component_sink_as_self_component(self_component_sink)); + bt_value_put_ref(field_stats->stats_value); + free(field_stats); +} + +bt_component_class_sink_graph_is_configured_method_status +field_stats_graph_is_configured(bt_self_component_sink *self_component_sink) +{ + struct field_stats *field_stats = (struct field_stats *) bt_self_component_get_data( + bt_self_component_sink_as_self_component(self_component_sink)); + bt_self_component_port_input *input_port = + bt_self_component_sink_borrow_input_port_by_index(self_component_sink, 0); + if (bt_message_iterator_create_from_sink_component( + self_component_sink, input_port, &field_stats->iterator) != + BT_MESSAGE_ITERATOR_CREATE_FROM_SINK_COMPONENT_STATUS_OK) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "input port message iterator creation failed"); + return BT_COMPONENT_CLASS_SINK_GRAPH_IS_CONFIGURED_METHOD_STATUS_ERROR; + } + + return BT_COMPONENT_CLASS_SINK_GRAPH_IS_CONFIGURED_METHOD_STATUS_OK; +} + +static bt_component_class_sink_consume_method_status +member_stats_set_min_max(bt_value *member_map, + const bt_field_class_structure_member *member, + const bt_field *member_field, + const bt_field_class *member_class, + const bt_field_class_type *member_class_type, + bt_self_component_sink *self_component_sink) +{ + bt_value *min, *max, *display_base = bt_value_null; + const char *name = bt_field_class_structure_member_get_name(member); + + if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER)) { + min = bt_value_integer_unsigned_create_init( + bt_field_integer_unsigned_get_value(member_field)); + max = bt_value_integer_unsigned_create_init( + bt_field_integer_unsigned_get_value(member_field)); + display_base = bt_value_integer_unsigned_create_init( + bt_field_class_integer_get_preferred_display_base(member_class)); + } else if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_SIGNED_INTEGER)) { + min = bt_value_integer_signed_create_init( + bt_field_integer_signed_get_value(member_field)); + max = bt_value_integer_signed_create_init( + bt_field_integer_signed_get_value(member_field)); + display_base = bt_value_integer_unsigned_create_init( + bt_field_class_integer_get_preferred_display_base(member_class)); + } else if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_STRING)) { + min = bt_value_string_create_init(bt_field_string_get_value(member_field)); + max = bt_value_string_create_init(bt_field_string_get_value(member_field)); + } else if (bt_field_class_type_is(*member_class_type, + BT_FIELD_CLASS_TYPE_DOUBLE_PRECISION_REAL)) { + min = bt_value_real_create_init( + bt_field_real_double_precision_get_value(member_field)); + max = bt_value_real_create_init( + bt_field_real_double_precision_get_value(member_field)); + } else if (bt_field_class_type_is(*member_class_type, + BT_FIELD_CLASS_TYPE_SINGLE_PRECISION_REAL)) { + min = bt_value_real_create_init( + bt_field_real_single_precision_get_value(member_field)); + max = bt_value_real_create_init( + bt_field_real_single_precision_get_value(member_field)); + } else if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_BIT_ARRAY)) { + min = bt_value_integer_unsigned_create_init( + bt_field_bit_array_get_value_as_integer(member_field)); + max = bt_value_integer_unsigned_create_init( + bt_field_bit_array_get_value_as_integer(member_field)); + } else { + const auto field_class_type_name = fmt::to_string(*member_class_type); + + fmt::print("Unsupported field type for '{}': {}\n", name, field_class_type_name); + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "Unsupported field type '%s' for member '%s'", + field_class_type_name.c_str(), + name); + + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; + } + + if (min != nullptr) { + bt_value_map_insert_entry(member_map, "min", min); + bt_value_put_ref(min); + } else { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "No minimum value for member '%s'", + name); + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; + } + if (max != nullptr) { + bt_value_map_insert_entry(member_map, "max", max); + bt_value_put_ref(max); + } else { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "No maximum value for member '%s'", + name); + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; + } + if (display_base != bt_value_null) { + bt_value_map_insert_entry(member_map, "display_base", display_base); + bt_value_put_ref(display_base); + } + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; +} + +static bt_component_class_sink_consume_method_status +member_stats_update_min_max(bt_value *member_map, + const bt_field_class_structure_member *member, + const bt_field *member_field, + const bt_field_class_type *member_class_type, + bt_self_component_sink *self_component_sink) +{ + const char *name = bt_field_class_structure_member_get_name(member); + bt_value *min = bt_value_map_borrow_entry_value(member_map, "min"); + bt_value *max = bt_value_map_borrow_entry_value(member_map, "max"); + + if (min == nullptr || max == nullptr) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "Missing min or max value for member '%s'", + name); + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; + } + + if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER)) { + bt_value *value = bt_value_integer_unsigned_create_init( + bt_field_integer_unsigned_get_value(member_field)); + if (bt_value_integer_unsigned_get(value) < bt_value_integer_unsigned_get(min)) { + bt_value_integer_unsigned_set(min, bt_value_integer_unsigned_get(value)); + } + if (bt_value_integer_unsigned_get(value) > bt_value_integer_unsigned_get(max)) { + bt_value_integer_unsigned_set(max, bt_value_integer_unsigned_get(value)); + } + bt_value_put_ref(value); + } else if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_SIGNED_INTEGER)) { + bt_value *value = bt_value_integer_signed_create_init( + bt_field_integer_signed_get_value(member_field)); + if (bt_value_integer_signed_get(value) < bt_value_integer_signed_get(min)) { + bt_value_integer_signed_set(min, bt_value_integer_signed_get(value)); + } + if (bt_value_integer_signed_get(value) > bt_value_integer_signed_get(max)) { + bt_value_integer_signed_set(max, bt_value_integer_signed_get(value)); + } + bt_value_put_ref(value); + } else if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_STRING)) { + bt_value *value = + bt_value_string_create_init(bt_field_string_get_value(member_field)); + if (strcmp(bt_value_string_get(value), bt_value_string_get(min)) < 0) { + bt_value_string_set(min, bt_value_string_get(value)); + } + if (strcmp(bt_value_string_get(value), bt_value_string_get(max)) > 0) { + bt_value_string_set(max, bt_value_string_get(value)); + } + bt_value_put_ref(value); + } else if (bt_field_class_type_is(*member_class_type, + BT_FIELD_CLASS_TYPE_DOUBLE_PRECISION_REAL)) { + bt_value *value = bt_value_real_create_init( + bt_field_real_double_precision_get_value(member_field)); + if (bt_value_real_get(value) < bt_value_real_get(min)) { + bt_value_real_set(min, bt_value_real_get(value)); + } + if (bt_value_real_get(value) > bt_value_real_get(max)) { + bt_value_real_set(max, bt_value_real_get(value)); + } + bt_value_put_ref(value); + } else if (bt_field_class_type_is(*member_class_type, + BT_FIELD_CLASS_TYPE_SINGLE_PRECISION_REAL)) { + bt_value *value = bt_value_real_create_init( + (double) bt_field_real_single_precision_get_value(member_field)); + if (bt_value_real_get(value) < bt_value_real_get(min)) { + bt_value_real_set(min, bt_value_real_get(value)); + } + if (bt_value_real_get(value) > bt_value_real_get(max)) { + bt_value_real_set(max, bt_value_real_get(value)); + } + bt_value_put_ref(value); + } else if (bt_field_class_type_is(*member_class_type, BT_FIELD_CLASS_TYPE_BIT_ARRAY)) { + bt_value *value = bt_value_integer_unsigned_create_init( + bt_field_bit_array_get_value_as_integer(member_field)); + if (bt_value_integer_unsigned_get(value) < bt_value_integer_unsigned_get(min)) { + bt_value_integer_unsigned_set(min, bt_value_integer_unsigned_get(value)); + } + if (bt_value_integer_unsigned_get(value) > bt_value_integer_unsigned_get(max)) { + bt_value_integer_unsigned_set(max, bt_value_integer_unsigned_get(value)); + } + bt_value_put_ref(value); + } else { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component(self_component_sink), + "Unsupported field type '%ld' for member '%s'", + *member_class_type, + name); + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; + } + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; +} + +static bt_component_class_sink_consume_method_status +update_stats(const bt_message *message, + field_stats *field_stats, + bt_self_component_sink *self_component_sink) +{ + if (bt_message_get_type(message) != BT_MESSAGE_TYPE_EVENT) { + /* It's not an error to get non-EVENT messages */ + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; + } + + bt_component_class_sink_consume_method_status status; + const bt_event *event = bt_message_event_borrow_event_const(message); + const bt_field *event_payload = bt_event_borrow_payload_field_const(event); + const bt_event_class *event_class = bt_event_borrow_class_const(event); + const bt_field_class *event_payload_class = + bt_event_class_borrow_payload_field_class_const(event_class); + + if (field_stats->event_class != nullptr) { + assert(event_class == field_stats->event_class); + } else { + field_stats->event_class = event_class; + } + + /* Iterate over each field in the event payload */ + for (uint64_t index = 0; + index < bt_field_class_structure_get_member_count(event_payload_class); + index++) { + const bt_field_class_structure_member *member = + bt_field_class_structure_borrow_member_by_index_const(event_payload_class, + index); + const char *name = bt_field_class_structure_member_get_name(member); + const bt_field *member_field = + bt_field_structure_borrow_member_field_by_name_const(event_payload, name); + const bt_field_class *member_class = + bt_field_class_structure_member_borrow_field_class_const(member); + const bt_field_class_type member_class_type = bt_field_class_get_type(member_class); + + /* Ignore array and structure field types. */ + if (bt_field_class_type_is(member_class_type, BT_FIELD_CLASS_TYPE_ARRAY) || + bt_field_class_type_is(member_class_type, BT_FIELD_CLASS_TYPE_STRUCTURE)) { + continue; + } + + bt_value *member_map = + bt_value_map_borrow_entry_value(field_stats->stats_value, name); + if (member_map == nullptr) { + if (bt_value_map_insert_empty_map_entry( + field_stats->stats_value, name, &member_map) != + BT_VALUE_MAP_INSERT_ENTRY_STATUS_OK) { + BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_COMPONENT( + bt_self_component_sink_as_self_component( + self_component_sink), + "Failed to insert new empty map entry for field '%s'", + name); + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_ERROR; + } + + status = member_stats_set_min_max(member_map, + member, + member_field, + member_class, + &member_class_type, + self_component_sink); + if (status != BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK) { + return status; + } + } else { + status = member_stats_update_min_max(member_map, + member, + member_field, + &member_class_type, + self_component_sink); + if (status != BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK) { + return status; + } + } + } + return BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; +} + +bt_component_class_sink_consume_method_status +field_stats_consume(bt_self_component_sink *self_component_sink) +{ + bt_component_class_sink_consume_method_status status; + struct field_stats *field_stats = (struct field_stats *) bt_self_component_get_data( + bt_self_component_sink_as_self_component(self_component_sink)); + bt_message_array_const messages; + uint64_t message_count; + bt_message_iterator_next_status next_status; + + assert(field_stats); + next_status = bt_message_iterator_next(field_stats->iterator, &messages, &message_count); + + if (next_status != BT_MESSAGE_ITERATOR_NEXT_STATUS_OK) { + if (next_status == BT_MESSAGE_ITERATOR_NEXT_STATUS_END) { + bt_value_map_foreach_entry_const( + field_stats->stats_value, stats_value_print_summary, nullptr); + bt_message_iterator_put_ref(field_stats->iterator); + } + status = static_cast(next_status); + goto end; + } + + for (uint64_t index = 0; index < message_count; index++) { + const bt_message *message = messages[index]; + status = update_stats(message, field_stats, self_component_sink); + bt_message_put_ref(message); + if (status != BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK) { + goto end; + } + } + status = BT_COMPONENT_CLASS_SINK_CONSUME_METHOD_STATUS_OK; +end: + return status; +} diff --git a/tests/utils/bt2_plugins/field_stats/field_stats.hpp b/tests/utils/bt2_plugins/field_stats/field_stats.hpp new file mode 100644 index 000000000..833a4cfaf --- /dev/null +++ b/tests/utils/bt2_plugins/field_stats/field_stats.hpp @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2023 Kienan Stewart + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef FIELD_STATS_H +#define FIELD_STATS_H + +#include + +bt_component_class_initialize_method_status +field_stats_initialize(bt_self_component_sink *self_component_sink, + bt_self_component_sink_configuration *config, + const bt_value *params, + void *initialize_method_data); + +void field_stats_finalize(bt_self_component_sink *self_component_sink); + +bt_component_class_sink_graph_is_configured_method_status +field_stats_graph_is_configured(bt_self_component_sink *self_component_sink); + +bt_component_class_sink_consume_method_status +field_stats_consume(bt_self_component_sink *self_component_sink); + +#endif /* FIELD_STATS_H */ diff --git a/tests/utils/bt2_plugins/fmt.hpp b/tests/utils/bt2_plugins/fmt.hpp new file mode 100644 index 000000000..d5c0857da --- /dev/null +++ b/tests/utils/bt2_plugins/fmt.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 Jérémie Galarneau + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#ifndef LTTNG_TESTS_UTILS_BT2_PLUGINS_FMT_H +#define LTTNG_TESTS_UTILS_BT2_PLUGINS_FMT_H + +#include + +#include + +/* + * Due to a bug in g++ < 7.1, this specialization must be enclosed in the fmt namespace, + * see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56480. + */ +namespace fmt { +template <> +struct formatter : formatter { + template + typename FormatContextType::iterator format(const bt_field_class_type field_class_type, + FormatContextType& ctx) + { + const char *name; + + switch (field_class_type) { + case BT_FIELD_CLASS_TYPE_BOOL: + name = "BOOL"; + break; + case BT_FIELD_CLASS_TYPE_BIT_ARRAY: + name = "BIT_ARRAY"; + break; + case BT_FIELD_CLASS_TYPE_INTEGER: + name = "INTEGER"; + break; + case BT_FIELD_CLASS_TYPE_UNSIGNED_INTEGER: + name = "UNSIGNED_INTEGER"; + break; + case BT_FIELD_CLASS_TYPE_SIGNED_INTEGER: + name = "SIGNED_INTEGER"; + break; + case BT_FIELD_CLASS_TYPE_ENUMERATION: + name = "ENUMERATION"; + break; + case BT_FIELD_CLASS_TYPE_UNSIGNED_ENUMERATION: + name = "UNSIGNED_ENUMERATION"; + break; + case BT_FIELD_CLASS_TYPE_SIGNED_ENUMERATION: + name = "SIGNED_ENUMERATION"; + break; + case BT_FIELD_CLASS_TYPE_REAL: + name = "REAL"; + break; + case BT_FIELD_CLASS_TYPE_SINGLE_PRECISION_REAL: + name = "SINGLE_PRECISION_REAL"; + break; + case BT_FIELD_CLASS_TYPE_DOUBLE_PRECISION_REAL: + name = "DOUBLE_PRECISION_REAL"; + break; + case BT_FIELD_CLASS_TYPE_STRING: + name = "STRING"; + break; + case BT_FIELD_CLASS_TYPE_STRUCTURE: + name = "STRUCTURE"; + break; + case BT_FIELD_CLASS_TYPE_ARRAY: + name = "ARRAY"; + break; + case BT_FIELD_CLASS_TYPE_STATIC_ARRAY: + name = "STATIC_ARRAY"; + break; + case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY: + name = "DYNAMIC_ARRAY"; + break; + case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY_WITHOUT_LENGTH_FIELD: + name = "DYNAMIC_ARRAY_WITHOUT_LENGTH_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_DYNAMIC_ARRAY_WITH_LENGTH_FIELD: + name = "DYNAMIC_ARRAY_WITH_LENGTH_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_OPTION: + name = "OPTION"; + break; + case BT_FIELD_CLASS_TYPE_OPTION_WITHOUT_SELECTOR_FIELD: + name = "OPTION_WITHOUT_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_OPTION_WITH_SELECTOR_FIELD: + name = "OPTION_WITH_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_OPTION_WITH_BOOL_SELECTOR_FIELD: + name = "OPTION_WITH_BOOL_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_OPTION_WITH_INTEGER_SELECTOR_FIELD: + name = "OPTION_WITH_INTEGER_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_OPTION_WITH_UNSIGNED_INTEGER_SELECTOR_FIELD: + name = "OPTION_WITH_UNSIGNED_INTEGER_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_OPTION_WITH_SIGNED_INTEGER_SELECTOR_FIELD: + name = "OPTION_WITH_SIGNED_INTEGER_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_VARIANT: + name = "VARIANT"; + break; + case BT_FIELD_CLASS_TYPE_VARIANT_WITHOUT_SELECTOR_FIELD: + name = "VARIANT_WITHOUT_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_VARIANT_WITH_SELECTOR_FIELD: + name = "VARIANT_WITH_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_VARIANT_WITH_INTEGER_SELECTOR_FIELD: + name = "VARIANT_WITH_INTEGER_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_VARIANT_WITH_UNSIGNED_INTEGER_SELECTOR_FIELD: + name = "VARIANT_WITH_UNSIGNED_INTEGER_SELECTOR_FIELD"; + break; + case BT_FIELD_CLASS_TYPE_VARIANT_WITH_SIGNED_INTEGER_SELECTOR_FIELD: + name = "VARIANT_WITH_SIGNED_INTEGER_SELECTOR_FIELD"; + break; + default: + abort(); + } + + return format_to(ctx.out(), name); + } +}; +} /* namespace fmt */ + +#endif /* LTTNG_TESTS_UTILS_BT2_PLUGINS_FMT_H */ diff --git a/tests/utils/bt2_plugins/lttngtest-plugin.cpp b/tests/utils/bt2_plugins/lttngtest-plugin.cpp new file mode 100644 index 000000000..ef6d6d216 --- /dev/null +++ b/tests/utils/bt2_plugins/lttngtest-plugin.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2023 Kienan Stewart + * + * SPDX-License-Identifier: LGPL-2.1-only + * + */ + +#include "event_name/event_name.hpp" +#include "field_stats/field_stats.hpp" + +#include + +BT_PLUGIN_MODULE(); + +BT_PLUGIN(lttngtest); +BT_PLUGIN_DESCRIPTION("Filter and sink used in lttng-tools test suite"); +BT_PLUGIN_AUTHOR("Kienan Stewart"); +BT_PLUGIN_LICENSE("LGPL-2.1-only"); + +/* flt.lttngtest.event_name */ +/* Filter class to pass events matching given names */ +BT_PLUGIN_FILTER_COMPONENT_CLASS(event_name, event_name_message_iterator_next); +BT_PLUGIN_FILTER_COMPONENT_CLASS_DESCRIPTION(event_name, "Filter events by tracepoint name(s)"); +BT_PLUGIN_FILTER_COMPONENT_CLASS_INITIALIZE_METHOD(event_name, event_name_initialize); +BT_PLUGIN_FILTER_COMPONENT_CLASS_FINALIZE_METHOD(event_name, event_name_finalize); +BT_PLUGIN_FILTER_COMPONENT_CLASS_MESSAGE_ITERATOR_CLASS_INITIALIZE_METHOD( + event_name, event_name_message_iterator_initialize); +BT_PLUGIN_FILTER_COMPONENT_CLASS_MESSAGE_ITERATOR_CLASS_FINALIZE_METHOD( + event_name, event_name_message_iterator_finalize); + +/* sink.lttngtest.field_stats */ +/* Sink class to produce certain statistics for seen fields */ +BT_PLUGIN_SINK_COMPONENT_CLASS(field_stats, field_stats_consume); +BT_PLUGIN_SINK_COMPONENT_CLASS_DESCRIPTION(field_stats, + "Track minimum and maxiumum values of seen fields"); +BT_PLUGIN_SINK_COMPONENT_CLASS_INITIALIZE_METHOD(field_stats, field_stats_initialize); +BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(field_stats, field_stats_finalize); +BT_PLUGIN_SINK_COMPONENT_CLASS_GRAPH_IS_CONFIGURED_METHOD(field_stats, + field_stats_graph_is_configured); -- 2.34.1