From c7eee084ca459af66ba1f6d375fba3f89db93584 Mon Sep 17 00:00:00 2001 From: Philippe Proulx Date: Mon, 14 Aug 2017 17:55:53 -0400 Subject: [PATCH] Add query executor MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Use a new object, a query executor, to query a component class: struct bt_query_executor *query_exec = bt_query_executor_create(); enum bt_query_status status = bt_query_executor_query(query_exec, comp_cls, "object", params, &result); The user's query method receives this query executor as a parameter so that some state can be shared between the user who queries (calls bt_query_executor_query()) and the user's method. Currently, only a cancellation flag is part of the query executor's state, so that, if you wish to cancel a query during a signal handler, you can call bt_query_executor_cancel(): then, when the user's method's current system call sets the EINTR error (or the equivalent on other supported platforms), it can check the state of the query executor with bt_query_executor_is_canceled() to know whether to retry the system call (debugging mode, query executor is not canceled) or to return an error (application mode, query executor is canceled). This is the same mechanism that is used with the graph object and component objects: bt_graph_cancel() and bt_graph_is_canceled(). Signed-off-by: Philippe Proulx Signed-off-by: Jérémie Galarneau --- bindings/python/bt2/Makefile.am | 2 + bindings/python/bt2/bt2/__init__.py.in | 13 + bindings/python/bt2/bt2/component.py | 48 +--- bindings/python/bt2/bt2/native_bt.i | 17 ++ .../python/bt2/bt2/native_btcomponentclass.i | 97 +++++-- bindings/python/bt2/bt2/native_btqueryexec.i | 49 ++++ bindings/python/bt2/bt2/native_btversion.i | 4 - bindings/python/bt2/bt2/query_executor.py | 96 +++++++ cli/babeltrace.c | 251 +++++++++++++----- include/Makefile.am | 2 + include/babeltrace/babeltrace.h | 1 + include/babeltrace/graph/component-class.h | 14 +- .../graph/query-executor-internal.h | 58 ++++ include/babeltrace/graph/query-executor.h | 70 +++++ lib/graph/Makefile.am | 3 +- lib/graph/component-class.c | 46 ---- lib/graph/graph.c | 16 +- lib/graph/query-executor.c | 204 ++++++++++++++ plugins/ctf/fs-src/fs.c | 16 +- plugins/ctf/fs-src/fs.h | 4 +- plugins/ctf/fs-src/query.c | 56 ++-- plugins/ctf/fs-src/query.h | 6 +- plugins/ctf/lttng-live/lttng-live-internal.h | 4 +- plugins/ctf/lttng-live/lttng-live.c | 43 ++- .../python/bt2/test_component_class.py | 27 +- .../python/bt2/test_query_executor.py | 131 +++++++++ tests/lib/test-plugin-plugins/sfs.c | 22 +- tests/lib/test_plugin.c | 26 +- 28 files changed, 1076 insertions(+), 250 deletions(-) create mode 100644 bindings/python/bt2/bt2/native_btqueryexec.i create mode 100644 bindings/python/bt2/bt2/query_executor.py create mode 100644 include/babeltrace/graph/query-executor-internal.h create mode 100644 include/babeltrace/graph/query-executor.h create mode 100644 lib/graph/query-executor.c create mode 100644 tests/bindings/python/bt2/test_query_executor.py diff --git a/bindings/python/bt2/Makefile.am b/bindings/python/bt2/Makefile.am index 2ae93f3f..d35a3adb 100644 --- a/bindings/python/bt2/Makefile.am +++ b/bindings/python/bt2/Makefile.am @@ -26,6 +26,7 @@ STATIC_BINDINGS_DEPS = \ bt2/native_btpacket.i \ bt2/native_btplugin.i \ bt2/native_btport.i \ + bt2/native_btqueryexec.i \ bt2/native_btref.i \ bt2/native_btstreamclass.i \ bt2/native_btstream.i \ @@ -50,6 +51,7 @@ STATIC_BINDINGS_DEPS = \ bt2/plugin.py \ bt2/port.py \ bt2/py_plugin.py \ + bt2/query_executor.py \ bt2/stream_class.py \ bt2/stream.py \ bt2/trace.py \ diff --git a/bindings/python/bt2/bt2/__init__.py.in b/bindings/python/bt2/bt2/__init__.py.in index ebab24ab..2cc4baaa 100644 --- a/bindings/python/bt2/bt2/__init__.py.in +++ b/bindings/python/bt2/bt2/__init__.py.in @@ -72,6 +72,7 @@ from bt2.port import _PrivateInputPort from bt2.port import _PrivateOutputPort from bt2.port import _PrivatePort from bt2.py_plugin import * +from bt2.query_executor import * from bt2.stream import _Stream from bt2.stream_class import * from bt2.trace import * @@ -95,6 +96,14 @@ class NoSuchPlugin(Error): pass +class InvalidQueryObject(Error): + pass + + +class InvalidQueryParams(Error): + pass + + class UnsupportedFeature(Exception): pass @@ -123,6 +132,10 @@ class GraphCanceled(Exception): pass +class QueryExecutorCanceled(Exception): + pass + + class NotificationIteratorCanceled(Exception): pass diff --git a/bindings/python/bt2/bt2/component.py b/bindings/python/bt2/bt2/component.py index 927fd061..9c178681 100644 --- a/bindings/python/bt2/bt2/component.py +++ b/bindings/python/bt2/bt2/component.py @@ -54,9 +54,6 @@ class _GenericComponentClass(object._Object): def help(self): return native_bt.component_class_get_help(self._ptr) - def query(self, obj, params=None): - return _query(self._ptr, obj, params) - def __eq__(self, other): if not isinstance(other, _GenericComponentClass): try: @@ -290,24 +287,6 @@ def _trim_docstring(docstring): return '\n'.join(trimmed) -def _query(comp_cls_ptr, obj, params): - utils._check_str(obj) - - if params is None: - params_ptr = native_bt.value_null - else: - params = bt2.create_value(params) - params_ptr = params._ptr - - results_ptr = native_bt.component_class_query(comp_cls_ptr, obj, - params_ptr) - - if results_ptr is None: - raise bt2.Error('cannot query info with object "{}"'.format(obj)) - - return bt2.values._create_from_ptr(results_ptr) - - # Metaclass for component classes defined by Python code. # # The Python user can create a standard Python class which inherits one @@ -488,10 +467,7 @@ class _UserComponentType(type): def addr(cls): return int(cls._cc_ptr) - def query(cls, obj, params=None): - return _query(cls._cc_ptr, obj, params) - - def _query_from_native(cls, obj, params_ptr): + def _query_from_native(cls, query_exec_ptr, obj, params_ptr): # this can raise, in which case the native call to # bt_component_class_query() returns NULL if params_ptr is not None: @@ -500,24 +476,17 @@ class _UserComponentType(type): else: params = None - try: - results = cls._query(obj, params) - except: - if not _NO_PRINT_TRACEBACK: - traceback.print_exc() + native_bt.get(query_exec_ptr) + query_exec = bt2.QueryExecutor._create_from_ptr(query_exec_ptr) - return + # this can raise, but the native side checks the exception + results = cls._query(query_exec, obj, params) if results is NotImplemented: return results - try: - results = bt2.create_value(results) - except: - if not _NO_PRINT_TRACEBACK: - traceback.print_exc() - - return + # this can raise, but the native side checks the exception + results = bt2.create_value(results) if results is None: results_addr = int(native_bt.value_null) @@ -528,8 +497,7 @@ class _UserComponentType(type): return results_addr - @classmethod - def _query(cls, obj, params): + def _query(cls, query_executor, obj, params): # BT catches this and returns NULL to the user return NotImplemented diff --git a/bindings/python/bt2/bt2/native_bt.i b/bindings/python/bt2/bt2/native_bt.i index c7ae8d63..59595a31 100644 --- a/bindings/python/bt2/bt2/native_bt.i +++ b/bindings/python/bt2/bt2/native_bt.i @@ -126,6 +126,22 @@ typedef int bt_bool; } } +/* Output argument typemap for value output (always appends) */ +%typemap(in, numinputs=0) struct bt_value **BTOUTVALUE (struct bt_value *temp_value = NULL) { + $1 = &temp_value; +} + +%typemap(argout) struct bt_value **BTOUTVALUE { + if (*$1) { + /* SWIG_Python_AppendOutput() steals the created object */ + $result = SWIG_Python_AppendOutput($result, SWIG_NewPointerObj(SWIG_as_voidptr(*$1), SWIGTYPE_p_bt_value, 0)); + } else { + /* SWIG_Python_AppendOutput() steals Py_None */ + Py_INCREF(Py_None); + $result = SWIG_Python_AppendOutput($result, Py_None); + } +} + /* Output argument typemap for initialized uint64_t output parameter (always appends) */ %typemap(in, numinputs=0) uint64_t *OUTPUTINIT (uint64_t temp = -1ULL) { $1 = &temp; @@ -194,6 +210,7 @@ typedef int bt_bool; %include "native_btpacket.i" %include "native_btplugin.i" %include "native_btport.i" +%include "native_btqueryexec.i" %include "native_btref.i" %include "native_btstream.i" %include "native_btstreamclass.i" diff --git a/bindings/python/bt2/bt2/native_btcomponentclass.i b/bindings/python/bt2/bt2/native_btcomponentclass.i index da25e376..2fe6ce34 100644 --- a/bindings/python/bt2/bt2/native_btcomponentclass.i +++ b/bindings/python/bt2/bt2/native_btcomponentclass.i @@ -40,9 +40,6 @@ const char *bt_component_class_get_description( struct bt_component_class *component_class); const char *bt_component_class_get_help( struct bt_component_class *component_class); -struct bt_value *bt_component_class_query( - struct bt_component_class *component_class, - const char *object, struct bt_value *params); enum bt_component_class_type bt_component_class_get_type( struct bt_component_class *component_class); @@ -108,9 +105,9 @@ static PyObject *py_mod_bt2_exc_unsupported_feature_type = NULL; static PyObject *py_mod_bt2_exc_try_again_type = NULL; static PyObject *py_mod_bt2_exc_stop_type = NULL; static PyObject *py_mod_bt2_exc_port_connection_refused_type = NULL; -static PyObject *py_mod_bt2_exc_graph_canceled_type = NULL; static PyObject *py_mod_bt2_exc_notif_iter_canceled_type = NULL; -static PyObject *py_mod_bt2_exc_connection_ended_type = NULL; +static PyObject *py_mod_bt2_exc_invalid_query_object_type = NULL; +static PyObject *py_mod_bt2_exc_invalid_query_params_type = NULL; static void bt_py3_cc_init_from_bt2(void) { @@ -129,17 +126,22 @@ static void bt_py3_cc_init_from_bt2(void) assert(py_mod_bt2_exc_error_type); py_mod_bt2_exc_unsupported_feature_type = PyObject_GetAttrString(py_mod_bt2, "UnsupportedFeature"); + assert(py_mod_bt2_exc_unsupported_feature_type); py_mod_bt2_exc_try_again_type = PyObject_GetAttrString(py_mod_bt2, "TryAgain"); + assert(py_mod_bt2_exc_try_again_type); py_mod_bt2_exc_stop_type = PyObject_GetAttrString(py_mod_bt2, "Stop"); + assert(py_mod_bt2_exc_stop_type); py_mod_bt2_exc_port_connection_refused_type = PyObject_GetAttrString(py_mod_bt2, "PortConnectionRefused"); - py_mod_bt2_exc_graph_canceled_type = - PyObject_GetAttrString(py_mod_bt2, "GraphCanceled"); - py_mod_bt2_exc_connection_ended_type = - PyObject_GetAttrString(py_mod_bt2, "ConnectionEnded"); - assert(py_mod_bt2_exc_stop_type); + assert(py_mod_bt2_exc_port_connection_refused_type); + py_mod_bt2_exc_invalid_query_object_type = + PyObject_GetAttrString(py_mod_bt2, "InvalidQueryObject"); + assert(py_mod_bt2_exc_invalid_query_object_type); + py_mod_bt2_exc_invalid_query_params_type = + PyObject_GetAttrString(py_mod_bt2, "InvalidQueryParams"); + assert(py_mod_bt2_exc_invalid_query_params_type); } static void bt_py3_cc_exit_handler(void) @@ -163,9 +165,9 @@ static void bt_py3_cc_exit_handler(void) Py_XDECREF(py_mod_bt2_exc_try_again_type); Py_XDECREF(py_mod_bt2_exc_stop_type); Py_XDECREF(py_mod_bt2_exc_port_connection_refused_type); - Py_XDECREF(py_mod_bt2_exc_graph_canceled_type); Py_XDECREF(py_mod_bt2_exc_notif_iter_canceled_type); - Py_XDECREF(py_mod_bt2_exc_connection_ended_type); + Py_XDECREF(py_mod_bt2_exc_invalid_query_object_type); + Py_XDECREF(py_mod_bt2_exc_invalid_query_params_type); } @@ -211,6 +213,33 @@ end: return status; } +static enum bt_query_status bt_py3_exc_to_query_status(void) +{ + enum bt_query_status status = BT_QUERY_STATUS_OK; + PyObject *exc = PyErr_Occurred(); + + if (!exc) { + goto end; + } + + if (PyErr_GivenExceptionMatches(exc, + py_mod_bt2_exc_invalid_query_object_type)) { + status = BT_QUERY_STATUS_INVALID_OBJECT; + } else if (PyErr_GivenExceptionMatches(exc, + py_mod_bt2_exc_invalid_query_params_type)) { + status = BT_QUERY_STATUS_INVALID_PARAMS; + } else if (PyErr_GivenExceptionMatches(exc, + py_mod_bt2_exc_try_again_type)) { + status = BT_QUERY_STATUS_AGAIN; + } else { + status = BT_QUERY_STATUS_ERROR; + } + +end: + PyErr_Clear(); + return status; +} + static enum bt_component_status bt_py3_exc_to_component_status(void) { enum bt_component_status status = BT_COMPONENT_STATUS_OK; @@ -493,16 +522,21 @@ static void bt_py3_cc_port_disconnected( Py_XDECREF(py_method_result); } -static struct bt_value *bt_py3_cc_query( +static struct bt_component_class_query_return bt_py3_cc_query( struct bt_component_class *comp_cls, + struct bt_query_executor *query_exec, const char *object, struct bt_value *params) { PyObject *py_cls = NULL; PyObject *py_params_ptr = NULL; + PyObject *py_query_exec_ptr = NULL; PyObject *py_query_func = NULL; PyObject *py_object = NULL; PyObject *py_results_addr = NULL; - struct bt_value *results = NULL; + struct bt_component_class_query_return ret = { + .status = BT_QUERY_STATUS_OK, + .result = NULL, + }; py_cls = lookup_cc_ptr_to_py_cls(comp_cls); if (!py_cls) { @@ -518,6 +552,13 @@ static struct bt_value *bt_py3_cc_query( goto error; } + py_query_exec_ptr = SWIG_NewPointerObj(SWIG_as_voidptr(query_exec), + SWIGTYPE_p_bt_query_executor, 0); + if (!py_query_exec_ptr) { + BT_LOGE_STR("Failed to create a SWIG pointer object."); + goto error; + } + py_object = SWIG_FromCharPtr(object); if (!py_object) { BT_LOGE_STR("Failed to create a Python string."); @@ -525,10 +566,19 @@ static struct bt_value *bt_py3_cc_query( } py_results_addr = PyObject_CallMethod(py_cls, - "_query_from_native", "(OO)", py_object, py_params_ptr); - if (!py_results_addr || py_results_addr == Py_None) { - BT_LOGE_STR("User's _query() method failed."); - goto error; + "_query_from_native", "(OOO)", py_query_exec_ptr, + py_object, py_params_ptr); + ret.status = bt_py3_exc_to_query_status(); + if (!py_results_addr && ret.status == BT_QUERY_STATUS_OK) { + /* Pretty sure this should never happen, but just in case */ + BT_LOGE("_query_from_native() class method failed without raising an exception: " + "status=%d", ret.status); + ret.status = BT_QUERY_STATUS_ERROR; + goto end; + } + + if (ret.status != BT_QUERY_STATUS_OK) { + goto end; } if (py_results_addr == Py_NotImplemented) { @@ -541,21 +591,24 @@ static struct bt_value *bt_py3_cc_query( * (PyLong) containing the address of a BT value object (new * reference). */ - results = (void *) PyLong_AsUnsignedLongLong(py_results_addr); + ret.result = (void *) PyLong_AsUnsignedLongLong(py_results_addr); assert(!PyErr_Occurred()); - assert(results); + assert(ret.result); goto end; error: - BT_PUT(results); + BT_PUT(ret.result); PyErr_Clear(); + ret.status = BT_QUERY_STATUS_ERROR; + BT_PUT(ret.result); end: Py_XDECREF(py_params_ptr); + Py_XDECREF(py_query_exec_ptr); Py_XDECREF(py_query_func); Py_XDECREF(py_object); Py_XDECREF(py_results_addr); - return results; + return ret; } static enum bt_notification_iterator_status bt_py3_cc_notification_iterator_init( diff --git a/bindings/python/bt2/bt2/native_btqueryexec.i b/bindings/python/bt2/bt2/native_btqueryexec.i new file mode 100644 index 00000000..b6af2a61 --- /dev/null +++ b/bindings/python/bt2/bt2/native_btqueryexec.i @@ -0,0 +1,49 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* Types */ +struct bt_query_executor; + +/* Query status */ +enum bt_query_status { + BT_QUERY_STATUS_OK = 0, + BT_QUERY_STATUS_AGAIN = 11, + BT_QUERY_STATUS_EXECUTOR_CANCELED = 125, + BT_QUERY_STATUS_ERROR = -1, + BT_QUERY_STATUS_INVALID = -22, + BT_QUERY_STATUS_INVALID_OBJECT = -23, + BT_QUERY_STATUS_INVALID_PARAMS = -24, + BT_QUERY_STATUS_NOMEM = -12, +}; + +/* Functions */ +struct bt_query_executor *bt_query_executor_create(void); +enum bt_query_status bt_query_executor_query( + struct bt_query_executor *query_executor, + struct bt_component_class *component_class, + const char *object, struct bt_value *params, + struct bt_value **BTOUTVALUE); +enum bt_query_status bt_query_executor_cancel( + struct bt_query_executor *query_executor); +int bt_query_executor_is_canceled(struct bt_query_executor *query_executor); diff --git a/bindings/python/bt2/bt2/native_btversion.i b/bindings/python/bt2/bt2/native_btversion.i index e368b9b3..3d99e7d2 100644 --- a/bindings/python/bt2/bt2/native_btversion.i +++ b/bindings/python/bt2/bt2/native_btversion.i @@ -22,10 +22,6 @@ * THE SOFTWARE. */ -%{ -#include -%} - /* Version functions */ int bt_version_get_major(void); int bt_version_get_minor(void); diff --git a/bindings/python/bt2/bt2/query_executor.py b/bindings/python/bt2/bt2/query_executor.py new file mode 100644 index 00000000..00644166 --- /dev/null +++ b/bindings/python/bt2/bt2/query_executor.py @@ -0,0 +1,96 @@ +# The MIT License (MIT) +# +# Copyright (c) 2017 Philippe Proulx +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from bt2 import native_bt, object, utils +import bt2.component +import bt2 + + +class QueryExecutor(object._Object): + def _handle_status(self, status, gen_error_msg): + if status == native_bt.QUERY_STATUS_AGAIN: + raise bt2.TryAgain + elif status == native_bt.QUERY_STATUS_EXECUTOR_CANCELED: + raise bt2.QueryExecutorCanceled + elif status == native_bt.QUERY_STATUS_INVALID_OBJECT: + raise bt2.InvalidQueryObject + elif status == native_bt.QUERY_STATUS_INVALID_PARAMS: + raise bt2.InvalidQueryParams + elif status < 0: + raise bt2.Error(gen_error_msg) + + def __init__(self): + ptr = native_bt.query_executor_create() + + if ptr is None: + raise bt2.CreationError('cannot create query executor object') + + super().__init__(ptr) + + def cancel(self): + status = native_bt.query_executor_cancel(self._ptr) + self._handle_status(status, 'cannot cancel query executor object') + + @property + def is_canceled(self): + is_canceled = native_bt.query_executor_is_canceled(self._ptr) + assert(is_canceled >= 0) + return is_canceled > 0 + + def query(self, component_class, object, params=None): + if not isinstance(component_class, bt2.component._GenericComponentClass): + err = False + + try: + if not issubclass(component_class, bt2.component._UserComponent): + err = True + except TypeError: + err = True + + if err: + o = component_class + raise TypeError("'{}' is not a component class object".format(o)) + + utils._check_str(object) + + if params is None: + params_ptr = native_bt.value_null + else: + params = bt2.create_value(params) + params_ptr = params._ptr + + if isinstance(component_class, bt2.component._GenericComponentClass): + cc_ptr = component_class._ptr + else: + cc_ptr = component_class._cc_ptr + + status, result_ptr = native_bt.query_executor_query(self._ptr, cc_ptr, + object, params_ptr) + self._handle_status(status, 'cannot query component class') + assert(result_ptr) + return bt2.values._create_from_ptr(result_ptr) + + def __eq__(self, other): + if type(other) is not type(self): + return False + + return self.addr == other.addr diff --git a/cli/babeltrace.c b/cli/babeltrace.c index 9eef511d..95b89c81 100644 --- a/cli/babeltrace.c +++ b/cli/babeltrace.c @@ -30,21 +30,8 @@ #include "logging.h" #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include #include #include @@ -85,6 +72,7 @@ static const char* log_level_env_var_names[] = { /* Application's processing graph (weak) */ static struct bt_graph *the_graph; +static struct bt_query_executor *the_query_executor; static bool canceled = false; GPtrArray *loaded_plugins; @@ -122,6 +110,10 @@ void signal_handler(int signum) bt_graph_cancel(the_graph); } + if (the_query_executor) { + bt_query_executor_cancel(the_query_executor); + } + canceled = true; } @@ -153,6 +145,116 @@ void fini_static_data(void) g_ptr_array_free(loaded_plugins, TRUE); } +static +int create_the_query_executor(void) +{ + int ret = 0; + + the_query_executor = bt_query_executor_create(); + if (!the_query_executor) { + BT_LOGE_STR("Cannot create a query executor."); + ret = -1; + } + + return ret; +} + +static +void destroy_the_query_executor(void) +{ + BT_PUT(the_query_executor); +} + +static +int query(struct bt_component_class *comp_cls, const char *obj, + struct bt_value *params, struct bt_value **user_result, + const char **fail_reason) +{ + struct bt_value *result = NULL; + enum bt_query_status status; + *fail_reason = "unknown error"; + int ret = 0; + + assert(fail_reason); + assert(user_result); + ret = create_the_query_executor(); + if (ret) { + /* create_the_query_executor() logs errors */ + goto end; + } + + if (canceled) { + BT_LOGI("Canceled by user before executing the query: " + "comp-cls-addr=%p, comp-cls-name=\"%s\", " + "query-obj=\"%s\"", comp_cls, + bt_component_class_get_name(comp_cls), obj); + *fail_reason = "canceled by user"; + goto error; + } + + while (true) { + status = bt_query_executor_query(the_query_executor, comp_cls, + obj, params, &result); + switch (status) { + case BT_QUERY_STATUS_OK: + goto ok; + case BT_QUERY_STATUS_AGAIN: + { + const uint64_t sleep_time_us = 100000; + + /* Wait 100 ms and retry */ + BT_LOGV("Got BT_QUERY_STATUS_AGAIN: sleeping: " + "time-us=%" PRIu64, sleep_time_us); + + if (usleep(sleep_time_us)) { + if (bt_query_executor_is_canceled(the_query_executor)) { + BT_LOGI("Query was canceled by user: " + "comp-cls-addr=%p, comp-cls-name=\"%s\", " + "query-obj=\"%s\"", comp_cls, + bt_component_class_get_name(comp_cls), + obj); + *fail_reason = "canceled by user"; + goto error; + } + } + + continue; + } + case BT_QUERY_STATUS_EXECUTOR_CANCELED: + *fail_reason = "canceled by user"; + goto error; + case BT_QUERY_STATUS_ERROR: + case BT_QUERY_STATUS_INVALID: + goto error; + case BT_QUERY_STATUS_INVALID_OBJECT: + *fail_reason = "invalid or unknown query object"; + goto error; + case BT_QUERY_STATUS_INVALID_PARAMS: + *fail_reason = "invalid query parameters"; + goto error; + case BT_QUERY_STATUS_NOMEM: + *fail_reason = "not enough memory"; + goto error; + default: + BT_LOGF("Unknown query status: status=%d", status); + abort(); + } + } + +ok: + *user_result = result; + result = NULL; + goto end; + +error: + ret = -1; + +end: + destroy_the_query_executor(); + bt_put(result); + return ret; +} + static struct bt_plugin *find_plugin(const char *name) { @@ -792,6 +894,7 @@ int cmd_query(struct bt_config *cfg) int ret = 0; struct bt_component_class *comp_cls = NULL; struct bt_value *results = NULL; + const char *fail_reason = NULL; comp_cls = find_component_class(cfg->cmd_data.query.cfg_component->plugin_name->str, cfg->cmd_data.query.cfg_component->comp_cls_name->str, @@ -815,35 +918,39 @@ int cmd_query(struct bt_config *cfg) goto end; } - results = bt_component_class_query(comp_cls, - cfg->cmd_data.query.object->str, - cfg->cmd_data.query.cfg_component->params); - if (!results) { - BT_LOGE("Failed to query component class: plugin-name=\"%s\", " - "comp-cls-name=\"%s\", comp-cls-type=%d " - "object=\"%s\"", - cfg->cmd_data.query.cfg_component->plugin_name->str, - cfg->cmd_data.query.cfg_component->comp_cls_name->str, - cfg->cmd_data.query.cfg_component->type, - cfg->cmd_data.query.object->str); - fprintf(stderr, "%s%sFailed to query info to %s", - bt_common_color_bold(), - bt_common_color_fg_red(), - bt_common_color_reset()); - print_plugin_comp_cls_opt(stderr, - cfg->cmd_data.query.cfg_component->plugin_name->str, - cfg->cmd_data.query.cfg_component->comp_cls_name->str, - cfg->cmd_data.query.cfg_component->type); - fprintf(stderr, "%s%s with object `%s`%s\n", - bt_common_color_bold(), - bt_common_color_fg_red(), - cfg->cmd_data.query.object->str, - bt_common_color_reset()); - ret = -1; - goto end; + ret = query(comp_cls, cfg->cmd_data.query.object->str, + cfg->cmd_data.query.cfg_component->params, &results, + &fail_reason); + if (ret) { + goto failed; } print_value(stdout, results, 0); + goto end; + +failed: + BT_LOGE("Failed to query component class: %s: plugin-name=\"%s\", " + "comp-cls-name=\"%s\", comp-cls-type=%d " + "object=\"%s\"", fail_reason, + cfg->cmd_data.query.cfg_component->plugin_name->str, + cfg->cmd_data.query.cfg_component->comp_cls_name->str, + cfg->cmd_data.query.cfg_component->type, + cfg->cmd_data.query.object->str); + fprintf(stderr, "%s%sFailed to query info to %s", + bt_common_color_bold(), + bt_common_color_fg_red(), + bt_common_color_reset()); + print_plugin_comp_cls_opt(stderr, + cfg->cmd_data.query.cfg_component->plugin_name->str, + cfg->cmd_data.query.cfg_component->comp_cls_name->str, + cfg->cmd_data.query.cfg_component->type); + fprintf(stderr, "%s%s with object `%s`: %s%s\n", + bt_common_color_bold(), + bt_common_color_fg_red(), + cfg->cmd_data.query.object->str, + fail_reason, + bt_common_color_reset()); + ret = -1; end: bt_put(comp_cls); @@ -1046,6 +1153,7 @@ int cmd_print_lttng_live_sessions(struct bt_config *cfg) static const enum bt_component_class_type comp_cls_type = BT_COMPONENT_CLASS_TYPE_SOURCE; int64_t array_size, i; + const char *fail_reason = NULL; assert(cfg->cmd_data.print_lttng_live_sessions.url); comp_cls = find_component_class(plugin_name, comp_cls_name, @@ -1076,15 +1184,9 @@ int cmd_print_lttng_live_sessions(struct bt_config *cfg) goto error; } - results = bt_component_class_query(comp_cls, "sessions", - params); - if (!results) { - BT_LOGE_STR("Failed to query for sessions."); - fprintf(stderr, "%s%sFailed to request sessions%s\n", - bt_common_color_bold(), - bt_common_color_fg_red(), - bt_common_color_reset()); - goto error; + ret = query(comp_cls, "sessions", params, &results, &fail_reason); + if (ret) { + goto failed; } if (!bt_value_is_array(results)) { @@ -1153,6 +1255,20 @@ int cmd_print_lttng_live_sessions(struct bt_config *cfg) BT_PUT(map); } + + goto end; + +failed: + BT_LOGE("Failed to query for sessions: %s", fail_reason); + fprintf(stderr, "%s%sFailed to request sessions: %s%s\n", + bt_common_color_bold(), + bt_common_color_fg_red(), + fail_reason, + bt_common_color_reset()); + +error: + ret = -1; + end: bt_put(v); bt_put(map); @@ -1160,10 +1276,6 @@ end: bt_put(params); bt_put(comp_cls); return 0; - -error: - ret = -1; - goto end; } static @@ -1179,6 +1291,7 @@ int cmd_print_ctf_metadata(struct bt_config *cfg) static const char * const comp_cls_name = "fs"; static const enum bt_component_class_type comp_cls_type = BT_COMPONENT_CLASS_TYPE_SOURCE; + const char *fail_reason = NULL; assert(cfg->cmd_data.print_ctf_metadata.path); comp_cls = find_component_class(plugin_name, comp_cls_name, @@ -1212,16 +1325,9 @@ int cmd_print_ctf_metadata(struct bt_config *cfg) goto end; } - results = bt_component_class_query(comp_cls, "metadata-info", - params); - if (!results) { - ret = -1; - BT_LOGE_STR("Failed to query for metadata info."); - fprintf(stderr, "%s%sFailed to request metadata info%s\n", - bt_common_color_bold(), - bt_common_color_fg_red(), - bt_common_color_reset()); - goto end; + ret = query(comp_cls, "metadata-info", params, &results, &fail_reason); + if (ret) { + goto failed; } metadata_text_value = bt_value_map_get(results, "text"); @@ -1234,8 +1340,19 @@ int cmd_print_ctf_metadata(struct bt_config *cfg) ret = bt_value_string_get(metadata_text_value, &metadata_text); assert(ret == 0); printf("%s\n", metadata_text); + goto end; + +failed: + ret = -1; + BT_LOGE("Failed to query for metadata info: %s", fail_reason); + fprintf(stderr, "%s%sFailed to request metadata info: %s%s\n", + bt_common_color_bold(), + bt_common_color_fg_red(), + fail_reason, + bt_common_color_reset()); end: + destroy_the_query_executor(); bt_put(results); bt_put(params); bt_put(metadata_text_value); @@ -1943,6 +2060,7 @@ int set_stream_intersections(struct cmd_run_ctx *ctx, struct bt_value *stream_info = NULL; struct port_id *port_id = NULL; struct trace_range *trace_range = NULL; + const char *fail_reason = NULL; component_path_value = bt_value_map_get(cfg_comp->params, "path"); if (!bt_value_is_string(component_path_value)) { @@ -1974,10 +2092,11 @@ int set_stream_intersections(struct cmd_run_ctx *ctx, goto error; } - query_result = bt_component_class_query(comp_cls, "trace-info", - query_params); - if (!query_result) { - BT_LOGD("Component class \'%s\' does not support the \'trace-info\' query.", + ret = query(comp_cls, "trace-info", query_params, &query_result, + &fail_reason); + if (ret) { + BT_LOGD("Component class does not support the `trace-info` query: %s: " + "comp-class-name=\"%s\"", fail_reason, bt_component_class_get_name(comp_cls)); ret = -1; goto error; diff --git a/include/Makefile.am b/include/Makefile.am index a0dd7080..e115d36b 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -72,6 +72,7 @@ babeltracegraphinclude_HEADERS = \ babeltrace/graph/notification-packet.h \ babeltrace/graph/notification-stream.h \ babeltrace/graph/port.h \ + babeltrace/graph/query-executor.h \ babeltrace/graph/private-component-filter.h \ babeltrace/graph/private-component-sink.h \ babeltrace/graph/private-component-source.h \ @@ -135,6 +136,7 @@ noinst_HEADERS = \ babeltrace/graph/notification-packet-internal.h \ babeltrace/graph/notification-stream-internal.h \ babeltrace/graph/port-internal.h \ + babeltrace/graph/query-executor-internal.h \ babeltrace/lib-logging-internal.h \ babeltrace/list-internal.h \ babeltrace/logging-internal.h \ diff --git a/include/babeltrace/babeltrace.h b/include/babeltrace/babeltrace.h index 31fcb9aa..02c83dc1 100644 --- a/include/babeltrace/babeltrace.h +++ b/include/babeltrace/babeltrace.h @@ -93,5 +93,6 @@ #include #include #include +#include #endif /* BABELTRACE_BABELTRACE_H */ diff --git a/include/babeltrace/graph/component-class.h b/include/babeltrace/graph/component-class.h index a4d1e7a2..466bd549 100644 --- a/include/babeltrace/graph/component-class.h +++ b/include/babeltrace/graph/component-class.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #ifdef __cplusplus @@ -41,6 +42,7 @@ struct bt_private_port; struct bt_port; struct bt_value; struct bt_private_notification_iterator; +struct bt_query_executor; /** * Component class type. @@ -63,6 +65,11 @@ struct bt_notification_iterator_next_return { enum bt_notification_iterator_status status; }; +struct bt_component_class_query_return { + struct bt_value *result; + enum bt_query_status status; +}; + typedef enum bt_component_status (*bt_component_class_init_method)( struct bt_private_component *private_component, struct bt_value *params, void *init_method_data); @@ -86,8 +93,9 @@ typedef enum bt_notification_iterator_status struct bt_private_notification_iterator *private_notification_iterator, int64_t time); -typedef struct bt_value *(*bt_component_class_query_method)( +typedef struct bt_component_class_query_return (*bt_component_class_query_method)( struct bt_component_class *component_class, + struct bt_query_executor *query_executor, const char *object, struct bt_value *params); typedef enum bt_component_status (*bt_component_class_accept_port_connection_method)( @@ -163,10 +171,6 @@ extern const char *bt_component_class_get_description( extern const char *bt_component_class_get_help( struct bt_component_class *component_class); -extern struct bt_value *bt_component_class_query( - struct bt_component_class *component_class, - const char *object, struct bt_value *params); - /** * Get a component class' type. * diff --git a/include/babeltrace/graph/query-executor-internal.h b/include/babeltrace/graph/query-executor-internal.h new file mode 100644 index 00000000..055f7a39 --- /dev/null +++ b/include/babeltrace/graph/query-executor-internal.h @@ -0,0 +1,58 @@ +#ifndef BABELTRACE_GRAPH_QUERY_EXECUTOR_INTERNAL_H +#define BABELTRACE_GRAPH_QUERY_EXECUTOR_INTERNAL_H + +/* + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +struct bt_query_executor { + struct bt_object base; + bt_bool canceled; +}; + +static inline const char *bt_query_status_string(enum bt_query_status status) +{ + switch (status) { + case BT_QUERY_STATUS_OK: + return "BT_QUERY_STATUS_OK"; + case BT_QUERY_STATUS_AGAIN: + return "BT_QUERY_STATUS_AGAIN"; + case BT_QUERY_STATUS_EXECUTOR_CANCELED: + return "BT_QUERY_STATUS_EXECUTOR_CANCELED"; + case BT_QUERY_STATUS_ERROR: + return "BT_QUERY_STATUS_ERROR"; + case BT_QUERY_STATUS_INVALID: + return "BT_QUERY_STATUS_INVALID"; + case BT_QUERY_STATUS_INVALID_OBJECT: + return "BT_QUERY_STATUS_INVALID_OBJECT"; + case BT_QUERY_STATUS_INVALID_PARAMS: + return "BT_QUERY_STATUS_INVALID_PARAMS"; + case BT_QUERY_STATUS_NOMEM: + return "BT_QUERY_STATUS_NOMEM"; + default: + return "(unknown)"; + } +}; + +#endif /* BABELTRACE_GRAPH_QUERY_EXECUTOR_INTERNAL_H */ diff --git a/include/babeltrace/graph/query-executor.h b/include/babeltrace/graph/query-executor.h new file mode 100644 index 00000000..f2187dd6 --- /dev/null +++ b/include/babeltrace/graph/query-executor.h @@ -0,0 +1,70 @@ +#ifndef BABELTRACE_GRAPH_QUERY_EXECUTOR_H +#define BABELTRACE_GRAPH_QUERY_EXECUTOR_H + +/* + * BabelTrace - Babeltrace Component Connection Interface + * + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct bt_value; +struct bt_query_executor; +struct bt_component_class; + +enum bt_query_status { + BT_QUERY_STATUS_OK = 0, + BT_QUERY_STATUS_AGAIN = 11, + BT_QUERY_STATUS_EXECUTOR_CANCELED = 125, + BT_QUERY_STATUS_ERROR = -1, + BT_QUERY_STATUS_INVALID = -22, + BT_QUERY_STATUS_INVALID_OBJECT = -23, + BT_QUERY_STATUS_INVALID_PARAMS = -24, + BT_QUERY_STATUS_NOMEM = -12, +}; + +extern +struct bt_query_executor *bt_query_executor_create(void); + +extern +enum bt_query_status bt_query_executor_query( + struct bt_query_executor *query_executor, + struct bt_component_class *component_class, + const char *object, struct bt_value *params, + struct bt_value **result); + +extern +enum bt_query_status bt_query_executor_cancel( + struct bt_query_executor *query_executor); + +extern +bt_bool bt_query_executor_is_canceled(struct bt_query_executor *query_executor); + +#ifdef __cplusplus +} +#endif + +#endif /* BABELTRACE_GRAPH_QUERY_EXECUTOR_H */ diff --git a/lib/graph/Makefile.am b/lib/graph/Makefile.am index 69990943..4e608569 100644 --- a/lib/graph/Makefile.am +++ b/lib/graph/Makefile.am @@ -14,7 +14,8 @@ libgraph_la_SOURCES = \ sink.c \ filter.c \ iterator.c \ - component-class-sink-colander.c + component-class-sink-colander.c \ + query-executor.c libgraph_la_LIBADD = \ notification/libgraph-notification.la diff --git a/lib/graph/component-class.c b/lib/graph/component-class.c index a899416d..53435750 100644 --- a/lib/graph/component-class.c +++ b/lib/graph/component-class.c @@ -969,49 +969,3 @@ int bt_component_class_freeze( end: return ret; } - -struct bt_value *bt_component_class_query( - struct bt_component_class *component_class, - const char *object, struct bt_value *params) -{ - struct bt_value *results = NULL; - - if (!component_class) { - BT_LOGW_STR("Invalid parameter: component class is NULL."); - goto end; - } - - if (!object) { - BT_LOGW_STR("Invalid parameter: object string is NULL."); - goto end; - } - - if (!params) { - BT_LOGW_STR("Invalid parameter: parameters value is NULL."); - goto end; - } - - if (!component_class->methods.query) { - /* Not an error: nothing to query */ - BT_LOGD("Component class has no registered query method: " - "addr=%p, name=\"%s\", type=%s", - component_class, - bt_component_class_get_name(component_class), - bt_component_class_type_string(component_class->type)); - goto end; - } - - BT_LOGD("Calling user's query method: " - "comp-class-addr=%p, comp-class-name=\"%s\", comp-class-type=%s" - "object=\"%s\", params-addr=%p", - component_class, - bt_component_class_get_name(component_class), - bt_component_class_type_string(component_class->type), - object, params); - results = component_class->methods.query(component_class, - object, params); - BT_LOGD("User method returned: results-addr=%p", results); - -end: - return results; -} diff --git a/lib/graph/graph.c b/lib/graph/graph.c index 7b013d35..0c18ef62 100644 --- a/lib/graph/graph.c +++ b/lib/graph/graph.c @@ -808,7 +808,7 @@ void bt_graph_notify_ports_disconnected(struct bt_graph *graph, } } -extern enum bt_graph_status bt_graph_cancel(struct bt_graph *graph) +enum bt_graph_status bt_graph_cancel(struct bt_graph *graph) { enum bt_graph_status ret = BT_GRAPH_STATUS_OK; @@ -825,9 +825,19 @@ end: return ret; } -extern bt_bool bt_graph_is_canceled(struct bt_graph *graph) +bt_bool bt_graph_is_canceled(struct bt_graph *graph) { - return graph ? graph->canceled : BT_FALSE; + bt_bool canceled = BT_FALSE; + + if (!graph) { + BT_LOGW_STR("Invalid parameter: graph is NULL."); + goto end; + } + + canceled = graph->canceled; + +end: + return canceled; } BT_HIDDEN diff --git a/lib/graph/query-executor.c b/lib/graph/query-executor.c new file mode 100644 index 00000000..f1cd95b6 --- /dev/null +++ b/lib/graph/query-executor.c @@ -0,0 +1,204 @@ +/* + * Copyright 2017 Philippe Proulx + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#define BT_LOG_TAG "QUERY-EXECUTOR" +#include + +#include +#include +#include +#include +#include +#include +#include + +static +void bt_query_executor_destroy(struct bt_object *obj) +{ + struct bt_query_executor *query_exec = + container_of(obj, struct bt_query_executor, base); + + BT_LOGD("Destroying port: addr=%p", query_exec); + g_free(query_exec); +} + +struct bt_query_executor *bt_query_executor_create(void) +{ + struct bt_query_executor *query_exec; + + BT_LOGD_STR("Creating query executor."); + query_exec = g_new0(struct bt_query_executor, 1); + if (!query_exec) { + BT_LOGE_STR("Failed to allocate one query executor."); + goto end; + } + + bt_object_init(query_exec, bt_query_executor_destroy); + BT_LOGD("Created query executor: addr=%p", query_exec); + +end: + return query_exec; +} + +enum bt_query_status bt_query_executor_query( + struct bt_query_executor *query_exec, + struct bt_component_class *component_class, + const char *object, struct bt_value *params, + struct bt_value **user_result) +{ + struct bt_component_class_query_return ret = { + .result = NULL, + .status = BT_QUERY_STATUS_OK, + }; + + if (!query_exec) { + BT_LOGW_STR("Invalid parameter: query executor is NULL."); + ret.status = BT_QUERY_STATUS_INVALID; + goto end; + } + + if (query_exec->canceled) { + BT_LOGW_STR("Invalid parameter: query executor is canceled."); + ret.status = BT_QUERY_STATUS_EXECUTOR_CANCELED; + goto end; + } + + if (!component_class) { + BT_LOGW_STR("Invalid parameter: component class is NULL."); + ret.status = BT_QUERY_STATUS_INVALID; + goto end; + } + + if (!object) { + BT_LOGW_STR("Invalid parameter: object string is NULL."); + ret.status = BT_QUERY_STATUS_INVALID; + goto end; + } + + if (!params) { + params = bt_value_null; + } + + if (!component_class->methods.query) { + /* Not an error: nothing to query */ + BT_LOGD("Component class has no registered query method: " + "addr=%p, name=\"%s\", type=%s", + component_class, + bt_component_class_get_name(component_class), + bt_component_class_type_string(component_class->type)); + ret.status = BT_QUERY_STATUS_ERROR; + goto end; + } + + BT_LOGD("Calling user's query method: " + "query-exec-addr=%p, comp-class-addr=%p, " + "comp-class-name=\"%s\", comp-class-type=%s, " + "object=\"%s\", params-addr=%p", + query_exec, component_class, + bt_component_class_get_name(component_class), + bt_component_class_type_string(component_class->type), + object, params); + ret = component_class->methods.query(component_class, query_exec, + object, params); + BT_LOGD("User method returned: status=%s, result-addr=%p", + bt_query_status_string(ret.status), ret.result); + if (query_exec->canceled) { + BT_PUT(ret.result); + ret.status = BT_QUERY_STATUS_EXECUTOR_CANCELED; + goto end; + } else { + if (ret.status == BT_QUERY_STATUS_EXECUTOR_CANCELED) { + /* + * The user cannot decide that the executor is + * canceled if it's not. + */ + BT_PUT(ret.result); + ret.status = BT_QUERY_STATUS_ERROR; + goto end; + } + } + + switch (ret.status) { + case BT_QUERY_STATUS_INVALID: + /* + * This is reserved for invalid parameters passed to + * this function. + */ + BT_PUT(ret.result); + ret.status = BT_QUERY_STATUS_ERROR; + break; + case BT_QUERY_STATUS_OK: + if (!ret.result) { + ret.result = bt_value_null; + } + break; + default: + if (ret.result) { + BT_LOGW("User method did not return BT_QUERY_STATUS_OK, but result is not NULL: " + "status=%s, result-addr=%p", + bt_query_status_string(ret.status), ret.result); + BT_PUT(ret.result); + } + } + +end: + if (user_result) { + *user_result = ret.result; + ret.result = NULL; + } + + bt_put(ret.result); + return ret.status; +} + +enum bt_query_status bt_query_executor_cancel( + struct bt_query_executor *query_exec) +{ + enum bt_query_status ret = BT_QUERY_STATUS_OK; + + if (!query_exec) { + BT_LOGW_STR("Invalid parameter: query executor is NULL."); + ret = BT_QUERY_STATUS_INVALID; + goto end; + } + + query_exec->canceled = BT_TRUE; + BT_LOGV("Canceled query executor: addr=%p", query_exec); + +end: + return ret; +} + +bt_bool bt_query_executor_is_canceled(struct bt_query_executor *query_exec) +{ + bt_bool canceled = BT_FALSE; + + if (!query_exec) { + BT_LOGW_STR("Invalid parameter: query executor is NULL."); + goto end; + } + + canceled = query_exec->canceled; + +end: + return canceled; +} diff --git a/plugins/ctf/fs-src/fs.c b/plugins/ctf/fs-src/fs.c index d2c0ede3..e36a70c4 100644 --- a/plugins/ctf/fs-src/fs.c +++ b/plugins/ctf/fs-src/fs.c @@ -1365,19 +1365,25 @@ enum bt_component_status ctf_fs_init(struct bt_private_component *priv_comp, } BT_HIDDEN -struct bt_value *ctf_fs_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return ctf_fs_query( + struct bt_component_class *comp_class, + struct bt_query_executor *query_exec, const char *object, struct bt_value *params) { - struct bt_value *result = NULL; + struct bt_component_class_query_return ret = { + .result = NULL, + .status = BT_QUERY_STATUS_OK, + }; if (!strcmp(object, "metadata-info")) { - result = metadata_info_query(comp_class, params); + ret = metadata_info_query(comp_class, params); } else if (!strcmp(object, "trace-info")) { - result = trace_info_query(comp_class, params); + ret = trace_info_query(comp_class, params); } else { BT_LOGE("Unknown query object `%s`", object); + ret.status = BT_QUERY_STATUS_INVALID_OBJECT; goto end; } end: - return result; + return ret; } diff --git a/plugins/ctf/fs-src/fs.h b/plugins/ctf/fs-src/fs.h index d832495d..46fa9d8c 100644 --- a/plugins/ctf/fs-src/fs.h +++ b/plugins/ctf/fs-src/fs.h @@ -142,7 +142,9 @@ BT_HIDDEN void ctf_fs_finalize(struct bt_private_component *component); BT_HIDDEN -struct bt_value *ctf_fs_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return ctf_fs_query( + struct bt_component_class *comp_class, + struct bt_query_executor *query_exec, const char *object, struct bt_value *params); BT_HIDDEN diff --git a/plugins/ctf/fs-src/query.c b/plugins/ctf/fs-src/query.c index b037f609..696d3d43 100644 --- a/plugins/ctf/fs-src/query.c +++ b/plugins/ctf/fs-src/query.c @@ -46,10 +46,15 @@ struct range { }; BT_HIDDEN -struct bt_value *metadata_info_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return metadata_info_query( + struct bt_component_class *comp_class, struct bt_value *params) { - struct bt_value *result = NULL; + struct bt_component_class_query_return query_ret = { + .result = NULL, + .status = BT_QUERY_STATUS_OK, + }; + struct bt_value *path_value = NULL; char *metadata_text = NULL; FILE *metadata_fp = NULL; @@ -59,13 +64,15 @@ struct bt_value *metadata_info_query(struct bt_component_class *comp_class, const char *path; bool is_packetized; - result = bt_value_map_create(); - if (!result) { + query_ret.result = bt_value_map_create(); + if (!query_ret.result) { + query_ret.status = BT_QUERY_STATUS_NOMEM; goto error; } if (!bt_value_is_map(params)) { BT_LOGE_STR("Query parameters is not a map value object."); + query_ret.status = BT_QUERY_STATUS_INVALID_PARAMS; goto error; } @@ -73,6 +80,7 @@ struct bt_value *metadata_info_query(struct bt_component_class *comp_class, ret = bt_value_string_get(path_value, &path); if (ret) { BT_LOGE_STR("Cannot get `path` string parameter."); + query_ret.status = BT_QUERY_STATUS_INVALID_PARAMS; goto error; } @@ -138,14 +146,14 @@ struct bt_value *metadata_info_query(struct bt_component_class *comp_class, g_string_append(g_metadata_text, metadata_text); - ret = bt_value_map_insert_string(result, "text", + ret = bt_value_map_insert_string(query_ret.result, "text", g_metadata_text->str); if (ret) { BT_LOGE_STR("Cannot insert metadata text into query result."); goto error; } - ret = bt_value_map_insert_bool(result, "is-packetized", + ret = bt_value_map_insert_bool(query_ret.result, "is-packetized", is_packetized); if (ret) { BT_LOGE_STR("Cannot insert \"is-packetized\" attribute into query result."); @@ -155,7 +163,11 @@ struct bt_value *metadata_info_query(struct bt_component_class *comp_class, goto end; error: - BT_PUT(result); + BT_PUT(query_ret.result); + + if (query_ret.status >= 0) { + query_ret.status = BT_QUERY_STATUS_ERROR; + } end: bt_put(path_value); @@ -168,7 +180,8 @@ end: if (metadata_fp) { fclose(metadata_fp); } - return result; + + return query_ret; } static @@ -445,10 +458,15 @@ end: } BT_HIDDEN -struct bt_value *trace_info_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return trace_info_query( + struct bt_component_class *comp_class, struct bt_value *params) { - struct bt_value *trace_infos = NULL; + struct bt_component_class_query_return query_ret = { + .result = NULL, + .status = BT_QUERY_STATUS_OK, + }; + struct bt_value *path_value = NULL; int ret = 0; const char *path = NULL; @@ -460,6 +478,7 @@ struct bt_value *trace_info_query(struct bt_component_class *comp_class, if (!bt_value_is_map(params)) { BT_LOGE("Query parameters is not a map value object."); + query_ret.status = BT_QUERY_STATUS_INVALID_PARAMS; goto error; } @@ -467,6 +486,7 @@ struct bt_value *trace_info_query(struct bt_component_class *comp_class, ret = bt_value_string_get(path_value, &path); if (ret) { BT_LOGE("Cannot get `path` string parameter."); + query_ret.status = BT_QUERY_STATUS_INVALID_PARAMS; goto error; } @@ -489,8 +509,9 @@ struct bt_value *trace_info_query(struct bt_component_class *comp_class, goto error; } - trace_infos = bt_value_array_create(); - if (!trace_infos) { + query_ret.result = bt_value_array_create(); + if (!query_ret.result) { + query_ret.status = BT_QUERY_STATUS_NOMEM; goto error; } @@ -516,7 +537,7 @@ struct bt_value *trace_info_query(struct bt_component_class *comp_class, goto error; } - status = bt_value_array_append(trace_infos, trace_info); + status = bt_value_array_append(query_ret.result, trace_info); bt_put(trace_info); if (status != BT_VALUE_STATUS_OK) { goto error; @@ -526,7 +547,12 @@ struct bt_value *trace_info_query(struct bt_component_class *comp_class, goto end; error: - BT_PUT(trace_infos); + BT_PUT(query_ret.result); + + if (query_ret.status >= 0) { + query_ret.status = BT_QUERY_STATUS_ERROR; + } + end: if (normalized_path) { g_string_free(normalized_path, TRUE); @@ -549,5 +575,5 @@ end: } /* "path" becomes invalid with the release of path_value. */ bt_put(path_value); - return trace_infos; + return query_ret; } diff --git a/plugins/ctf/fs-src/query.h b/plugins/ctf/fs-src/query.h index 996a0f1a..fff036c6 100644 --- a/plugins/ctf/fs-src/query.h +++ b/plugins/ctf/fs-src/query.h @@ -30,11 +30,13 @@ #include BT_HIDDEN -struct bt_value *metadata_info_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return metadata_info_query( + struct bt_component_class *comp_class, struct bt_value *params); BT_HIDDEN -struct bt_value *trace_info_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return trace_info_query( + struct bt_component_class *comp_class, struct bt_value *params); #endif /* BABELTRACE_PLUGIN_CTF_FS_QUERY_H */ diff --git a/plugins/ctf/lttng-live/lttng-live-internal.h b/plugins/ctf/lttng-live/lttng-live-internal.h index 8b0af2d0..bf94314e 100644 --- a/plugins/ctf/lttng-live/lttng-live-internal.h +++ b/plugins/ctf/lttng-live/lttng-live-internal.h @@ -215,7 +215,9 @@ enum bt_ctf_lttng_live_iterator_status { enum bt_component_status lttng_live_component_init(struct bt_private_component *source, struct bt_value *params, void *init_method_data); -struct bt_value *lttng_live_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return lttng_live_query( + struct bt_component_class *comp_class, + struct bt_query_executor *query_exec, const char *object, struct bt_value *params); void lttng_live_component_finalize(struct bt_private_component *component); diff --git a/plugins/ctf/lttng-live/lttng-live.c b/plugins/ctf/lttng-live/lttng-live.c index d89f3723..c3e7b641 100644 --- a/plugins/ctf/lttng-live/lttng-live.c +++ b/plugins/ctf/lttng-live/lttng-live.c @@ -961,22 +961,30 @@ error: } static -struct bt_value *lttng_live_query_list_sessions(struct bt_component_class *comp_class, +struct bt_component_class_query_return lttng_live_query_list_sessions( + struct bt_component_class *comp_class, + struct bt_query_executor *query_exec, struct bt_value *params) { + struct bt_component_class_query_return query_ret = { + .result = NULL, + .status = BT_QUERY_STATUS_OK, + }; + struct bt_value *url_value = NULL; - struct bt_value *results = NULL; const char *url; struct bt_live_viewer_connection *viewer_connection = NULL; url_value = bt_value_map_get(params, "url"); if (!url_value || bt_value_is_null(url_value) || !bt_value_is_string(url_value)) { BT_LOGW("Mandatory \"url\" parameter missing"); + query_ret.status = BT_QUERY_STATUS_INVALID_PARAMS; goto error; } if (bt_value_string_get(url_value, &url) != BT_VALUE_STATUS_OK) { BT_LOGW("\"url\" parameter is required to be a string value"); + query_ret.status = BT_QUERY_STATUS_INVALID_PARAMS; goto error; } @@ -985,28 +993,47 @@ struct bt_value *lttng_live_query_list_sessions(struct bt_component_class *comp_ goto error; } - results = bt_live_viewer_connection_list_sessions(viewer_connection); + query_ret.result = + bt_live_viewer_connection_list_sessions(viewer_connection); + if (!query_ret.result) { + goto error; + } + goto end; + error: - BT_PUT(results); + BT_PUT(query_ret.result); + + if (query_ret.status >= 0) { + query_ret.status = BT_QUERY_STATUS_ERROR; + } + end: if (viewer_connection) { bt_live_viewer_connection_destroy(viewer_connection); } BT_PUT(url_value); - return results; + return query_ret; } BT_HIDDEN -struct bt_value *lttng_live_query(struct bt_component_class *comp_class, +struct bt_component_class_query_return lttng_live_query( + struct bt_component_class *comp_class, + struct bt_query_executor *query_exec, const char *object, struct bt_value *params) { + struct bt_component_class_query_return ret = { + .result = NULL, + .status = BT_QUERY_STATUS_OK, + }; + if (strcmp(object, "sessions") == 0) { return lttng_live_query_list_sessions(comp_class, - params); + query_exec, params); } BT_LOGW("Unknown query object `%s`", object); - return NULL; + ret.status = BT_QUERY_STATUS_INVALID_OBJECT; + return ret; } static diff --git a/tests/bindings/python/bt2/test_component_class.py b/tests/bindings/python/bt2/test_component_class.py index a8160c33..d29f22f7 100644 --- a/tests/bindings/python/bt2/test_component_class.py +++ b/tests/bindings/python/bt2/test_component_class.py @@ -168,7 +168,7 @@ class UserComponentClassTestCase(unittest.TestCase): pass with self.assertRaises(bt2.Error): - MySink.query('obj', 23) + bt2.QueryExecutor().query(MySink, 'obj', 23) def test_query_raises(self): class MySink(bt2._UserSinkComponent): @@ -176,11 +176,11 @@ class UserComponentClassTestCase(unittest.TestCase): pass @classmethod - def _query(cls, obj, params): + def _query(cls, query_exec, obj, params): raise ValueError with self.assertRaises(bt2.Error): - MySink.query('obj', 23) + bt2.QueryExecutor().query(MySink, 'obj', 23) def test_query_wrong_return_type(self): class MySink(bt2._UserSinkComponent): @@ -188,11 +188,11 @@ class UserComponentClassTestCase(unittest.TestCase): pass @classmethod - def _query(cls, obj, params): + def _query(cls, query_exec, obj, params): return ... with self.assertRaises(bt2.Error): - MySink.query('obj', 23) + bt2.QueryExecutor().query(MySink, 'obj', 23) def test_query_params_none(self): class MySink(bt2._UserSinkComponent): @@ -200,14 +200,14 @@ class UserComponentClassTestCase(unittest.TestCase): pass @classmethod - def _query(cls, obj, params): + def _query(cls, query_exec, obj, params): nonlocal query_params query_params = params return None query_params = None params = None - res = MySink.query('obj', params) + res = bt2.QueryExecutor().query(MySink, 'obj', params) self.assertEqual(query_params, params) self.assertIsNone(res) del query_params @@ -218,14 +218,14 @@ class UserComponentClassTestCase(unittest.TestCase): pass @classmethod - def _query(cls, obj, params): + def _query(cls, query_exec, obj, params): nonlocal query_params query_params = params return 17.5 query_params = None params = ['coucou', 23, None] - res = MySink.query('obj', params) + res = bt2.QueryExecutor().query(MySink, 'obj', params) self.assertEqual(query_params, params) self.assertEqual(res, 17.5) del query_params @@ -236,7 +236,7 @@ class UserComponentClassTestCase(unittest.TestCase): pass @classmethod - def _query(cls, obj, params): + def _query(cls, query_exec, obj, params): nonlocal query_params query_params = params return { @@ -255,7 +255,7 @@ class UserComponentClassTestCase(unittest.TestCase): 'null': None, } - res = MySink.query('obj', params) + res = bt2.QueryExecutor().query(MySink, 'obj', params) self.assertEqual(query_params, params) self.assertEqual(res, { 'null': None, @@ -283,7 +283,7 @@ class GenericComponentClassTestCase(unittest.TestCase): pass @classmethod - def _query(cls, obj, params): + def _query(cls, query_exec, obj, params): return [obj, params, 23] self._py_comp_cls = MySink @@ -318,6 +318,7 @@ class GenericComponentClassTestCase(unittest.TestCase): self.assertEqual(self._py_comp_cls, self._comp_cls) def test_query(self): - res = self._comp_cls.query('an object', {'yes': 'no', 'book': -17}) + res = bt2.QueryExecutor().query(self._comp_cls, 'an object', + {'yes': 'no', 'book': -17}) expected = ['an object', {'yes': 'no', 'book': -17}, 23] self.assertEqual(res, expected) diff --git a/tests/bindings/python/bt2/test_query_executor.py b/tests/bindings/python/bt2/test_query_executor.py new file mode 100644 index 00000000..f0ee9d49 --- /dev/null +++ b/tests/bindings/python/bt2/test_query_executor.py @@ -0,0 +1,131 @@ +from bt2 import values +import unittest +import copy +import bt2 + + +class QueryExecutorTestCase(unittest.TestCase): + def test_query(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + nonlocal query_params + query_params = params + return { + 'null': None, + 'bt2': 'BT2', + } + + query_params = None + params = { + 'array': ['coucou', 23, None], + 'other_map': { + 'yes': 'yeah', + '19': 19, + 'minus 1.5': -1.5, + }, + 'null': None, + } + + res = bt2.QueryExecutor().query(MySink, 'obj', params) + self.assertEqual(query_params, params) + self.assertEqual(res, { + 'null': None, + 'bt2': 'BT2', + }) + del query_params + + def test_query_params_none(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + nonlocal query_params + query_params = params + + query_params = 23 + res = bt2.QueryExecutor().query(MySink, 'obj', None) + self.assertEqual(query_params, None) + del query_params + + def test_query_gen_error(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + raise ValueError + + with self.assertRaises(bt2.Error): + res = bt2.QueryExecutor().query(MySink, 'obj', [17, 23]) + + def test_query_invalid_object(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + raise bt2.InvalidQueryObject + + with self.assertRaises(bt2.InvalidQueryObject): + res = bt2.QueryExecutor().query(MySink, 'obj', [17, 23]) + + def test_query_invalid_params(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + raise bt2.InvalidQueryParams + + with self.assertRaises(bt2.InvalidQueryParams): + res = bt2.QueryExecutor().query(MySink, 'obj', [17, 23]) + + def test_query_try_again(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + raise bt2.TryAgain + + with self.assertRaises(bt2.TryAgain): + res = bt2.QueryExecutor().query(MySink, 'obj', [17, 23]) + + def test_cancel_no_query(self): + query_exec = bt2.QueryExecutor() + self.assertFalse(query_exec.is_canceled) + query_exec.cancel() + self.assertTrue(query_exec.is_canceled) + + def test_query_canceled(self): + class MySink(bt2._UserSinkComponent): + def _consume(self): + pass + + @classmethod + def _query(cls, query_exec, obj, params): + raise bt2.TryAgain + + query_exec = bt2.QueryExecutor() + query_exec.cancel() + + with self.assertRaises(bt2.QueryExecutorCanceled): + res = query_exec.query(MySink, 'obj', [17, 23]) + + def test_eq(self): + query_exec = bt2.QueryExecutor() + self.assertEqual(query_exec, query_exec) + + def test_eq_invalid(self): + query_exec = bt2.QueryExecutor() + self.assertNotEqual(query_exec, 23) diff --git a/tests/lib/test-plugin-plugins/sfs.c b/tests/lib/test-plugin-plugins/sfs.c index c17f078b..b66baead 100644 --- a/tests/lib/test-plugin-plugins/sfs.c +++ b/tests/lib/test-plugin-plugins/sfs.c @@ -57,19 +57,23 @@ static enum bt_notification_iterator_status dummy_iterator_seek_time_method( return BT_NOTIFICATION_ITERATOR_STATUS_OK; } -static struct bt_value *query_method( +static struct bt_component_class_query_return query_method( struct bt_component_class *component_class, + struct bt_query_executor *query_exec, const char *object, struct bt_value *params) { - int ret; - struct bt_value *results = bt_value_array_create(); + struct bt_component_class_query_return ret = { + .status = BT_QUERY_STATUS_OK, + .result = bt_value_array_create(), + }; + int iret; - assert(results); - ret = bt_value_array_append_string(results, object); - assert(ret == 0); - ret = bt_value_array_append(results, params); - assert(ret == 0); - return results; + assert(ret.result); + iret = bt_value_array_append_string(ret.result, object); + assert(iret == 0); + iret = bt_value_array_append(ret.result, params); + assert(iret == 0); + return ret; } BT_PLUGIN_MODULE(); diff --git a/tests/lib/test_plugin.c b/tests/lib/test_plugin.c index 2a6e7fd8..c367b90d 100644 --- a/tests/lib/test_plugin.c +++ b/tests/lib/test_plugin.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -168,7 +169,10 @@ static void test_sfs(const char *plugin_dir) const char *object_str; enum bt_value_status value_ret; enum bt_graph_status graph_ret; + struct bt_query_executor *query_exec = bt_query_executor_create(); + int ret; + assert(query_exec); assert(sfs_path); diag("sfs plugin test below"); @@ -214,15 +218,18 @@ static void test_sfs(const char *plugin_dir) "bt_plugin_get_component_class_by_name_and_type() does not find a component class given the wrong type"); params = bt_value_integer_create_init(23); assert(params); - ok (!bt_component_class_query(NULL, "get-something", params), - "bt_component_class_query() handles NULL (component class)"); - ok (!bt_component_class_query(filter_comp_class, NULL, params), - "bt_component_class_query() handles NULL (object)"); - ok (!bt_component_class_query(filter_comp_class, "get-something", NULL), - "bt_component_class_query() handles NULL (parameters)"); - results = bt_component_class_query(filter_comp_class, - "get-something", params); - ok(results, "bt_component_class_query() succeeds"); + ret = bt_query_executor_query(NULL, filter_comp_class, "object", + params, &results); + ok (ret, "bt_query_executor_query() handles NULL (query executor)"); + ret = bt_query_executor_query(query_exec, NULL, "object", + params, &results); + ok (ret, "bt_query_executor_query() handles NULL (component class)"); + ret = bt_query_executor_query(query_exec, filter_comp_class, NULL, + params, &results); + ok (ret, "bt_query_executor_query() handles NULL (object)"); + ret = bt_query_executor_query(query_exec, filter_comp_class, + "get-something", params, &results); + ok(ret == 0 && results, "bt_query_executor_query() succeeds"); assert(bt_value_is_array(results) && bt_value_array_size(results) == 2); object = bt_value_array_get(results, 0); assert(object && bt_value_is_string(object)); @@ -270,6 +277,7 @@ static void test_sfs(const char *plugin_dir) bt_put(res_params); bt_put(results); bt_put(params); + bt_put(query_exec); } static void test_create_all_from_dir(const char *plugin_dir) -- 2.34.1