tests: Replace babelstats.pl with bt2 plugins
authorKienan Stewart <kstewart@efficios.com>
Thu, 28 Sep 2023 20:54:42 +0000 (16:54 -0400)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Fri, 8 Mar 2024 16:55:24 +0000 (11:55 -0500)
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 <kstewart@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
18 files changed:
CONTRIBUTING.md
README.adoc
configure.ac
tests/regression/tools/exclusion/test_exclusion
tests/regression/tools/filtering/test_valid_filter
tests/regression/tools/tracefile-limits/test_tracefile_count
tests/utils/Makefile.am
tests/utils/babelstats.pl [deleted file]
tests/utils/bt2_plugins/Makefile.am [new file with mode: 0644]
tests/utils/bt2_plugins/README.md [new file with mode: 0644]
tests/utils/bt2_plugins/event_name/Makefile.am [new file with mode: 0644]
tests/utils/bt2_plugins/event_name/event_name.cpp [new file with mode: 0644]
tests/utils/bt2_plugins/event_name/event_name.hpp [new file with mode: 0644]
tests/utils/bt2_plugins/field_stats/Makefile.am [new file with mode: 0644]
tests/utils/bt2_plugins/field_stats/field_stats.cpp [new file with mode: 0644]
tests/utils/bt2_plugins/field_stats/field_stats.hpp [new file with mode: 0644]
tests/utils/bt2_plugins/fmt.hpp [new file with mode: 0644]
tests/utils/bt2_plugins/lttngtest-plugin.cpp [new file with mode: 0644]

index 8b15bea0f05b09c8be02148dc21ae14419669e62..fbb48cd5ee145f11ceff31910ead598524d6f26c 100644 (file)
@@ -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.
 
index d66b4ff381fb1ebe26a07eaa93f02c16f7cba9ba..6ec60856a1419b28e202b0afd4fcf1baba0b8244 100644 (file)
@@ -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.
 +
index 836ff15b8e562bd2cb2738e43e6ce8fc84c6d386..b479c7350eafaaeb1d189ab80aaa64613eea0999 100644 (file)
@@ -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
index 47deede0604b2b5dc1d3e0e4795157ecef9a4dea..6b3a14fd476ffdb33771f470baecf75c188da12b 100755 (executable)
@@ -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
index 1d7b0db4726c25be78c1ab94b2cbd8a23636eb26..79a9d85f237c393317e9df6da9ae369aeb3a29d0 100755 (executable)
@@ -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
 }
 
index df32b4039cc56e8ecff209da1ea50f87738d5297..9253d3ec2fcc0afdb2b6abe7320bb6f086b51ed7 100755 (executable)
@@ -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"
index 941476e316920a9bc45160f74e2ac34f858208d3..455e749a25f2a553217d43df2ddcaf549964ca11 100644 (file)
@@ -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 (executable)
index 13c710b..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-#!/usr/bin/env perl
-
-# Copyright (C) 2012 Christian Babeux <christian.babeux@efficios.com>
-#
-# 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 <name>");
-
-# 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 (file)
index 0000000..59fdbcb
--- /dev/null
@@ -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 (file)
index 0000000..5c0186f
--- /dev/null
@@ -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 (file)
index 0000000..d4dc2f1
--- /dev/null
@@ -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 (file)
index 0000000..6c8e1c5
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2023 Kienan Stewart <kstewart@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#include "event_name.hpp"
+
+#include <assert.h>
+#include <babeltrace2/babeltrace.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <unordered_set>
+
+struct event_name {
+       std::unordered_set<std::string> 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<std::string>& 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<bt_message_iterator_class_next_method_status>(
+                               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 (file)
index 0000000..65513b2
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 Kienan Stewart <kstewart@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#ifndef EVENT_NAME_H
+#define EVENT_NAME_H
+
+#include <babeltrace2/babeltrace.h>
+
+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 (file)
index 0000000..9341356
--- /dev/null
@@ -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 (file)
index 0000000..0dc59fc
--- /dev/null
@@ -0,0 +1,452 @@
+/**
+ * Copyright (C) 2023 Kienan Stewart <kstewart@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#include "../fmt.hpp"
+#include "field_stats.hpp"
+
+#include <assert.h>
+#include <babeltrace2/babeltrace.h>
+#include <cstring>
+#include <stdio.h>
+#include <stdlib.h>
+
+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<bt_component_class_sink_consume_method_status>(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 (file)
index 0000000..833a4cf
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (C) 2023 Kienan Stewart <kstewart@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#ifndef FIELD_STATS_H
+#define FIELD_STATS_H
+
+#include <babeltrace2/babeltrace.h>
+
+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 (file)
index 0000000..d5c0857
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2024 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#ifndef LTTNG_TESTS_UTILS_BT2_PLUGINS_FMT_H
+#define LTTNG_TESTS_UTILS_BT2_PLUGINS_FMT_H
+
+#include <common/format.hpp>
+
+#include <babeltrace2/babeltrace.h>
+
+/*
+ * 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<bt_field_class_type> : formatter<std::string> {
+       template <typename FormatContextType>
+       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 (file)
index 0000000..ef6d6d2
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 Kienan Stewart <kstewart@efficios.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ */
+
+#include "event_name/event_name.hpp"
+#include "field_stats/field_stats.hpp"
+
+#include <babeltrace2/babeltrace.h>
+
+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);
This page took 0.043347 seconds and 4 git commands to generate.