2 * Copyright (c) 2019 EfficiOS Inc. and Linux Foundation
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 #define BT_LOG_TAG "CLI-CFG-SRC-AUTO-DISC"
24 #define BT_LOG_OUTPUT_LEVEL log_level
25 #include "logging/log.h"
28 #include "common/common.h"
30 #define BT_AUTODISC_LOG_AND_APPEND(_lvl, _fmt, ...) \
32 BT_LOG_WRITE(_lvl, BT_LOG_TAG, _fmt, ##__VA_ARGS__); \
33 (void) BT_CURRENT_THREAD_ERROR_APPEND_CAUSE_FROM_UNKNOWN( \
34 "Source auto-discovery", _fmt, ##__VA_ARGS__); \
37 #define BT_AUTODISC_LOGE_APPEND_CAUSE(_fmt, ...) \
38 BT_AUTODISC_LOG_AND_APPEND(BT_LOG_ERROR, _fmt, ##__VA_ARGS__)
40 /* Finalize and free a `struct auto_source_discovery_result`. */
43 void auto_source_discovery_result_destroy(struct auto_source_discovery_result
*res
)
47 bt_value_put_ref(res
->inputs
);
48 bt_value_put_ref(res
->original_input_indices
);
53 /* Allocate and initialize a `struct auto_source_discovery_result`. */
56 struct auto_source_discovery_result
*auto_source_discovery_result_create(
57 const char *plugin_name
, const char *source_cc_name
,
58 const char *group
, bt_logging_level log_level
)
60 struct auto_source_discovery_result
*res
;
62 res
= g_new0(struct auto_source_discovery_result
, 1);
64 BT_AUTODISC_LOGE_APPEND_CAUSE(
65 "Failed to allocate a auto_source_discovery_result structure.");
69 res
->plugin_name
= plugin_name
;
70 res
->source_cc_name
= source_cc_name
;
71 res
->group
= g_strdup(group
);
72 if (group
&& !res
->group
) {
73 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to allocate a string.");
77 res
->inputs
= bt_value_array_create();
79 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to allocate an array value.");
83 res
->original_input_indices
= bt_value_array_create();
84 if (!res
->original_input_indices
) {
85 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to allocate an array value.");
91 auto_source_discovery_result_destroy(res
);
98 /* Finalize a `struct auto_source_discovery`. */
100 void auto_source_discovery_fini(struct auto_source_discovery
*auto_disc
)
102 if (auto_disc
->results
) {
103 g_ptr_array_free(auto_disc
->results
, TRUE
);
107 /* Initialize an already allocated `struct auto_source_discovery`. */
109 int auto_source_discovery_init(struct auto_source_discovery
*auto_disc
)
113 auto_disc
->results
= g_ptr_array_new_with_free_func(
114 (GDestroyNotify
) auto_source_discovery_result_destroy
);
116 if (!auto_disc
->results
) {
124 auto_source_discovery_fini(auto_disc
);
133 const bt_value
*borrow_array_value_last_element_const(const bt_value
*array
)
135 uint64_t last_index
= bt_value_array_get_length(array
) - 1;
137 return bt_value_array_borrow_element_by_index_const(array
, last_index
);
141 * Assign `input` to source component class `source_cc_name` of plugin
142 * `plugin_name`, in the group with key `group`.
146 int auto_source_discovery_add(struct auto_source_discovery
*auto_disc
,
147 const char *plugin_name
,
148 const char *source_cc_name
,
151 uint64_t original_input_index
,
152 bt_logging_level log_level
)
155 bt_value_array_append_element_status append_status
;
158 struct auto_source_discovery_result
*res
= NULL
;
161 len
= auto_disc
->results
->len
;
165 for (i
= 0; i
< len
; i
++) {
166 res
= g_ptr_array_index(auto_disc
->results
, i
);
168 if (strcmp(res
->plugin_name
, plugin_name
) != 0) {
172 if (strcmp(res
->source_cc_name
, source_cc_name
) != 0) {
176 if (g_strcmp0(res
->group
, group
) != 0) {
185 /* Add a new result entry. */
186 res
= auto_source_discovery_result_create(plugin_name
,
187 source_cc_name
, group
, log_level
);
192 g_ptr_array_add(auto_disc
->results
, res
);
195 append_status
= bt_value_array_append_string_element(res
->inputs
, input
);
196 if (append_status
!= BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK
) {
197 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to append a string value.");
202 * Append `original_input_index` to `original_input_indices` if not
203 * there already. We process the `inputs` array in order, so if it is
204 * present, it has to be the last element.
206 if (bt_value_array_is_empty(res
->original_input_indices
)) {
209 const bt_value
*last_index_value
;
213 borrow_array_value_last_element_const(res
->original_input_indices
);
214 last_index
= bt_value_integer_unsigned_get(last_index_value
);
216 BT_ASSERT(last_index
<= original_input_index
);
218 append_index
= (last_index
!= original_input_index
);
222 append_status
= bt_value_array_append_unsigned_integer_element(
223 res
->original_input_indices
, original_input_index
);
225 if (append_status
!= BT_VALUE_ARRAY_APPEND_ELEMENT_STATUS_OK
) {
226 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to append an unsigned integer value.");
242 int convert_weight_value(const bt_value
*weight_value
, double *weight
,
243 const char *plugin_name
, const char *source_cc_name
,
244 const char *input
, const char *input_type
,
245 bt_logging_level log_level
)
247 enum bt_value_type weight_value_type
;
250 weight_value_type
= bt_value_get_type(weight_value
);
252 if (weight_value_type
== BT_VALUE_TYPE_REAL
) {
253 *weight
= bt_value_real_get(weight_value
);
254 } else if (weight_value_type
== BT_VALUE_TYPE_SIGNED_INTEGER
) {
255 /* Accept signed integer as a convenience for "return 0" or "return 1" in Python. */
256 *weight
= bt_value_integer_signed_get(weight_value
);
258 BT_LOGW("babeltrace.support-info query: unexpected type for weight: "
259 "component-class-name=source.%s.%s, input=%s, input-type=%s, "
260 "expected-entry-type=%s, actual-entry-type=%s",
261 plugin_name
, source_cc_name
, input
, input_type
,
262 bt_common_value_type_string(BT_VALUE_TYPE_REAL
),
263 bt_common_value_type_string(bt_value_get_type(weight_value
)));
267 if (*weight
< 0.0 || *weight
> 1.0) {
268 BT_LOGW("babeltrace.support-info query: weight value is out of range [0.0, 1.0]: "
269 "component-class-name=source.%s.%s, input=%s, input-type=%s, "
271 plugin_name
, source_cc_name
, input
, input_type
, *weight
);
286 bt_query_executor_query_status
simple_query(const bt_component_class
*comp_cls
,
287 const char *obj
, const bt_value
*params
,
288 bt_logging_level log_level
, const bt_value
**result
)
290 bt_query_executor_query_status status
;
291 bt_query_executor_set_logging_level_status set_logging_level_status
;
292 bt_query_executor
*query_exec
;
294 query_exec
= bt_query_executor_create(comp_cls
, obj
, params
);
296 BT_AUTODISC_LOGE_APPEND_CAUSE("Cannot create a query executor.");
297 status
= BT_QUERY_EXECUTOR_QUERY_STATUS_MEMORY_ERROR
;
301 set_logging_level_status
= bt_query_executor_set_logging_level(query_exec
, log_level
);
302 if (set_logging_level_status
!= BT_QUERY_EXECUTOR_SET_LOGGING_LEVEL_STATUS_OK
) {
303 BT_AUTODISC_LOGE_APPEND_CAUSE(
304 "Cannot set query executor's logging level: "
306 bt_common_logging_level_string(log_level
));
307 status
= (int) set_logging_level_status
;
311 status
= bt_query_executor_query(query_exec
, result
);
314 bt_query_executor_put_ref(query_exec
);
321 * Query all known source components to see if any of them can handle `input`
322 * as the given `type`(arbitrary string, directory or file).
324 * If `plugin_restrict` is non-NULL, only query source component classes provided
325 * by the plugin with that name.
327 * If `component_class_restrict` is non-NULL, only query source component classes
332 * - > 0 on success, if no source component class has reported that it handles `input`
333 * - 0 on success, if a source component class has reported that it handles `input`
334 * - < 0 on failure (e.g. memory error)
337 int support_info_query_all_sources(const char *input
,
338 const char *input_type
,
339 uint64_t original_input_index
,
340 const bt_plugin
**plugins
,
342 const char *component_class_restrict
,
343 enum bt_logging_level log_level
,
344 struct auto_source_discovery
*auto_disc
)
346 bt_value_map_insert_entry_status insert_status
;
347 bt_value
*query_params
= NULL
;
350 const struct bt_value
*query_result
= NULL
;
352 const bt_component_class_source
*source
;
353 const bt_plugin
*plugin
;
354 const bt_value
*group
;
356 } winner
= { NULL
, NULL
, NULL
, 0 };
358 query_params
= bt_value_map_create();
360 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to allocate a map value.");
364 insert_status
= bt_value_map_insert_string_entry(query_params
, "input", input
);
365 if (insert_status
!= BT_VALUE_MAP_INSERT_ENTRY_STATUS_OK
) {
366 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to insert a map entry.");
370 insert_status
= bt_value_map_insert_string_entry(query_params
, "type", input_type
);
371 if (insert_status
!= BT_VALUE_MAP_INSERT_ENTRY_STATUS_OK
) {
372 BT_AUTODISC_LOGE_APPEND_CAUSE("Failed to insert a map entry.");
376 for (i_plugins
= 0; i_plugins
< plugin_count
; i_plugins
++) {
377 const bt_plugin
*plugin
;
378 const char *plugin_name
;
379 uint64_t source_count
;
382 plugin
= plugins
[i_plugins
];
383 plugin_name
= bt_plugin_get_name(plugin
);
385 source_count
= bt_plugin_get_source_component_class_count(plugin
);
387 for (i_sources
= 0; i_sources
< source_count
; i_sources
++) {
388 const bt_component_class_source
*source_cc
;
389 const bt_component_class
*cc
;
390 const char *source_cc_name
;
391 bt_query_executor_query_status query_status
;
393 source_cc
= bt_plugin_borrow_source_component_class_by_index_const(plugin
, i_sources
);
394 cc
= bt_component_class_source_as_component_class_const(source_cc
);
395 source_cc_name
= bt_component_class_get_name(cc
);
398 * If the search is restricted to a specific component class, only consider the
399 * component classes with that name.
401 if (component_class_restrict
&& strcmp(component_class_restrict
, source_cc_name
) != 0) {
405 BT_LOGD("babeltrace.support-info query: before: component-class-name=source.%s.%s, input=%s, "
406 "type=%s", plugin_name
, source_cc_name
, input
, input_type
);
408 BT_VALUE_PUT_REF_AND_RESET(query_result
);
409 query_status
= simple_query(cc
, "babeltrace.support-info",
410 query_params
, log_level
, &query_result
);
412 if (query_status
== BT_QUERY_EXECUTOR_QUERY_STATUS_OK
) {
414 const bt_value
*group_value
= NULL
;
415 enum bt_value_type query_result_type
;
417 BT_ASSERT(query_result
);
419 query_result_type
= bt_value_get_type(query_result
);
421 if (query_result_type
== BT_VALUE_TYPE_REAL
|| query_result_type
== BT_VALUE_TYPE_SIGNED_INTEGER
) {
422 if (convert_weight_value(query_result
, &weight
, plugin_name
, source_cc_name
, input
, input_type
, log_level
) != 0) {
423 /* convert_weight_value has already warned. */
426 } else if (query_result_type
== BT_VALUE_TYPE_MAP
) {
427 const bt_value
*weight_value
;
429 if (!bt_value_map_has_entry(query_result
, "weight")) {
430 BT_LOGW("babeltrace.support-info query: result is missing `weight` entry: "
431 "component-class-name=source.%s.%s, input=%s, input-type=%s",
432 bt_plugin_get_name(plugin
),
433 bt_component_class_get_name(cc
), input
,
438 weight_value
= bt_value_map_borrow_entry_value_const(query_result
, "weight");
439 BT_ASSERT(weight_value
);
441 if (convert_weight_value(weight_value
, &weight
, plugin_name
, source_cc_name
, input
, input_type
, log_level
) != 0) {
442 /* convert_weight_value has already warned. */
446 if (bt_value_map_has_entry(query_result
, "group")) {
447 enum bt_value_type group_value_type
;
449 group_value
= bt_value_map_borrow_entry_value_const(query_result
, "group");
450 BT_ASSERT(group_value
);
452 group_value_type
= bt_value_get_type(group_value
);
454 if (group_value_type
== BT_VALUE_TYPE_NULL
) {
455 /* Do as if no value was passed. */
457 } else if (bt_value_get_type(group_value
) != BT_VALUE_TYPE_STRING
) {
458 BT_LOGW("babeltrace.support-info query: unexpected type for entry `group`: "
459 "component-class-name=source.%s.%s, input=%s, input-type=%s, "
460 "expected-entry-type=%s,%s, actual-entry-type=%s",
461 bt_plugin_get_name(plugin
),
462 bt_component_class_get_name(cc
), input
,
464 bt_common_value_type_string(BT_VALUE_TYPE_NULL
),
465 bt_common_value_type_string(BT_VALUE_TYPE_STRING
),
466 bt_common_value_type_string(bt_value_get_type(group_value
)));
471 BT_LOGW("babeltrace.support-info query: unexpected result type: "
472 "component-class-name=source.%s.%s, input=%s, input-type=%s, "
473 "expected-types=%s,%s,%s, actual-type=%s",
474 bt_plugin_get_name(plugin
),
475 bt_component_class_get_name(cc
), input
,
477 bt_common_value_type_string(BT_VALUE_TYPE_REAL
),
478 bt_common_value_type_string(BT_VALUE_TYPE_MAP
),
479 bt_common_value_type_string(BT_VALUE_TYPE_SIGNED_INTEGER
),
480 bt_common_value_type_string(bt_value_get_type(query_result
)));
484 BT_LOGD("babeltrace.support-info query: success: component-class-name=source.%s.%s, input=%s, "
485 "type=%s, weight=%f\n",
486 bt_plugin_get_name(plugin
), bt_component_class_get_name(cc
), input
,
489 if (weight
> winner
.weigth
) {
490 winner
.source
= source_cc
;
491 winner
.plugin
= plugin
;
493 bt_value_put_ref(winner
.group
);
494 winner
.group
= group_value
;
495 bt_value_get_ref(winner
.group
);
497 winner
.weigth
= weight
;
499 } else if (query_status
== BT_QUERY_EXECUTOR_QUERY_STATUS_ERROR
) {
500 BT_AUTODISC_LOGE_APPEND_CAUSE("babeltrace.support-info query failed.");
502 } else if (query_status
== BT_QUERY_EXECUTOR_QUERY_STATUS_MEMORY_ERROR
) {
503 BT_AUTODISC_LOGE_APPEND_CAUSE("Memory error.");
506 BT_LOGD("babeltrace.support-info query: failure: component-class-name=source.%s.%s, input=%s, "
507 "type=%s, status=%s\n",
508 bt_plugin_get_name(plugin
), bt_component_class_get_name(cc
), input
,
510 bt_common_func_status_string(query_status
));
516 const char *source_name
;
517 const char *plugin_name
;
520 source_name
= bt_component_class_get_name(
521 bt_component_class_source_as_component_class_const(winner
.source
));
522 plugin_name
= bt_plugin_get_name(winner
.plugin
);
523 group
= winner
.group
? bt_value_string_get(winner
.group
) : NULL
;
525 BT_LOGI("Input %s is awarded to component class source.%s.%s with weight %f",
526 input
, plugin_name
, source_name
, winner
.weigth
);
528 status
= auto_source_discovery_add(auto_disc
, plugin_name
,
529 source_name
, group
, input
, original_input_index
, log_level
);
534 BT_LOGI("Input %s (%s) was not recognized by any source component class.",
545 bt_value_put_ref(query_result
);
546 bt_value_put_ref(query_params
);
547 bt_value_put_ref(winner
.group
);
553 * Look for a source component class that recognizes `input` as an arbitrary
556 * Same return value semantic as `support_info_query_all_sources`.
560 int auto_discover_source_for_input_as_string(const char *input
,
561 uint64_t original_input_index
,
562 const bt_plugin
**plugins
,
564 const char *component_class_restrict
,
565 enum bt_logging_level log_level
,
566 struct auto_source_discovery
*auto_disc
)
568 return support_info_query_all_sources(input
, "string",
569 original_input_index
, plugins
, plugin_count
,
570 component_class_restrict
, log_level
, auto_disc
);
574 int auto_discover_source_for_input_as_dir_or_file_rec(GString
*input
,
575 uint64_t original_input_index
,
576 const bt_plugin
**plugins
,
578 const char *component_class_restrict
,
579 enum bt_logging_level log_level
,
580 struct auto_source_discovery
*auto_disc
)
583 GError
*error
= NULL
;
585 if (g_file_test(input
->str
, G_FILE_TEST_IS_REGULAR
)) {
587 status
= support_info_query_all_sources(input
->str
,
588 "file", original_input_index
, plugins
, plugin_count
,
589 component_class_restrict
, log_level
, auto_disc
);
590 } else if (g_file_test(input
->str
, G_FILE_TEST_IS_DIR
)) {
593 gsize saved_input_len
;
596 /* It's a directory. */
597 status
= support_info_query_all_sources(input
->str
,
598 "directory", original_input_index
, plugins
,
599 plugin_count
, component_class_restrict
, log_level
,
605 } else if (status
== 0) {
607 * A component class claimed this input as a directory,
613 dir
= g_dir_open(input
->str
, 0, &error
);
615 const char *fmt
= "Failed to open directory %s: %s";
616 BT_LOGW(fmt
, input
->str
, error
->message
);
618 if (error
->code
== G_FILE_ERROR_ACCES
) {
619 /* This is not a fatal error, we just skip it. */
623 BT_AUTODISC_LOGE_APPEND_CAUSE(fmt
, input
->str
,
629 saved_input_len
= input
->len
;
633 dirent
= g_dir_read_name(dir
);
635 g_string_append_c_inline(input
, G_DIR_SEPARATOR
);
636 g_string_append(input
, dirent
);
638 status
= auto_discover_source_for_input_as_dir_or_file_rec(
639 input
, original_input_index
, plugins
, plugin_count
,
640 component_class_restrict
, log_level
, auto_disc
);
642 g_string_truncate(input
, saved_input_len
);
647 } else if (status
== 0) {
650 } else if (errno
!= 0) {
651 BT_LOGW_ERRNO("Failed to read directory entry", ": dir=%s", input
->str
);
660 BT_LOGD("Skipping %s, not a file or directory", input
->str
);
679 * Look for a source component class that recognizes `input` as a directory or
680 * file. If `input` is a directory and is not directly recognized, recurse and
681 * apply the same logic to children nodes.
683 * Same return value semantic as `support_info_query_all_sources`.
687 int auto_discover_source_for_input_as_dir_or_file(const char *input
,
688 uint64_t original_input_index
,
689 const bt_plugin
**plugins
,
691 const char *component_class_restrict
,
692 enum bt_logging_level log_level
,
693 struct auto_source_discovery
*auto_disc
)
695 GString
*mutable_input
;
698 mutable_input
= g_string_new(input
);
699 if (!mutable_input
) {
704 status
= auto_discover_source_for_input_as_dir_or_file_rec(
705 mutable_input
, original_input_index
, plugins
, plugin_count
,
706 component_class_restrict
, log_level
, auto_disc
);
708 g_string_free(mutable_input
, TRUE
);
713 int auto_discover_source_components(
714 const bt_value
*inputs
,
715 const bt_plugin
**plugins
,
717 const char *component_class_restrict
,
718 enum bt_logging_level log_level
,
719 struct auto_source_discovery
*auto_disc
)
721 uint64_t i_inputs
, input_count
;
724 input_count
= bt_value_array_get_length(inputs
);
726 for (i_inputs
= 0; i_inputs
< input_count
; i_inputs
++) {
727 const bt_value
*input_value
;
730 input_value
= bt_value_array_borrow_element_by_index_const(inputs
, i_inputs
);
731 input
= bt_value_string_get(input_value
);
732 status
= auto_discover_source_for_input_as_string(input
, i_inputs
,
733 plugins
, plugin_count
, component_class_restrict
,
734 log_level
, auto_disc
);
738 } else if (status
== 0) {
739 /* A component class has claimed this input as an arbitrary string. */
743 status
= auto_discover_source_for_input_as_dir_or_file(input
,
744 i_inputs
, plugins
, plugin_count
,
745 component_class_restrict
, log_level
, auto_disc
);
749 } else if (status
== 0) {
751 * This input (or something under it) was recognized.
756 BT_LOGW("No trace was found based on input `%s`.", input
);