From 2f100782231d86cdaaadf7a8568c5b28583800f4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=A9mie=20Galarneau?= Date: Mon, 9 Jun 2014 20:51:16 -0400 Subject: [PATCH] Implement CTF-IR event getters MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérémie Galarneau --- formats/ctf/ir/event.c | 242 ++++++++++++++++-- formats/ctf/ir/stream-class.c | 100 +++++--- formats/ctf/writer/writer.c | 17 +- include/babeltrace/ctf-ir/event-internal.h | 14 +- include/babeltrace/ctf-ir/event.h | 141 +++++++++- .../babeltrace/ctf-ir/stream-class-internal.h | 4 - include/babeltrace/ctf-ir/stream-class.h | 35 +++ .../babeltrace/ctf-writer/writer-internal.h | 2 +- 8 files changed, 469 insertions(+), 86 deletions(-) diff --git a/formats/ctf/ir/event.c b/formats/ctf/ir/event.c index 7d4a8d7f..6eb4a3f5 100644 --- a/formats/ctf/ir/event.c +++ b/formats/ctf/ir/event.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -59,6 +60,73 @@ end: return event_class; } +const char *bt_ctf_event_class_get_name(struct bt_ctf_event_class *event_class) +{ + const char *name = NULL; + + if (!event_class) { + goto end; + } + + name = g_quark_to_string(event_class->name); +end: + return name; +} + +int64_t bt_ctf_event_class_get_id(struct bt_ctf_event_class *event_class) +{ + int64_t ret; + + if (!event_class || !event_class->id_set) { + ret = -1; + goto end; + } + + ret = (int64_t) event_class->id; +end: + return ret; +} + +int bt_ctf_event_class_set_id(struct bt_ctf_event_class *event_class, + uint32_t id) +{ + int ret = 0; + + if (!event_class) { + ret = -1; + goto end; + } + + if (event_class->stream_class) { + /* + * We don't allow changing the id if the event class has already + * been added to a stream class. + */ + ret = -1; + goto end; + } + + event_class->id = id; + event_class->id_set = 1; +end: + return ret; +} + +struct bt_ctf_stream_class *bt_ctf_event_class_get_stream_class( + struct bt_ctf_event_class *event_class) +{ + struct bt_ctf_stream_class *stream_class = NULL; + + if (!event_class) { + goto end; + } + + stream_class = event_class->stream_class; + bt_ctf_stream_class_get(stream_class); +end: + return stream_class; +} + int bt_ctf_event_class_add_field(struct bt_ctf_event_class *event_class, struct bt_ctf_field_type *type, const char *name) @@ -85,6 +153,63 @@ end: return ret; } +int64_t bt_ctf_event_class_get_field_count( + struct bt_ctf_event_class *event_class) +{ + int64_t ret; + + if (!event_class) { + ret = -1; + goto end; + } + + ret = bt_ctf_field_type_structure_get_field_count(event_class->fields); +end: + return ret; +} + +int bt_ctf_event_class_get_field(struct bt_ctf_event_class *event_class, + const char **field_name, struct bt_ctf_field_type **field_type, + size_t index) +{ + int ret; + + if (!event_class) { + ret = -1; + goto end; + } + + ret = bt_ctf_field_type_structure_get_field(event_class->fields, + field_name, field_type, index); +end: + return ret; +} + +struct bt_ctf_field_type *bt_ctf_event_class_get_field_by_name( + struct bt_ctf_event_class *event_class, const char *name) +{ + GQuark name_quark; + struct bt_ctf_field_type *field_type = NULL; + + if (!event_class || !name) { + goto end; + } + + name_quark = g_quark_try_string(name); + if (!name_quark) { + goto end; + } + + /* + * No need to increment field_type's reference count since getting it + * from the structure already does. + */ + field_type = bt_ctf_field_type_structure_get_field_type_by_name( + event_class->fields, name); +end: + return field_type; +} + void bt_ctf_event_class_get(struct bt_ctf_event_class *event_class) { if (!event_class) { @@ -126,6 +251,53 @@ end: return event; } +struct bt_ctf_event_class *bt_ctf_event_get_class(struct bt_ctf_event *event) +{ + struct bt_ctf_event_class *event_class = NULL; + + if (!event) { + goto end; + } + + event_class = event->event_class; + bt_ctf_event_class_get(event_class); +end: + return event_class; +} + +struct bt_ctf_clock *bt_ctf_event_get_clock(struct bt_ctf_event *event) +{ + struct bt_ctf_clock *clock = NULL; + struct bt_ctf_event_class *event_class; + struct bt_ctf_stream_class *stream_class; + + if (!event) { + goto end; + } + + event_class = bt_ctf_event_get_class(event); + if (!event_class) { + goto end; + } + + stream_class = bt_ctf_event_class_get_stream_class(event_class); + if (!stream_class) { + goto error_put_event_class; + } + + clock = bt_ctf_stream_class_get_clock(stream_class); + if (!clock) { + goto error_put_stream_class; + } + +error_put_stream_class: + bt_ctf_stream_class_put(stream_class); +error_put_event_class: + bt_ctf_event_class_put(event_class); +end: + return clock; +} + int bt_ctf_event_set_payload(struct bt_ctf_event *event, const char *name, struct bt_ctf_field *value) @@ -158,6 +330,21 @@ end: return field; } +struct bt_ctf_field *bt_ctf_event_get_payload_by_index( + struct bt_ctf_event *event, size_t index) +{ + struct bt_ctf_field *field = NULL; + + if (!event) { + goto end; + } + + field = bt_ctf_field_structure_get_field_by_index(event->fields_payload, + index); +end: + return field; +} + void bt_ctf_event_get(struct bt_ctf_event *event) { if (!event) { @@ -185,6 +372,10 @@ void bt_ctf_event_class_destroy(struct bt_ctf_ref *ref) return; } + /* + * Don't call put() on the stream class. See comment in + * bt_ctf_event_class_set_stream_class for explanation. + */ event_class = container_of(ref, struct bt_ctf_event_class, ref_count); bt_ctf_field_type_put(event_class->context); bt_ctf_field_type_put(event_class->fields); @@ -218,43 +409,31 @@ void bt_ctf_event_class_freeze(struct bt_ctf_event_class *event_class) } BT_HIDDEN -int bt_ctf_event_class_set_id(struct bt_ctf_event_class *event_class, - uint32_t id) +int bt_ctf_event_class_set_stream_class(struct bt_ctf_event_class *event_class, + struct bt_ctf_stream_class *stream_class) { int ret = 0; - if (event_class->id_set && id != event_class->id) { + if (!event_class) { ret = -1; goto end; } - event_class->id = id; - event_class->id_set = 1; -end: - return ret; -} - -BT_HIDDEN -uint32_t bt_ctf_event_class_get_id(struct bt_ctf_event_class *event_class) -{ - assert(event_class); - return event_class->id; -} - -BT_HIDDEN -int bt_ctf_event_class_set_stream_id(struct bt_ctf_event_class *event_class, - uint32_t id) -{ - int ret = 0; - - assert(event_class); - if (event_class->stream_id_set && id != event_class->stream_id) { + /* Allow a NULL stream_class to unset the current stream_class */ + if (stream_class && event_class->stream_class) { ret = -1; goto end; } - event_class->stream_id = id; - event_class->stream_id_set = 1; + event_class->stream_class = stream_class; + /* + * We don't get() the stream_class since doing so would introduce + * a circular ownership between event classes and stream classes. + * + * A stream class will always unset itself from its events before + * being destroyed. This ensures that a user won't get a pointer + * to a stale stream class instance from an event class. + */ end: return ret; } @@ -264,15 +443,22 @@ int bt_ctf_event_class_serialize(struct bt_ctf_event_class *event_class, struct metadata_context *context) { int ret = 0; + int64_t stream_id; assert(event_class); assert(context); + stream_id = bt_ctf_stream_class_get_id(event_class->stream_class); + if (stream_id < 0) { + ret = -1; + goto end; + } + context->current_indentation_level = 1; g_string_assign(context->field_name, ""); - g_string_append_printf(context->string, "event {\n\tname = \"%s\";\n\tid = %u;\n\tstream_id = %" PRIu32 ";\n", + g_string_append_printf(context->string, "event {\n\tname = \"%s\";\n\tid = %u;\n\tstream_id = %" PRId64 ";\n", g_quark_to_string(event_class->name), event_class->id, - event_class->stream_id); + stream_id); if (event_class->context) { g_string_append(context->string, "\tcontext := "); diff --git a/formats/ctf/ir/stream-class.c b/formats/ctf/ir/stream-class.c index 6cba98f9..7957a496 100644 --- a/formats/ctf/ir/stream-class.c +++ b/formats/ctf/ir/stream-class.c @@ -77,6 +77,21 @@ error: return stream_class; } +struct bt_ctf_clock *bt_ctf_stream_class_get_clock( + struct bt_ctf_stream_class *stream_class) +{ + struct bt_ctf_clock *clock = NULL; + + if (!stream_class || !stream_class->clock) { + goto end; + } + + clock = stream_class->clock; + bt_ctf_clock_get(clock); +end: + return clock; +} + int bt_ctf_stream_class_set_clock(struct bt_ctf_stream_class *stream_class, struct bt_ctf_clock *clock) { @@ -97,11 +112,42 @@ end: return ret; } +int64_t bt_ctf_stream_class_get_id(struct bt_ctf_stream_class *stream_class) +{ + int64_t ret; + + if (!stream_class || !stream_class->id_set) { + ret = -1; + goto end; + } + + ret = (int64_t) stream_class->id; +end: + return ret; +} + +int bt_ctf_stream_class_set_id(struct bt_ctf_stream_class *stream_class, + uint32_t id) +{ + int ret = 0; + + if (!stream_class) { + ret = -1; + goto end; + } + + stream_class->id = id; + stream_class->id_set = 1; +end: + return ret; +} + int bt_ctf_stream_class_add_event_class( struct bt_ctf_stream_class *stream_class, struct bt_ctf_event_class *event_class) { int ret = 0; + int64_t event_id; if (!stream_class || !event_class) { ret = -1; @@ -116,10 +162,18 @@ int bt_ctf_stream_class_add_event_class( goto end; } - if (bt_ctf_event_class_set_id(event_class, - stream_class->next_event_id++)) { - /* The event is already associated to a stream class */ - ret = -1; + /* Only set an event id if none was explicitly set before */ + event_id = bt_ctf_event_class_get_id(event_class); + if (event_id < 0) { + if (bt_ctf_event_class_set_id(event_class, + stream_class->next_event_id++)) { + ret = -1; + goto end; + } + } + + ret = bt_ctf_event_class_set_stream_class(event_class, stream_class); + if (ret) { goto end; } @@ -160,24 +214,6 @@ void bt_ctf_stream_class_freeze(struct bt_ctf_stream_class *stream_class) (GFunc)bt_ctf_event_class_freeze, NULL); } -BT_HIDDEN -int bt_ctf_stream_class_set_id(struct bt_ctf_stream_class *stream_class, - uint32_t id) -{ - int ret = 0; - - if (!stream_class || - (stream_class->id_set && (id != stream_class->id))) { - ret = -1; - goto end; - } - - stream_class->id = id; - stream_class->id_set = 1; -end: - return ret; -} - BT_HIDDEN int bt_ctf_stream_class_set_byte_order(struct bt_ctf_stream_class *stream_class, enum bt_ctf_byte_order byte_order) @@ -201,7 +237,7 @@ BT_HIDDEN int bt_ctf_stream_class_serialize(struct bt_ctf_stream_class *stream_class, struct metadata_context *context) { - int ret = 0; + int64_t ret = 0; size_t i; g_string_assign(context->field_name, ""); @@ -237,17 +273,10 @@ int bt_ctf_stream_class_serialize(struct bt_ctf_stream_class *stream_class, } g_string_append(context->string, ";\n};\n\n"); - - /* Assign this stream's ID to every event and serialize them */ - g_ptr_array_foreach(stream_class->event_classes, - (GFunc) bt_ctf_event_class_set_stream_id, - GUINT_TO_POINTER(stream_class->id)); for (i = 0; i < stream_class->event_classes->len; i++) { struct bt_ctf_event_class *event_class = stream_class->event_classes->pdata[i]; - ret = bt_ctf_event_class_set_stream_id(event_class, - stream_class->id); if (ret) { goto end; } @@ -275,6 +304,17 @@ void bt_ctf_stream_class_destroy(struct bt_ctf_ref *ref) bt_ctf_clock_put(stream_class->clock); if (stream_class->event_classes) { + size_t i; + + /* Unregister this stream class from the event classes */ + for (i = 0; i < stream_class->event_classes->len; i++) { + struct bt_ctf_event_class *event_class = + g_ptr_array_index(stream_class->event_classes, + i); + + bt_ctf_event_class_set_stream_class(event_class, NULL); + } + g_ptr_array_free(stream_class->event_classes, TRUE); } diff --git a/formats/ctf/writer/writer.c b/formats/ctf/writer/writer.c index d1ef5dc4..57236bd9 100644 --- a/formats/ctf/writer/writer.c +++ b/formats/ctf/writer/writer.c @@ -234,6 +234,7 @@ struct bt_ctf_stream *bt_ctf_writer_create_stream(struct bt_ctf_writer *writer, goto error; } + for (i = 0; i < writer->stream_classes->len; i++) { if (writer->stream_classes->pdata[i] == stream->stream_class) { stream_class_found = 1; @@ -241,11 +242,21 @@ struct bt_ctf_stream *bt_ctf_writer_create_stream(struct bt_ctf_writer *writer, } if (!stream_class_found) { - if (bt_ctf_stream_class_set_id(stream->stream_class, - writer->next_stream_id++)) { - goto error; + int64_t stream_id = bt_ctf_stream_class_get_id(stream_class); + if (stream_id < 0) { + if (bt_ctf_stream_class_set_id(stream->stream_class, + writer->next_stream_id++)) { + goto error; + } } + for (i = 0; i < writer->stream_classes->len; i++) { + if (stream_id == bt_ctf_stream_class_get_id( + writer->stream_classes->pdata[i])) { + /* Duplicate stream id found */ + goto error; + } + } bt_ctf_stream_class_get(stream->stream_class); g_ptr_array_add(writer->stream_classes, stream->stream_class); } diff --git a/include/babeltrace/ctf-ir/event-internal.h b/include/babeltrace/ctf-ir/event-internal.h index aaf2ea88..ac4cd6de 100644 --- a/include/babeltrace/ctf-ir/event-internal.h +++ b/include/babeltrace/ctf-ir/event-internal.h @@ -39,8 +39,7 @@ struct bt_ctf_event_class { GQuark name; int id_set; uint32_t id; - int stream_id_set; - uint32_t stream_id; + struct bt_ctf_stream_class *stream_class; /* Structure type containing the event's context */ struct bt_ctf_field_type *context; /* Structure type containing the event's fields */ @@ -60,15 +59,8 @@ BT_HIDDEN void bt_ctf_event_class_freeze(struct bt_ctf_event_class *event_class); BT_HIDDEN -int bt_ctf_event_class_set_id(struct bt_ctf_event_class *event_class, - uint32_t id); - -BT_HIDDEN -uint32_t bt_ctf_event_class_get_id(struct bt_ctf_event_class *event_class); - -BT_HIDDEN -int bt_ctf_event_class_set_stream_id(struct bt_ctf_event_class *event_class, - uint32_t id); +int bt_ctf_event_class_set_stream_class(struct bt_ctf_event_class *event_class, + struct bt_ctf_stream_class *stream_class); BT_HIDDEN int bt_ctf_event_class_serialize(struct bt_ctf_event_class *event_class, diff --git a/include/babeltrace/ctf-ir/event.h b/include/babeltrace/ctf-ir/event.h index 26996c0d..7daab395 100644 --- a/include/babeltrace/ctf-ir/event.h +++ b/include/babeltrace/ctf-ir/event.h @@ -30,6 +30,9 @@ * http://www.efficios.com/ctf */ +#include +#include + #ifdef __cplusplus extern "C" { #endif @@ -38,12 +41,14 @@ struct bt_ctf_event_class; struct bt_ctf_event; struct bt_ctf_field; struct bt_ctf_field_type; +struct bt_ctf_stream_class; /* * bt_ctf_event_class_create: create an event class. * * Allocate a new event class of the given name. The creation of an event class - * sets its reference count to 1. + * sets its reference count to 1. A unique event id is automatically assigned + * to the event class. * * @param name Event class name (will be copied). * @@ -51,6 +56,52 @@ struct bt_ctf_field_type; */ extern struct bt_ctf_event_class *bt_ctf_event_class_create(const char *name); +/* + * bt_ctf_event_class_get_name: Get an event class' name. + * + * @param event_class Event class. + * + * Returns the event class' name, NULL on error. + */ +extern const char *bt_ctf_event_class_get_name( + struct bt_ctf_event_class *event_class); + +/* + * bt_ctf_event_class_get_id: Get an event class' id. + * + * @param event_class Event class. + * + * Returns the event class' id, a negative value on error. + */ +extern int64_t bt_ctf_event_class_get_id( + struct bt_ctf_event_class *event_class); + +/* + * bt_ctf_event_class_set_id: Set an event class' id. + * + * Set an event class' id. Must be unique stream-wise. + * Note that event classes are already assigned a unique id when added to a + * stream class if none was set explicitly. + * + * @param event_class Event class. + * @param id Event class id. + * + * Returns 0 on success, a negative value on error. + */ +extern int bt_ctf_event_class_set_id( + struct bt_ctf_event_class *event_class, uint32_t id); + +/* + * bt_ctf_event_class_get_stream_class: Get an event class' stream class. + * + * @param event_class Event class. + * + * Returns the event class' stream class, NULL on error or if the event class + * is not associated with a stream class. + */ +extern struct bt_ctf_stream_class *bt_ctf_event_class_get_stream_class( + struct bt_ctf_event_class *event_class); + /* * bt_ctf_event_class_add_field: add a field to an event class. * @@ -68,6 +119,43 @@ extern int bt_ctf_event_class_add_field(struct bt_ctf_event_class *event_class, struct bt_ctf_field_type *type, const char *name); +/* + * bt_ctf_event_class_get_field_count: Get an event class' field count. + * + * @param event_class Event class. + * + * Returns the event class' field count, a negative value on error. + */ +extern int64_t bt_ctf_event_class_get_field_count( + struct bt_ctf_event_class *event_class); + +/* + * bt_ctf_event_class_get_field: Get event class' field type and name by index. + * + * @param event_class Event class. + * @param field_name Pointer to a const char* where the field's name will + * be returned. + * @param field_type Pointer to a bt_ctf_field_type* where the field's type will + * be returned. + * @param index Index of field. + * + * Returns 0 on success, a negative error on value. + */ +extern int bt_ctf_event_class_get_field(struct bt_ctf_event_class *event_class, + const char **field_name, struct bt_ctf_field_type **field_type, + size_t index); + +/* + * bt_ctf_event_class_get_field_type_by_name: Get an event class's field by name + * + * @param event_class Event class. + * @param name Name of the field. + * + * Returns a field type on success, NULL on error. + */ +extern struct bt_ctf_field_type *bt_ctf_event_class_get_field_by_name( + struct bt_ctf_event_class *event_class, const char *name); + /* * bt_ctf_event_class__get and bt_ctf_event_class_put: increment and decrement * the event class' reference count. @@ -99,6 +187,40 @@ extern void bt_ctf_event_class_put(struct bt_ctf_event_class *event_class); extern struct bt_ctf_event *bt_ctf_event_create( struct bt_ctf_event_class *event_class); +/* + * bt_ctf_event_get_class: get an event's class. + * + * @param event Event. + * + * Returns the event's class, NULL on error. + */ +extern struct bt_ctf_event_class *bt_ctf_event_get_class( + struct bt_ctf_event *event); + +/* + * bt_ctf_event_get_clock: get an event's associated clock. + * + * @param event Event. + * + * Returns the event's clock, NULL on error. + */ +extern struct bt_ctf_clock *bt_ctf_event_get_clock( + struct bt_ctf_event *event); + +/* + * bt_ctf_event_get_payload: get an event's field. + * + * Returns the field matching "name". bt_ctf_field_put() must be called on the + * returned value. + * + * @param event Event instance. + * @param name Event field name. + * + * Returns a field instance on success, NULL on error. + */ +extern struct bt_ctf_field *bt_ctf_event_get_payload(struct bt_ctf_event *event, + const char *name); + /* * bt_ctf_event_set_payload: set an event's field. * @@ -117,18 +239,19 @@ extern int bt_ctf_event_set_payload(struct bt_ctf_event *event, struct bt_ctf_field *value); /* - * bt_ctf_event_get_payload: get an event's field. + * bt_ctf_event_get_payload_by_index: Get event's field by index. * - * Returns the field matching "name". bt_ctf_field_put() must be called on the - * returned value. + * Returns the field associated with the provided index. bt_ctf_field_put() + * must be called on the returned value. The indexes to be provided are + * the same as can be retrieved from the event class. * - * @param event Event instance. - * @param name Event field name. + * @param event Event. + * @param index Index of field. * - * Returns a field instance on success, NULL on error. + * Returns the event's field, NULL on error. */ -extern struct bt_ctf_field *bt_ctf_event_get_payload(struct bt_ctf_event *event, - const char *name); +extern struct bt_ctf_field *bt_ctf_event_get_payload_by_index( + struct bt_ctf_event *event, size_t index); /* * bt_ctf_event_get and bt_ctf_event_put: increment and decrement diff --git a/include/babeltrace/ctf-ir/stream-class-internal.h b/include/babeltrace/ctf-ir/stream-class-internal.h index 9b609a4d..c53823d1 100644 --- a/include/babeltrace/ctf-ir/stream-class-internal.h +++ b/include/babeltrace/ctf-ir/stream-class-internal.h @@ -56,10 +56,6 @@ struct bt_ctf_stream_class { BT_HIDDEN void bt_ctf_stream_class_freeze(struct bt_ctf_stream_class *stream_class); -BT_HIDDEN -int bt_ctf_stream_class_set_id(struct bt_ctf_stream_class *stream_class, - uint32_t id); - BT_HIDDEN int bt_ctf_stream_class_serialize(struct bt_ctf_stream_class *stream_class, struct metadata_context *context); diff --git a/include/babeltrace/ctf-ir/stream-class.h b/include/babeltrace/ctf-ir/stream-class.h index 33737335..d26bc2ca 100644 --- a/include/babeltrace/ctf-ir/stream-class.h +++ b/include/babeltrace/ctf-ir/stream-class.h @@ -50,6 +50,16 @@ struct bt_ctf_clock; */ extern struct bt_ctf_stream_class *bt_ctf_stream_class_create(const char *name); +/* + * bt_ctf_stream_class_get_clock: get the clock associated with a stream class. + * + * @param stream_class Stream class. + * + * Returns a clock instance, NULL on error. + */ +extern struct bt_ctf_clock *bt_ctf_stream_class_get_clock( + struct bt_ctf_stream_class *stream_class); + /* * bt_ctf_stream_class_set_clock: assign a clock to a stream class. * @@ -65,6 +75,31 @@ extern int bt_ctf_stream_class_set_clock( struct bt_ctf_stream_class *stream_class, struct bt_ctf_clock *clock); +/* + * bt_ctf_stream_class_get_id: Get a stream class' id. + * + * @param stream_class Stream class. + * + * Returns the stream class' id, a negative value on error. + */ +extern int64_t bt_ctf_stream_class_get_id( + struct bt_ctf_stream_class *stream_class); + +/* + * bt_ctf_stream_class_set_id: Set a stream class' id. + * + * Set a stream class' id. Must be unique trace-wise. + * Note that stream classes are assigned a unique id when a stream instance + * is created for the first time from a trace or writer. + * + * @param stream_class Stream class. + * @param id Stream class id. + * + * Returns 0 on success, a negative value on error. + */ +extern int bt_ctf_stream_class_set_id( + struct bt_ctf_stream_class *stream_class, uint32_t id); + /* * bt_ctf_stream_class_set_clock: assign a clock to a stream class. * diff --git a/include/babeltrace/ctf-writer/writer-internal.h b/include/babeltrace/ctf-writer/writer-internal.h index 1ab77d81..cf7e399c 100644 --- a/include/babeltrace/ctf-writer/writer-internal.h +++ b/include/babeltrace/ctf-writer/writer-internal.h @@ -61,7 +61,7 @@ struct bt_ctf_writer { GPtrArray *streams; /* Array of pointers to bt_ctf_stream */ struct bt_ctf_field_type *trace_packet_header_type; struct bt_ctf_field *trace_packet_header; - uint32_t next_stream_id; + uint64_t next_stream_id; }; struct environment_variable { -- 2.34.1