flt.utils.trimmer: honor component's initial log level
[babeltrace.git] / src / plugins / utils / trimmer / trimmer.c
1 /*
2 * Copyright 2016 Jérémie Galarneau <jeremie.galarneau@efficios.com>
3 * Copyright 2019 Philippe Proulx <pproulx@efficios.com>
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 * SOFTWARE.
22 */
23
24 #define BT_LOG_OUTPUT_LEVEL (trimmer_comp->log_level)
25 #define BT_LOG_TAG "PLUGIN/FLT.UTILS.TRIMMER"
26 #include "logging/log.h"
27
28 #include "compat/utc.h"
29 #include "compat/time.h"
30 #include <babeltrace2/babeltrace.h>
31 #include "common/common.h"
32 #include "common/assert.h"
33 #include <stdint.h>
34 #include <inttypes.h>
35 #include <glib.h>
36
37 #include "trimmer.h"
38
39 #define NS_PER_S INT64_C(1000000000)
40
41 static const char * const in_port_name = "in";
42
43 struct trimmer_time {
44 unsigned int hour, minute, second, ns;
45 };
46
47 struct trimmer_bound {
48 /*
49 * Nanoseconds from origin, valid if `is_set` is set and
50 * `is_infinite` is false.
51 */
52 int64_t ns_from_origin;
53
54 /* True if this bound's full time (`ns_from_origin`) is set */
55 bool is_set;
56
57 /*
58 * True if this bound represents the infinity (negative or
59 * positive depending on which bound it is). If this is true,
60 * then we don't care about `ns_from_origin` above.
61 */
62 bool is_infinite;
63
64 /*
65 * This bound's time without the date; this time is used to set
66 * `ns_from_origin` once we know the date.
67 */
68 struct trimmer_time time;
69 };
70
71 struct trimmer_comp {
72 struct trimmer_bound begin, end;
73 bool is_gmt;
74 bt_logging_level log_level;
75 };
76
77 enum trimmer_iterator_state {
78 /*
79 * Find the first message's date and set the bounds's times
80 * accordingly.
81 */
82 TRIMMER_ITERATOR_STATE_SET_BOUNDS_NS_FROM_ORIGIN,
83
84 /*
85 * Initially seek to the trimming range's beginning time.
86 */
87 TRIMMER_ITERATOR_STATE_SEEK_INITIALLY,
88
89 /*
90 * Fill the output message queue for as long as received input
91 * messages are within the trimming time range.
92 */
93 TRIMMER_ITERATOR_STATE_TRIM,
94
95 /* Flush the remaining messages in the output message queue */
96 TRIMMER_ITERATOR_STATE_ENDING,
97
98 /* Trimming operation and message iterator is ended */
99 TRIMMER_ITERATOR_STATE_ENDED,
100 };
101
102 struct trimmer_iterator {
103 /* Weak */
104 struct trimmer_comp *trimmer_comp;
105
106 /* Weak */
107 bt_self_message_iterator *self_msg_iter;
108
109 enum trimmer_iterator_state state;
110
111 /* Owned by this */
112 bt_self_component_port_input_message_iterator *upstream_iter;
113 struct trimmer_bound begin, end;
114
115 /*
116 * Queue of `const bt_message *` (owned by the queue).
117 *
118 * This is where the trimming operation pushes the messages to
119 * output by this message iterator.
120 */
121 GQueue *output_messages;
122
123 /*
124 * Hash table of `bt_stream *` (weak) to
125 * `struct trimmer_iterator_stream_state *` (owned by the HT).
126 */
127 GHashTable *stream_states;
128 };
129
130 struct trimmer_iterator_stream_state {
131 /*
132 * True if both stream beginning and initial stream activity
133 * beginning messages were pushed for this stream.
134 */
135 bool inited;
136
137 /*
138 * True if the last pushed message for this stream was a stream
139 * activity end message.
140 */
141 bool last_msg_is_stream_activity_end;
142
143 /*
144 * Time to use for a generated stream end activity message when
145 * ending the stream.
146 */
147 int64_t stream_act_end_ns_from_origin;
148
149 /* Weak */
150 const bt_stream *stream;
151
152 /* Owned by this (`NULL` initially and between packets) */
153 const bt_packet *cur_packet;
154
155 /* Owned by this */
156 const bt_message *stream_beginning_msg;
157 };
158
159 static
160 void destroy_trimmer_comp(struct trimmer_comp *trimmer_comp)
161 {
162 BT_ASSERT(trimmer_comp);
163 g_free(trimmer_comp);
164 }
165
166 static
167 struct trimmer_comp *create_trimmer_comp(void)
168 {
169 return g_new0(struct trimmer_comp, 1);
170 }
171
172 BT_HIDDEN
173 void trimmer_finalize(bt_self_component_filter *self_comp)
174 {
175 struct trimmer_comp *trimmer_comp =
176 bt_self_component_get_data(
177 bt_self_component_filter_as_self_component(self_comp));
178
179 if (trimmer_comp) {
180 destroy_trimmer_comp(trimmer_comp);
181 }
182 }
183
184 /*
185 * Sets the time (in ns from origin) of a trimmer bound from date and
186 * time components.
187 *
188 * Returns a negative value if anything goes wrong.
189 */
190 static
191 int set_bound_ns_from_origin(struct trimmer_bound *bound,
192 unsigned int year, unsigned int month, unsigned int day,
193 unsigned int hour, unsigned int minute, unsigned int second,
194 unsigned int ns, bool is_gmt)
195 {
196 int ret = 0;
197 time_t result;
198 struct tm tm = {
199 .tm_sec = second,
200 .tm_min = minute,
201 .tm_hour = hour,
202 .tm_mday = day,
203 .tm_mon = month - 1,
204 .tm_year = year - 1900,
205 .tm_isdst = -1,
206 };
207
208 if (is_gmt) {
209 result = bt_timegm(&tm);
210 } else {
211 result = mktime(&tm);
212 }
213
214 if (result < 0) {
215 ret = -1;
216 goto end;
217 }
218
219 BT_ASSERT(bound);
220 bound->ns_from_origin = (int64_t) result;
221 bound->ns_from_origin *= NS_PER_S;
222 bound->ns_from_origin += ns;
223 bound->is_set = true;
224
225 end:
226 return ret;
227 }
228
229 /*
230 * Parses a timestamp, figuring out its format.
231 *
232 * Returns a negative value if anything goes wrong.
233 *
234 * Expected formats:
235 *
236 * YYYY-MM-DD hh:mm[:ss[.ns]]
237 * [hh:mm:]ss[.ns]
238 * [-]s[.ns]
239 *
240 * TODO: Check overflows.
241 */
242 static
243 int set_bound_from_str(struct trimmer_comp *trimmer_comp,
244 const char *str, struct trimmer_bound *bound, bool is_gmt)
245 {
246 int ret = 0;
247 int s_ret;
248 unsigned int year, month, day, hour, minute, second, ns;
249 char dummy;
250
251 /* Try `YYYY-MM-DD hh:mm:ss.ns` format */
252 s_ret = sscanf(str, "%u-%u-%u %u:%u:%u.%u%c", &year, &month, &day,
253 &hour, &minute, &second, &ns, &dummy);
254 if (s_ret == 7) {
255 ret = set_bound_ns_from_origin(bound, year, month, day,
256 hour, minute, second, ns, is_gmt);
257 goto end;
258 }
259
260 /* Try `YYYY-MM-DD hh:mm:ss` format */
261 s_ret = sscanf(str, "%u-%u-%u %u:%u:%u%c", &year, &month, &day,
262 &hour, &minute, &second, &dummy);
263 if (s_ret == 6) {
264 ret = set_bound_ns_from_origin(bound, year, month, day,
265 hour, minute, second, 0, is_gmt);
266 goto end;
267 }
268
269 /* Try `YYYY-MM-DD hh:mm` format */
270 s_ret = sscanf(str, "%u-%u-%u %u:%u%c", &year, &month, &day,
271 &hour, &minute, &dummy);
272 if (s_ret == 5) {
273 ret = set_bound_ns_from_origin(bound, year, month, day,
274 hour, minute, 0, 0, is_gmt);
275 goto end;
276 }
277
278 /* Try `YYYY-MM-DD` format */
279 s_ret = sscanf(str, "%u-%u-%u%c", &year, &month, &day, &dummy);
280 if (s_ret == 3) {
281 ret = set_bound_ns_from_origin(bound, year, month, day,
282 0, 0, 0, 0, is_gmt);
283 goto end;
284 }
285
286 /* Try `hh:mm:ss.ns` format */
287 s_ret = sscanf(str, "%u:%u:%u.%u%c", &hour, &minute, &second, &ns,
288 &dummy);
289 if (s_ret == 4) {
290 bound->time.hour = hour;
291 bound->time.minute = minute;
292 bound->time.second = second;
293 bound->time.ns = ns;
294 goto end;
295 }
296
297 /* Try `hh:mm:ss` format */
298 s_ret = sscanf(str, "%u:%u:%u%c", &hour, &minute, &second, &dummy);
299 if (s_ret == 3) {
300 bound->time.hour = hour;
301 bound->time.minute = minute;
302 bound->time.second = second;
303 bound->time.ns = 0;
304 goto end;
305 }
306
307 /* Try `-s.ns` format */
308 s_ret = sscanf(str, "-%u.%u%c", &second, &ns, &dummy);
309 if (s_ret == 2) {
310 bound->ns_from_origin = -((int64_t) second) * NS_PER_S;
311 bound->ns_from_origin -= (int64_t) ns;
312 bound->is_set = true;
313 goto end;
314 }
315
316 /* Try `s.ns` format */
317 s_ret = sscanf(str, "%u.%u%c", &second, &ns, &dummy);
318 if (s_ret == 2) {
319 bound->ns_from_origin = ((int64_t) second) * NS_PER_S;
320 bound->ns_from_origin += (int64_t) ns;
321 bound->is_set = true;
322 goto end;
323 }
324
325 /* Try `-s` format */
326 s_ret = sscanf(str, "-%u%c", &second, &dummy);
327 if (s_ret == 1) {
328 bound->ns_from_origin = -((int64_t) second) * NS_PER_S;
329 bound->is_set = true;
330 goto end;
331 }
332
333 /* Try `s` format */
334 s_ret = sscanf(str, "%u%c", &second, &dummy);
335 if (s_ret == 1) {
336 bound->ns_from_origin = (int64_t) second * NS_PER_S;
337 bound->is_set = true;
338 goto end;
339 }
340
341 BT_LOGE("Invalid date/time format: param=\"%s\"", str);
342 ret = -1;
343
344 end:
345 return ret;
346 }
347
348 /*
349 * Sets a trimmer bound's properties from a parameter string/integer
350 * value.
351 *
352 * Returns a negative value if anything goes wrong.
353 */
354 static
355 int set_bound_from_param(struct trimmer_comp *trimmer_comp,
356 const char *param_name, const bt_value *param,
357 struct trimmer_bound *bound, bool is_gmt)
358 {
359 int ret;
360 const char *arg;
361 char tmp_arg[64];
362
363 if (bt_value_is_signed_integer(param)) {
364 int64_t value = bt_value_signed_integer_get(param);
365
366 /*
367 * Just convert it to a temporary string to handle
368 * everything the same way.
369 */
370 sprintf(tmp_arg, "%" PRId64, value);
371 arg = tmp_arg;
372 } else if (bt_value_is_string(param)) {
373 arg = bt_value_string_get(param);
374 } else {
375 BT_LOGE("`%s` parameter must be an integer or a string value.",
376 param_name);
377 ret = -1;
378 goto end;
379 }
380
381 ret = set_bound_from_str(trimmer_comp, arg, bound, is_gmt);
382
383 end:
384 return ret;
385 }
386
387 static
388 int validate_trimmer_bounds(struct trimmer_comp *trimmer_comp,
389 struct trimmer_bound *begin, struct trimmer_bound *end)
390 {
391 int ret = 0;
392
393 BT_ASSERT(begin->is_set);
394 BT_ASSERT(end->is_set);
395
396 if (!begin->is_infinite && !end->is_infinite &&
397 begin->ns_from_origin > end->ns_from_origin) {
398 BT_LOGE("Trimming time range's beginning time is greater than end time: "
399 "begin-ns-from-origin=%" PRId64 ", "
400 "end-ns-from-origin=%" PRId64,
401 begin->ns_from_origin,
402 end->ns_from_origin);
403 ret = -1;
404 goto end;
405 }
406
407 if (!begin->is_infinite && begin->ns_from_origin == INT64_MIN) {
408 BT_LOGE("Invalid trimming time range's beginning time: "
409 "ns-from-origin=%" PRId64,
410 begin->ns_from_origin);
411 ret = -1;
412 goto end;
413 }
414
415 if (!end->is_infinite && end->ns_from_origin == INT64_MIN) {
416 BT_LOGE("Invalid trimming time range's end time: "
417 "ns-from-origin=%" PRId64,
418 end->ns_from_origin);
419 ret = -1;
420 goto end;
421 }
422
423 end:
424 return ret;
425 }
426
427 static
428 int init_trimmer_comp_from_params(struct trimmer_comp *trimmer_comp,
429 const bt_value *params)
430 {
431 const bt_value *value;
432 int ret = 0;
433
434 BT_ASSERT(params);
435 value = bt_value_map_borrow_entry_value_const(params, "gmt");
436 if (value) {
437 trimmer_comp->is_gmt = (bool) bt_value_bool_get(value);
438 }
439
440 value = bt_value_map_borrow_entry_value_const(params, "begin");
441 if (value) {
442 if (set_bound_from_param(trimmer_comp, "begin", value,
443 &trimmer_comp->begin, trimmer_comp->is_gmt)) {
444 /* set_bound_from_param() logs errors */
445 ret = BT_SELF_COMPONENT_STATUS_ERROR;
446 goto end;
447 }
448 } else {
449 trimmer_comp->begin.is_infinite = true;
450 trimmer_comp->begin.is_set = true;
451 }
452
453 value = bt_value_map_borrow_entry_value_const(params, "end");
454 if (value) {
455 if (set_bound_from_param(trimmer_comp, "end", value,
456 &trimmer_comp->end, trimmer_comp->is_gmt)) {
457 /* set_bound_from_param() logs errors */
458 ret = BT_SELF_COMPONENT_STATUS_ERROR;
459 goto end;
460 }
461 } else {
462 trimmer_comp->end.is_infinite = true;
463 trimmer_comp->end.is_set = true;
464 }
465
466 end:
467 if (trimmer_comp->begin.is_set && trimmer_comp->end.is_set) {
468 /* validate_trimmer_bounds() logs errors */
469 ret = validate_trimmer_bounds(trimmer_comp,
470 &trimmer_comp->begin, &trimmer_comp->end);
471 }
472
473 return ret;
474 }
475
476 bt_self_component_status trimmer_init(bt_self_component_filter *self_comp,
477 const bt_value *params, void *init_data)
478 {
479 int ret;
480 bt_self_component_status status;
481 struct trimmer_comp *trimmer_comp = create_trimmer_comp();
482
483 if (!trimmer_comp) {
484 status = BT_SELF_COMPONENT_STATUS_NOMEM;
485 goto error;
486 }
487
488 trimmer_comp->log_level = bt_component_get_logging_level(
489 bt_self_component_as_component(
490 bt_self_component_filter_as_self_component(self_comp)));
491 status = bt_self_component_filter_add_input_port(
492 self_comp, in_port_name, NULL, NULL);
493 if (status != BT_SELF_COMPONENT_STATUS_OK) {
494 goto error;
495 }
496
497 status = bt_self_component_filter_add_output_port(
498 self_comp, "out", NULL, NULL);
499 if (status != BT_SELF_COMPONENT_STATUS_OK) {
500 goto error;
501 }
502
503 ret = init_trimmer_comp_from_params(trimmer_comp, params);
504 if (ret) {
505 status = BT_SELF_COMPONENT_STATUS_ERROR;
506 goto error;
507 }
508
509 bt_self_component_set_data(
510 bt_self_component_filter_as_self_component(self_comp),
511 trimmer_comp);
512 goto end;
513
514 error:
515 if (status == BT_SELF_COMPONENT_STATUS_OK) {
516 status = BT_SELF_COMPONENT_STATUS_ERROR;
517 }
518
519 if (trimmer_comp) {
520 destroy_trimmer_comp(trimmer_comp);
521 }
522
523 end:
524 return status;
525 }
526
527 static
528 void destroy_trimmer_iterator(struct trimmer_iterator *trimmer_it)
529 {
530 BT_ASSERT(trimmer_it);
531 bt_self_component_port_input_message_iterator_put_ref(
532 trimmer_it->upstream_iter);
533
534 if (trimmer_it->output_messages) {
535 g_queue_free(trimmer_it->output_messages);
536 }
537
538 if (trimmer_it->stream_states) {
539 g_hash_table_destroy(trimmer_it->stream_states);
540 }
541
542 g_free(trimmer_it);
543 }
544
545 static
546 void destroy_trimmer_iterator_stream_state(
547 struct trimmer_iterator_stream_state *sstate)
548 {
549 BT_ASSERT(sstate);
550 BT_PACKET_PUT_REF_AND_RESET(sstate->cur_packet);
551 BT_MESSAGE_PUT_REF_AND_RESET(sstate->stream_beginning_msg);
552 g_free(sstate);
553 }
554
555 BT_HIDDEN
556 bt_self_message_iterator_status trimmer_msg_iter_init(
557 bt_self_message_iterator *self_msg_iter,
558 bt_self_component_filter *self_comp,
559 bt_self_component_port_output *port)
560 {
561 bt_self_message_iterator_status status =
562 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
563 struct trimmer_iterator *trimmer_it;
564
565 trimmer_it = g_new0(struct trimmer_iterator, 1);
566 if (!trimmer_it) {
567 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
568 goto end;
569 }
570
571 trimmer_it->trimmer_comp = bt_self_component_get_data(
572 bt_self_component_filter_as_self_component(self_comp));
573 BT_ASSERT(trimmer_it->trimmer_comp);
574
575 if (trimmer_it->trimmer_comp->begin.is_set &&
576 trimmer_it->trimmer_comp->end.is_set) {
577 /*
578 * Both trimming time range's bounds are set, so skip
579 * the
580 * `TRIMMER_ITERATOR_STATE_SET_BOUNDS_NS_FROM_ORIGIN`
581 * phase.
582 */
583 trimmer_it->state = TRIMMER_ITERATOR_STATE_SEEK_INITIALLY;
584 }
585
586 trimmer_it->begin = trimmer_it->trimmer_comp->begin;
587 trimmer_it->end = trimmer_it->trimmer_comp->end;
588 trimmer_it->upstream_iter =
589 bt_self_component_port_input_message_iterator_create(
590 bt_self_component_filter_borrow_input_port_by_name(
591 self_comp, in_port_name));
592 if (!trimmer_it->upstream_iter) {
593 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
594 goto end;
595 }
596
597 trimmer_it->output_messages = g_queue_new();
598 if (!trimmer_it->output_messages) {
599 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
600 goto end;
601 }
602
603 trimmer_it->stream_states = g_hash_table_new_full(g_direct_hash,
604 g_direct_equal, NULL,
605 (GDestroyNotify) destroy_trimmer_iterator_stream_state);
606 if (!trimmer_it->stream_states) {
607 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
608 goto end;
609 }
610
611 trimmer_it->self_msg_iter = self_msg_iter;
612 bt_self_message_iterator_set_data(self_msg_iter, trimmer_it);
613
614 end:
615 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK && trimmer_it) {
616 destroy_trimmer_iterator(trimmer_it);
617 }
618
619 return status;
620 }
621
622 static inline
623 int get_msg_ns_from_origin(const bt_message *msg, int64_t *ns_from_origin,
624 bool *skip)
625 {
626 const bt_clock_class *clock_class = NULL;
627 const bt_clock_snapshot *clock_snapshot = NULL;
628 bt_message_stream_activity_clock_snapshot_state sa_cs_state;
629 int ret = 0;
630
631 BT_ASSERT(msg);
632 BT_ASSERT(ns_from_origin);
633 BT_ASSERT(skip);
634
635 switch (bt_message_get_type(msg)) {
636 case BT_MESSAGE_TYPE_EVENT:
637 clock_class =
638 bt_message_event_borrow_stream_class_default_clock_class_const(
639 msg);
640 if (G_UNLIKELY(!clock_class)) {
641 goto error;
642 }
643
644 clock_snapshot = bt_message_event_borrow_default_clock_snapshot_const(
645 msg);
646 break;
647 case BT_MESSAGE_TYPE_PACKET_BEGINNING:
648 clock_class =
649 bt_message_packet_beginning_borrow_stream_class_default_clock_class_const(
650 msg);
651 if (G_UNLIKELY(!clock_class)) {
652 goto error;
653 }
654
655 clock_snapshot = bt_message_packet_beginning_borrow_default_clock_snapshot_const(
656 msg);
657 break;
658 case BT_MESSAGE_TYPE_PACKET_END:
659 clock_class =
660 bt_message_packet_end_borrow_stream_class_default_clock_class_const(
661 msg);
662 if (G_UNLIKELY(!clock_class)) {
663 goto error;
664 }
665
666 clock_snapshot = bt_message_packet_end_borrow_default_clock_snapshot_const(
667 msg);
668 break;
669 case BT_MESSAGE_TYPE_DISCARDED_EVENTS:
670 clock_class =
671 bt_message_discarded_events_borrow_stream_class_default_clock_class_const(
672 msg);
673 if (G_UNLIKELY(!clock_class)) {
674 goto error;
675 }
676
677 clock_snapshot = bt_message_discarded_events_borrow_beginning_default_clock_snapshot_const(
678 msg);
679 break;
680 case BT_MESSAGE_TYPE_DISCARDED_PACKETS:
681 clock_class =
682 bt_message_discarded_packets_borrow_stream_class_default_clock_class_const(
683 msg);
684 if (G_UNLIKELY(!clock_class)) {
685 goto error;
686 }
687
688 clock_snapshot = bt_message_discarded_packets_borrow_beginning_default_clock_snapshot_const(
689 msg);
690 break;
691 case BT_MESSAGE_TYPE_STREAM_ACTIVITY_BEGINNING:
692 clock_class =
693 bt_message_stream_activity_beginning_borrow_stream_class_default_clock_class_const(
694 msg);
695 if (G_UNLIKELY(!clock_class)) {
696 goto error;
697 }
698
699 sa_cs_state = bt_message_stream_activity_beginning_borrow_default_clock_snapshot_const(
700 msg, &clock_snapshot);
701 if (sa_cs_state == BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_UNKNOWN ||
702 sa_cs_state == BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_INFINITE) {
703 /* Lowest possible time to always include them */
704 *ns_from_origin = INT64_MIN;
705 goto no_clock_snapshot;
706 }
707
708 break;
709 case BT_MESSAGE_TYPE_STREAM_ACTIVITY_END:
710 clock_class =
711 bt_message_stream_activity_end_borrow_stream_class_default_clock_class_const(
712 msg);
713 if (G_UNLIKELY(!clock_class)) {
714 goto error;
715 }
716
717 sa_cs_state = bt_message_stream_activity_end_borrow_default_clock_snapshot_const(
718 msg, &clock_snapshot);
719 if (sa_cs_state == BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_UNKNOWN) {
720 /* Lowest time to always include it */
721 *ns_from_origin = INT64_MIN;
722 goto no_clock_snapshot;
723 } else if (sa_cs_state == BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_INFINITE) {
724 /* Greatest time to always exclude it */
725 *ns_from_origin = INT64_MAX;
726 goto no_clock_snapshot;
727 }
728
729 break;
730 case BT_MESSAGE_TYPE_MESSAGE_ITERATOR_INACTIVITY:
731 clock_snapshot =
732 bt_message_message_iterator_inactivity_borrow_default_clock_snapshot_const(
733 msg);
734 break;
735 default:
736 goto no_clock_snapshot;
737 }
738
739 ret = bt_clock_snapshot_get_ns_from_origin(clock_snapshot,
740 ns_from_origin);
741 if (G_UNLIKELY(ret)) {
742 goto error;
743 }
744
745 goto end;
746
747 no_clock_snapshot:
748 *skip = true;
749 goto end;
750
751 error:
752 ret = -1;
753
754 end:
755 return ret;
756 }
757
758 static inline
759 void put_messages(bt_message_array_const msgs, uint64_t count)
760 {
761 uint64_t i;
762
763 for (i = 0; i < count; i++) {
764 BT_MESSAGE_PUT_REF_AND_RESET(msgs[i]);
765 }
766 }
767
768 static inline
769 int set_trimmer_iterator_bound(struct trimmer_iterator *trimmer_it,
770 struct trimmer_bound *bound, int64_t ns_from_origin,
771 bool is_gmt)
772 {
773 struct trimmer_comp *trimmer_comp = trimmer_it->trimmer_comp;
774 struct tm tm;
775 time_t time_seconds = (time_t) (ns_from_origin / NS_PER_S);
776 int ret = 0;
777
778 BT_ASSERT(!bound->is_set);
779 errno = 0;
780
781 /* We only need to extract the date from this time */
782 if (is_gmt) {
783 bt_gmtime_r(&time_seconds, &tm);
784 } else {
785 bt_localtime_r(&time_seconds, &tm);
786 }
787
788 if (errno) {
789 BT_LOGE_ERRNO("Cannot convert timestamp to date and time",
790 "ts=%" PRId64, (int64_t) time_seconds);
791 ret = -1;
792 goto end;
793 }
794
795 ret = set_bound_ns_from_origin(bound, tm.tm_year + 1900, tm.tm_mon + 1,
796 tm.tm_mday, bound->time.hour, bound->time.minute,
797 bound->time.second, bound->time.ns, is_gmt);
798
799 end:
800 return ret;
801 }
802
803 static
804 bt_self_message_iterator_status state_set_trimmer_iterator_bounds(
805 struct trimmer_iterator *trimmer_it)
806 {
807 bt_message_iterator_status upstream_iter_status =
808 BT_MESSAGE_ITERATOR_STATUS_OK;
809 struct trimmer_comp *trimmer_comp = trimmer_it->trimmer_comp;
810 bt_message_array_const msgs;
811 uint64_t count = 0;
812 int64_t ns_from_origin = INT64_MIN;
813 uint64_t i;
814 int ret;
815
816 BT_ASSERT(!trimmer_it->begin.is_set ||
817 !trimmer_it->end.is_set);
818
819 while (true) {
820 upstream_iter_status =
821 bt_self_component_port_input_message_iterator_next(
822 trimmer_it->upstream_iter, &msgs, &count);
823 if (upstream_iter_status != BT_MESSAGE_ITERATOR_STATUS_OK) {
824 goto end;
825 }
826
827 for (i = 0; i < count; i++) {
828 const bt_message *msg = msgs[i];
829 bool skip = false;
830 int ret;
831
832 ret = get_msg_ns_from_origin(msg, &ns_from_origin,
833 &skip);
834 if (ret) {
835 goto error;
836 }
837
838 if (skip) {
839 continue;
840 }
841
842 BT_ASSERT(ns_from_origin != INT64_MIN &&
843 ns_from_origin != INT64_MAX);
844 put_messages(msgs, count);
845 goto found;
846 }
847
848 put_messages(msgs, count);
849 }
850
851 found:
852 if (!trimmer_it->begin.is_set) {
853 BT_ASSERT(!trimmer_it->begin.is_infinite);
854 ret = set_trimmer_iterator_bound(trimmer_it, &trimmer_it->begin,
855 ns_from_origin, trimmer_comp->is_gmt);
856 if (ret) {
857 goto error;
858 }
859 }
860
861 if (!trimmer_it->end.is_set) {
862 BT_ASSERT(!trimmer_it->end.is_infinite);
863 ret = set_trimmer_iterator_bound(trimmer_it, &trimmer_it->end,
864 ns_from_origin, trimmer_comp->is_gmt);
865 if (ret) {
866 goto error;
867 }
868 }
869
870 ret = validate_trimmer_bounds(trimmer_it->trimmer_comp,
871 &trimmer_it->begin, &trimmer_it->end);
872 if (ret) {
873 goto error;
874 }
875
876 goto end;
877
878 error:
879 put_messages(msgs, count);
880 upstream_iter_status = BT_MESSAGE_ITERATOR_STATUS_ERROR;
881
882 end:
883 return (int) upstream_iter_status;
884 }
885
886 static
887 bt_self_message_iterator_status state_seek_initially(
888 struct trimmer_iterator *trimmer_it)
889 {
890 struct trimmer_comp *trimmer_comp = trimmer_it->trimmer_comp;
891 bt_self_message_iterator_status status =
892 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
893
894 BT_ASSERT(trimmer_it->begin.is_set);
895
896 if (trimmer_it->begin.is_infinite) {
897 if (!bt_self_component_port_input_message_iterator_can_seek_beginning(
898 trimmer_it->upstream_iter)) {
899 BT_LOGE_STR("Cannot make upstream message iterator initially seek its beginning.");
900 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
901 goto end;
902 }
903
904 status = (int) bt_self_component_port_input_message_iterator_seek_beginning(
905 trimmer_it->upstream_iter);
906 } else {
907 if (!bt_self_component_port_input_message_iterator_can_seek_ns_from_origin(
908 trimmer_it->upstream_iter,
909 trimmer_it->begin.ns_from_origin)) {
910 BT_LOGE("Cannot make upstream message iterator initially seek: "
911 "seek-ns-from-origin=%" PRId64,
912 trimmer_it->begin.ns_from_origin);
913 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
914 goto end;
915 }
916
917 status = (int) bt_self_component_port_input_message_iterator_seek_ns_from_origin(
918 trimmer_it->upstream_iter, trimmer_it->begin.ns_from_origin);
919 }
920
921 if (status == BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
922 trimmer_it->state = TRIMMER_ITERATOR_STATE_TRIM;
923 }
924
925 end:
926 return status;
927 }
928
929 static inline
930 void push_message(struct trimmer_iterator *trimmer_it, const bt_message *msg)
931 {
932 g_queue_push_head(trimmer_it->output_messages, (void *) msg);
933 }
934
935 static inline
936 const bt_message *pop_message(struct trimmer_iterator *trimmer_it)
937 {
938 return g_queue_pop_tail(trimmer_it->output_messages);
939 }
940
941 static inline
942 int clock_raw_value_from_ns_from_origin(const bt_clock_class *clock_class,
943 int64_t ns_from_origin, uint64_t *raw_value)
944 {
945
946 int64_t cc_offset_s;
947 uint64_t cc_offset_cycles;
948 uint64_t cc_freq;
949
950 bt_clock_class_get_offset(clock_class, &cc_offset_s, &cc_offset_cycles);
951 cc_freq = bt_clock_class_get_frequency(clock_class);
952 return bt_common_clock_value_from_ns_from_origin(cc_offset_s,
953 cc_offset_cycles, cc_freq, ns_from_origin, raw_value);
954 }
955
956 static inline
957 bt_self_message_iterator_status end_stream(struct trimmer_iterator *trimmer_it,
958 struct trimmer_iterator_stream_state *sstate)
959 {
960 bt_self_message_iterator_status status =
961 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
962 uint64_t raw_value;
963 const bt_clock_class *clock_class;
964 int ret;
965 bt_message *msg = NULL;
966
967 BT_ASSERT(!trimmer_it->end.is_infinite);
968
969 if (!sstate->stream) {
970 goto end;
971 }
972
973 if (sstate->cur_packet) {
974 /*
975 * The last message could not have been a stream
976 * activity end message if we have a current packet.
977 */
978 BT_ASSERT(!sstate->last_msg_is_stream_activity_end);
979
980 /*
981 * Create and push a packet end message, making its time
982 * the trimming range's end time.
983 */
984 clock_class = bt_stream_class_borrow_default_clock_class_const(
985 bt_stream_borrow_class_const(sstate->stream));
986 BT_ASSERT(clock_class);
987 ret = clock_raw_value_from_ns_from_origin(clock_class,
988 trimmer_it->end.ns_from_origin, &raw_value);
989 if (ret) {
990 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
991 goto end;
992 }
993
994 msg = bt_message_packet_end_create_with_default_clock_snapshot(
995 trimmer_it->self_msg_iter, sstate->cur_packet,
996 raw_value);
997 if (!msg) {
998 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
999 goto end;
1000 }
1001
1002 push_message(trimmer_it, msg);
1003 msg = NULL;
1004 BT_PACKET_PUT_REF_AND_RESET(sstate->cur_packet);
1005
1006 /*
1007 * Because we generated a packet end message, set the
1008 * stream activity end message's time to use to the
1009 * trimming range's end time (this packet end message's
1010 * time).
1011 */
1012 sstate->stream_act_end_ns_from_origin =
1013 trimmer_it->end.ns_from_origin;
1014 }
1015
1016 if (!sstate->last_msg_is_stream_activity_end) {
1017 /* Create and push a stream activity end message */
1018 msg = bt_message_stream_activity_end_create(
1019 trimmer_it->self_msg_iter, sstate->stream);
1020 if (!msg) {
1021 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1022 goto end;
1023 }
1024
1025 clock_class = bt_stream_class_borrow_default_clock_class_const(
1026 bt_stream_borrow_class_const(sstate->stream));
1027 BT_ASSERT(clock_class);
1028
1029 if (sstate->stream_act_end_ns_from_origin == INT64_MIN) {
1030 /*
1031 * We received at least what is necessary to
1032 * have a stream state (stream beginning and
1033 * stream activity beginning messages), but
1034 * nothing else: use the trimmer range's end
1035 * time.
1036 */
1037 sstate->stream_act_end_ns_from_origin =
1038 trimmer_it->end.ns_from_origin;
1039 }
1040
1041 ret = clock_raw_value_from_ns_from_origin(clock_class,
1042 sstate->stream_act_end_ns_from_origin, &raw_value);
1043 if (ret) {
1044 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1045 goto end;
1046 }
1047
1048 bt_message_stream_activity_end_set_default_clock_snapshot(
1049 msg, raw_value);
1050 push_message(trimmer_it, msg);
1051 msg = NULL;
1052 }
1053
1054 /* Create and push a stream end message */
1055 msg = bt_message_stream_end_create(trimmer_it->self_msg_iter,
1056 sstate->stream);
1057 if (!msg) {
1058 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1059 goto end;
1060 }
1061
1062 push_message(trimmer_it, msg);
1063 msg = NULL;
1064
1065 /*
1066 * Just to make sure that we don't use this stream state again
1067 * in the future without an obvious error.
1068 */
1069 sstate->stream = NULL;
1070
1071 end:
1072 bt_message_put_ref(msg);
1073 return status;
1074 }
1075
1076 static inline
1077 bt_self_message_iterator_status end_iterator_streams(
1078 struct trimmer_iterator *trimmer_it)
1079 {
1080 bt_self_message_iterator_status status =
1081 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1082 GHashTableIter iter;
1083 gpointer key, sstate;
1084
1085 if (trimmer_it->end.is_infinite) {
1086 /*
1087 * An infinite trimming range's end time guarantees that
1088 * we received (and pushed) all the appropriate end
1089 * messages.
1090 */
1091 goto remove_all;
1092 }
1093
1094 /*
1095 * End each stream and then remove them from the hash table of
1096 * stream states to release unneeded references.
1097 */
1098 g_hash_table_iter_init(&iter, trimmer_it->stream_states);
1099
1100 while (g_hash_table_iter_next(&iter, &key, &sstate)) {
1101 status = end_stream(trimmer_it, sstate);
1102 if (status) {
1103 goto end;
1104 }
1105 }
1106
1107 remove_all:
1108 g_hash_table_remove_all(trimmer_it->stream_states);
1109
1110 end:
1111 return status;
1112 }
1113
1114 static inline
1115 bt_self_message_iterator_status create_stream_beginning_activity_message(
1116 struct trimmer_iterator *trimmer_it,
1117 const bt_stream *stream,
1118 const bt_clock_class *clock_class, bt_message **msg)
1119 {
1120 bt_message *local_msg;
1121 bt_self_message_iterator_status status =
1122 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1123
1124 BT_ASSERT(msg);
1125 BT_ASSERT(!trimmer_it->begin.is_infinite);
1126
1127 local_msg = bt_message_stream_activity_beginning_create(
1128 trimmer_it->self_msg_iter, stream);
1129 if (!local_msg) {
1130 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1131 goto end;
1132 }
1133
1134 if (clock_class) {
1135 int ret;
1136 uint64_t raw_value;
1137
1138 ret = clock_raw_value_from_ns_from_origin(clock_class,
1139 trimmer_it->begin.ns_from_origin, &raw_value);
1140 if (ret) {
1141 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1142 bt_message_put_ref(local_msg);
1143 goto end;
1144 }
1145
1146 bt_message_stream_activity_beginning_set_default_clock_snapshot(
1147 local_msg, raw_value);
1148 }
1149
1150 BT_MESSAGE_MOVE_REF(*msg, local_msg);
1151
1152 end:
1153 return status;
1154 }
1155
1156 /*
1157 * Makes sure to initialize a stream state, pushing the appropriate
1158 * initial messages.
1159 *
1160 * `stream_act_beginning_msg` is an initial stream activity beginning
1161 * message to potentially use, depending on its clock snapshot state.
1162 * This function consumes `stream_act_beginning_msg` unconditionally.
1163 */
1164 static inline
1165 bt_self_message_iterator_status ensure_stream_state_is_inited(
1166 struct trimmer_iterator *trimmer_it,
1167 struct trimmer_iterator_stream_state *sstate,
1168 const bt_message *stream_act_beginning_msg)
1169 {
1170 bt_self_message_iterator_status status =
1171 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1172 bt_message *new_msg = NULL;
1173 const bt_clock_class *clock_class =
1174 bt_stream_class_borrow_default_clock_class_const(
1175 bt_stream_borrow_class_const(sstate->stream));
1176
1177 BT_ASSERT(!sstate->inited);
1178
1179 if (!sstate->stream_beginning_msg) {
1180 /* No initial stream beginning message: create one */
1181 sstate->stream_beginning_msg =
1182 bt_message_stream_beginning_create(
1183 trimmer_it->self_msg_iter, sstate->stream);
1184 if (!sstate->stream_beginning_msg) {
1185 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1186 goto end;
1187 }
1188 }
1189
1190 /* Push initial stream beginning message */
1191 BT_ASSERT(sstate->stream_beginning_msg);
1192 push_message(trimmer_it, sstate->stream_beginning_msg);
1193 sstate->stream_beginning_msg = NULL;
1194
1195 if (stream_act_beginning_msg) {
1196 /*
1197 * Initial stream activity beginning message exists: if
1198 * its time is -inf, then create and push a new one
1199 * having the trimming range's beginning time. Otherwise
1200 * push it as is (known and unknown).
1201 */
1202 const bt_clock_snapshot *cs;
1203 bt_message_stream_activity_clock_snapshot_state sa_cs_state;
1204
1205 sa_cs_state = bt_message_stream_activity_beginning_borrow_default_clock_snapshot_const(
1206 stream_act_beginning_msg, &cs);
1207 if (sa_cs_state == BT_MESSAGE_STREAM_ACTIVITY_CLOCK_SNAPSHOT_STATE_INFINITE &&
1208 !trimmer_it->begin.is_infinite) {
1209 /*
1210 * -inf time: use trimming range's beginning
1211 * time (which is not -inf).
1212 */
1213 status = create_stream_beginning_activity_message(
1214 trimmer_it, sstate->stream, clock_class,
1215 &new_msg);
1216 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1217 goto end;
1218 }
1219
1220 push_message(trimmer_it, new_msg);
1221 new_msg = NULL;
1222 } else {
1223 /* Known/unknown: push as is */
1224 push_message(trimmer_it, stream_act_beginning_msg);
1225 stream_act_beginning_msg = NULL;
1226 }
1227 } else {
1228 BT_ASSERT(!trimmer_it->begin.is_infinite);
1229
1230 /*
1231 * No stream beginning activity message: create and push
1232 * a new message.
1233 */
1234 status = create_stream_beginning_activity_message(
1235 trimmer_it, sstate->stream, clock_class, &new_msg);
1236 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1237 goto end;
1238 }
1239
1240 push_message(trimmer_it, new_msg);
1241 new_msg = NULL;
1242 }
1243
1244 sstate->inited = true;
1245
1246 end:
1247 bt_message_put_ref(new_msg);
1248 bt_message_put_ref(stream_act_beginning_msg);
1249 return status;
1250 }
1251
1252 static inline
1253 bt_self_message_iterator_status ensure_cur_packet_exists(
1254 struct trimmer_iterator *trimmer_it,
1255 struct trimmer_iterator_stream_state *sstate,
1256 const bt_packet *packet)
1257 {
1258 bt_self_message_iterator_status status =
1259 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1260 int ret;
1261 const bt_clock_class *clock_class =
1262 bt_stream_class_borrow_default_clock_class_const(
1263 bt_stream_borrow_class_const(sstate->stream));
1264 bt_message *msg = NULL;
1265 uint64_t raw_value;
1266
1267 BT_ASSERT(!trimmer_it->begin.is_infinite);
1268 BT_ASSERT(!sstate->cur_packet);
1269
1270 /*
1271 * Create and push an initial packet beginning message,
1272 * making its time the trimming range's beginning time.
1273 */
1274 ret = clock_raw_value_from_ns_from_origin(clock_class,
1275 trimmer_it->begin.ns_from_origin, &raw_value);
1276 if (ret) {
1277 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1278 goto end;
1279 }
1280
1281 msg = bt_message_packet_beginning_create_with_default_clock_snapshot(
1282 trimmer_it->self_msg_iter, packet, raw_value);
1283 if (!msg) {
1284 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1285 goto end;
1286 }
1287
1288 push_message(trimmer_it, msg);
1289 msg = NULL;
1290
1291 /* Set packet as this stream's current packet */
1292 sstate->cur_packet = packet;
1293 bt_packet_get_ref(sstate->cur_packet);
1294
1295 end:
1296 bt_message_put_ref(msg);
1297 return status;
1298 }
1299
1300 /*
1301 * Handles a message which is associated to a given stream state. This
1302 * _could_ make the iterator's output message queue grow; this could
1303 * also consume the message without pushing anything to this queue, only
1304 * modifying the stream state.
1305 *
1306 * This function consumes the `msg` reference, _whatever the outcome_.
1307 *
1308 * `ns_from_origin` is the message's time, as given by
1309 * get_msg_ns_from_origin().
1310 *
1311 * This function sets `reached_end` if handling this message made the
1312 * iterator reach the end of the trimming range. Note that the output
1313 * message queue could contain messages even if this function sets
1314 * `reached_end`.
1315 */
1316 static inline
1317 bt_self_message_iterator_status handle_message_with_stream_state(
1318 struct trimmer_iterator *trimmer_it, const bt_message *msg,
1319 struct trimmer_iterator_stream_state *sstate,
1320 int64_t ns_from_origin, bool *reached_end)
1321 {
1322 bt_self_message_iterator_status status =
1323 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1324 bt_message_type msg_type = bt_message_get_type(msg);
1325 int ret;
1326
1327 switch (msg_type) {
1328 case BT_MESSAGE_TYPE_EVENT:
1329 if (G_UNLIKELY(!trimmer_it->end.is_infinite &&
1330 ns_from_origin > trimmer_it->end.ns_from_origin)) {
1331 status = end_iterator_streams(trimmer_it);
1332 *reached_end = true;
1333 break;
1334 }
1335
1336 if (G_UNLIKELY(!sstate->inited)) {
1337 status = ensure_stream_state_is_inited(trimmer_it,
1338 sstate, NULL);
1339 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1340 goto end;
1341 }
1342 }
1343
1344 if (G_UNLIKELY(!sstate->cur_packet)) {
1345 const bt_event *event =
1346 bt_message_event_borrow_event_const(msg);
1347 const bt_packet *packet = bt_event_borrow_packet_const(
1348 event);
1349
1350 status = ensure_cur_packet_exists(trimmer_it, sstate,
1351 packet);
1352 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1353 goto end;
1354 }
1355 }
1356
1357 BT_ASSERT(sstate->cur_packet);
1358 push_message(trimmer_it, msg);
1359 msg = NULL;
1360 break;
1361 case BT_MESSAGE_TYPE_PACKET_BEGINNING:
1362 if (G_UNLIKELY(!trimmer_it->end.is_infinite &&
1363 ns_from_origin > trimmer_it->end.ns_from_origin)) {
1364 status = end_iterator_streams(trimmer_it);
1365 *reached_end = true;
1366 break;
1367 }
1368
1369 if (G_UNLIKELY(!sstate->inited)) {
1370 status = ensure_stream_state_is_inited(trimmer_it,
1371 sstate, NULL);
1372 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1373 goto end;
1374 }
1375 }
1376
1377 BT_ASSERT(!sstate->cur_packet);
1378 sstate->cur_packet =
1379 bt_message_packet_beginning_borrow_packet_const(msg);
1380 bt_packet_get_ref(sstate->cur_packet);
1381 push_message(trimmer_it, msg);
1382 msg = NULL;
1383 break;
1384 case BT_MESSAGE_TYPE_PACKET_END:
1385 sstate->stream_act_end_ns_from_origin = ns_from_origin;
1386
1387 if (G_UNLIKELY(!trimmer_it->end.is_infinite &&
1388 ns_from_origin > trimmer_it->end.ns_from_origin)) {
1389 status = end_iterator_streams(trimmer_it);
1390 *reached_end = true;
1391 break;
1392 }
1393
1394 if (G_UNLIKELY(!sstate->inited)) {
1395 status = ensure_stream_state_is_inited(trimmer_it,
1396 sstate, NULL);
1397 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1398 goto end;
1399 }
1400 }
1401
1402 if (G_UNLIKELY(!sstate->cur_packet)) {
1403 const bt_packet *packet =
1404 bt_message_packet_end_borrow_packet_const(msg);
1405
1406 status = ensure_cur_packet_exists(trimmer_it, sstate,
1407 packet);
1408 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1409 goto end;
1410 }
1411 }
1412
1413 BT_ASSERT(sstate->cur_packet);
1414 BT_PACKET_PUT_REF_AND_RESET(sstate->cur_packet);
1415 push_message(trimmer_it, msg);
1416 msg = NULL;
1417 break;
1418 case BT_MESSAGE_TYPE_DISCARDED_EVENTS:
1419 case BT_MESSAGE_TYPE_DISCARDED_PACKETS:
1420 {
1421 /*
1422 * `ns_from_origin` is the message's time range's
1423 * beginning time here.
1424 */
1425 int64_t end_ns_from_origin;
1426 const bt_clock_snapshot *end_cs;
1427
1428 if (bt_message_get_type(msg) ==
1429 BT_MESSAGE_TYPE_DISCARDED_EVENTS) {
1430 /*
1431 * Safe to ignore the return value because we
1432 * know there's a default clock and it's always
1433 * known.
1434 */
1435 end_cs = bt_message_discarded_events_borrow_end_default_clock_snapshot_const(
1436 msg);
1437 } else {
1438 /*
1439 * Safe to ignore the return value because we
1440 * know there's a default clock and it's always
1441 * known.
1442 */
1443 end_cs = bt_message_discarded_packets_borrow_end_default_clock_snapshot_const(
1444 msg);
1445 }
1446
1447 if (bt_clock_snapshot_get_ns_from_origin(end_cs,
1448 &end_ns_from_origin)) {
1449 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1450 goto end;
1451 }
1452
1453 sstate->stream_act_end_ns_from_origin = end_ns_from_origin;
1454
1455 if (!trimmer_it->end.is_infinite &&
1456 ns_from_origin > trimmer_it->end.ns_from_origin) {
1457 status = end_iterator_streams(trimmer_it);
1458 *reached_end = true;
1459 break;
1460 }
1461
1462 if (!trimmer_it->end.is_infinite &&
1463 end_ns_from_origin > trimmer_it->end.ns_from_origin) {
1464 /*
1465 * This message's end time is outside the
1466 * trimming time range: replace it with a new
1467 * message having an end time equal to the
1468 * trimming time range's end and without a
1469 * count.
1470 */
1471 const bt_clock_class *clock_class =
1472 bt_clock_snapshot_borrow_clock_class_const(
1473 end_cs);
1474 const bt_clock_snapshot *begin_cs;
1475 bt_message *new_msg;
1476 uint64_t end_raw_value;
1477
1478 ret = clock_raw_value_from_ns_from_origin(clock_class,
1479 trimmer_it->end.ns_from_origin, &end_raw_value);
1480 if (ret) {
1481 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1482 goto end;
1483 }
1484
1485 if (msg_type == BT_MESSAGE_TYPE_DISCARDED_EVENTS) {
1486 begin_cs = bt_message_discarded_events_borrow_beginning_default_clock_snapshot_const(
1487 msg);
1488 new_msg = bt_message_discarded_events_create_with_default_clock_snapshots(
1489 trimmer_it->self_msg_iter,
1490 sstate->stream,
1491 bt_clock_snapshot_get_value(begin_cs),
1492 end_raw_value);
1493 } else {
1494 begin_cs = bt_message_discarded_packets_borrow_beginning_default_clock_snapshot_const(
1495 msg);
1496 new_msg = bt_message_discarded_packets_create_with_default_clock_snapshots(
1497 trimmer_it->self_msg_iter,
1498 sstate->stream,
1499 bt_clock_snapshot_get_value(begin_cs),
1500 end_raw_value);
1501 }
1502
1503 if (!new_msg) {
1504 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1505 goto end;
1506 }
1507
1508 /* Replace the original message */
1509 BT_MESSAGE_MOVE_REF(msg, new_msg);
1510 }
1511
1512 if (G_UNLIKELY(!sstate->inited)) {
1513 status = ensure_stream_state_is_inited(trimmer_it,
1514 sstate, NULL);
1515 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1516 goto end;
1517 }
1518 }
1519
1520 push_message(trimmer_it, msg);
1521 msg = NULL;
1522 break;
1523 }
1524 case BT_MESSAGE_TYPE_STREAM_ACTIVITY_BEGINNING:
1525 if (!trimmer_it->end.is_infinite &&
1526 ns_from_origin > trimmer_it->end.ns_from_origin) {
1527 /*
1528 * This only happens when the message's time is
1529 * known and is greater than the trimming
1530 * range's end time. Unknown and -inf times are
1531 * always less than
1532 * `trimmer_it->end.ns_from_origin`.
1533 */
1534 status = end_iterator_streams(trimmer_it);
1535 *reached_end = true;
1536 break;
1537 }
1538
1539 if (!sstate->inited) {
1540 status = ensure_stream_state_is_inited(trimmer_it,
1541 sstate, msg);
1542 msg = NULL;
1543 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1544 goto end;
1545 }
1546 } else {
1547 push_message(trimmer_it, msg);
1548 msg = NULL;
1549 }
1550
1551 break;
1552 case BT_MESSAGE_TYPE_STREAM_ACTIVITY_END:
1553 if (trimmer_it->end.is_infinite) {
1554 push_message(trimmer_it, msg);
1555 msg = NULL;
1556 break;
1557 }
1558
1559 if (ns_from_origin == INT64_MIN) {
1560 /* Unknown: push as is if stream state is inited */
1561 if (sstate->inited) {
1562 push_message(trimmer_it, msg);
1563 msg = NULL;
1564 sstate->last_msg_is_stream_activity_end = true;
1565 }
1566 } else if (ns_from_origin == INT64_MAX) {
1567 /* Infinite: use trimming range's end time */
1568 sstate->stream_act_end_ns_from_origin =
1569 trimmer_it->end.ns_from_origin;
1570 } else {
1571 /* Known: check if outside of trimming range */
1572 if (ns_from_origin > trimmer_it->end.ns_from_origin) {
1573 sstate->stream_act_end_ns_from_origin =
1574 trimmer_it->end.ns_from_origin;
1575 status = end_iterator_streams(trimmer_it);
1576 *reached_end = true;
1577 break;
1578 }
1579
1580 if (!sstate->inited) {
1581 /*
1582 * First message for this stream is a
1583 * stream activity end: we can't deduce
1584 * anything about the stream activity
1585 * beginning's time, and using this
1586 * message's time would make a useless
1587 * pair of stream activity beginning/end
1588 * with the same time. Just skip this
1589 * message and wait for something
1590 * useful.
1591 */
1592 break;
1593 }
1594
1595 push_message(trimmer_it, msg);
1596 msg = NULL;
1597 sstate->last_msg_is_stream_activity_end = true;
1598 sstate->stream_act_end_ns_from_origin = ns_from_origin;
1599 }
1600
1601 break;
1602 case BT_MESSAGE_TYPE_STREAM_BEGINNING:
1603 /*
1604 * We don't know what follows at this point, so just
1605 * keep this message until we know what to do with it
1606 * (it will be used in ensure_stream_state_is_inited()).
1607 */
1608 BT_ASSERT(!sstate->inited);
1609 BT_MESSAGE_MOVE_REF(sstate->stream_beginning_msg, msg);
1610 break;
1611 case BT_MESSAGE_TYPE_STREAM_END:
1612 if (sstate->inited) {
1613 /*
1614 * This is the end of an inited stream: end this
1615 * stream if its stream activity end message
1616 * time is not the trimming range's end time
1617 * (which means the final stream activity end
1618 * message had an infinite time). end_stream()
1619 * will generate its own stream end message.
1620 */
1621 if (trimmer_it->end.is_infinite) {
1622 push_message(trimmer_it, msg);
1623 msg = NULL;
1624 g_hash_table_remove(trimmer_it->stream_states,
1625 sstate->stream);
1626 } else if (sstate->stream_act_end_ns_from_origin <
1627 trimmer_it->end.ns_from_origin) {
1628 status = end_stream(trimmer_it, sstate);
1629 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1630 goto end;
1631 }
1632
1633 /* We won't need this stream state again */
1634 g_hash_table_remove(trimmer_it->stream_states,
1635 sstate->stream);
1636 }
1637 } else {
1638 /* We dont't need this stream state anymore */
1639 g_hash_table_remove(trimmer_it->stream_states, sstate->stream);
1640 }
1641
1642 break;
1643 default:
1644 break;
1645 }
1646
1647 end:
1648 /* We release the message's reference whatever the outcome */
1649 bt_message_put_ref(msg);
1650 return BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1651 }
1652
1653 /*
1654 * Handles an input message. This _could_ make the iterator's output
1655 * message queue grow; this could also consume the message without
1656 * pushing anything to this queue, only modifying the stream state.
1657 *
1658 * This function consumes the `msg` reference, _whatever the outcome_.
1659 *
1660 * This function sets `reached_end` if handling this message made the
1661 * iterator reach the end of the trimming range. Note that the output
1662 * message queue could contain messages even if this function sets
1663 * `reached_end`.
1664 */
1665 static inline
1666 bt_self_message_iterator_status handle_message(
1667 struct trimmer_iterator *trimmer_it, const bt_message *msg,
1668 bool *reached_end)
1669 {
1670 bt_self_message_iterator_status status;
1671 const bt_stream *stream = NULL;
1672 int64_t ns_from_origin = INT64_MIN;
1673 bool skip;
1674 int ret;
1675 struct trimmer_iterator_stream_state *sstate = NULL;
1676 struct trimmer_comp *trimmer_comp = trimmer_it->trimmer_comp;
1677
1678 /* Find message's associated stream */
1679 switch (bt_message_get_type(msg)) {
1680 case BT_MESSAGE_TYPE_EVENT:
1681 stream = bt_event_borrow_stream_const(
1682 bt_message_event_borrow_event_const(msg));
1683 break;
1684 case BT_MESSAGE_TYPE_PACKET_BEGINNING:
1685 stream = bt_packet_borrow_stream_const(
1686 bt_message_packet_beginning_borrow_packet_const(msg));
1687 break;
1688 case BT_MESSAGE_TYPE_PACKET_END:
1689 stream = bt_packet_borrow_stream_const(
1690 bt_message_packet_end_borrow_packet_const(msg));
1691 break;
1692 case BT_MESSAGE_TYPE_DISCARDED_EVENTS:
1693 stream = bt_message_discarded_events_borrow_stream_const(msg);
1694 break;
1695 case BT_MESSAGE_TYPE_DISCARDED_PACKETS:
1696 stream = bt_message_discarded_packets_borrow_stream_const(msg);
1697 break;
1698 case BT_MESSAGE_TYPE_STREAM_ACTIVITY_BEGINNING:
1699 stream = bt_message_stream_activity_beginning_borrow_stream_const(msg);
1700 break;
1701 case BT_MESSAGE_TYPE_STREAM_ACTIVITY_END:
1702 stream = bt_message_stream_activity_end_borrow_stream_const(msg);
1703 break;
1704 case BT_MESSAGE_TYPE_STREAM_BEGINNING:
1705 stream = bt_message_stream_beginning_borrow_stream_const(msg);
1706 break;
1707 case BT_MESSAGE_TYPE_STREAM_END:
1708 stream = bt_message_stream_end_borrow_stream_const(msg);
1709 break;
1710 default:
1711 break;
1712 }
1713
1714 if (G_LIKELY(stream)) {
1715 /* Find stream state */
1716 sstate = g_hash_table_lookup(trimmer_it->stream_states,
1717 stream);
1718 if (G_UNLIKELY(!sstate)) {
1719 /* No stream state yet: create one now */
1720 const bt_stream_class *sc;
1721
1722 /*
1723 * Validate right now that the stream's class
1724 * has a registered default clock class so that
1725 * an existing stream state guarantees existing
1726 * default clock snapshots for its associated
1727 * messages.
1728 *
1729 * Also check that clock snapshots are always
1730 * known.
1731 */
1732 sc = bt_stream_borrow_class_const(stream);
1733 if (!bt_stream_class_borrow_default_clock_class_const(sc)) {
1734 BT_LOGE("Unsupported stream: stream class does "
1735 "not have a default clock class: "
1736 "stream-addr=%p, "
1737 "stream-id=%" PRIu64 ", "
1738 "stream-name=\"%s\"",
1739 stream, bt_stream_get_id(stream),
1740 bt_stream_get_name(stream));
1741 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1742 goto end;
1743 }
1744
1745 /*
1746 * Temporary: make sure packet beginning, packet
1747 * end, discarded events, and discarded packets
1748 * messages have default clock snapshots until
1749 * the support for not having them is
1750 * implemented.
1751 */
1752 if (!bt_stream_class_packets_have_beginning_default_clock_snapshot(
1753 sc)) {
1754 BT_LOGE("Unsupported stream: packets have "
1755 "no beginning clock snapshot: "
1756 "stream-addr=%p, "
1757 "stream-id=%" PRIu64 ", "
1758 "stream-name=\"%s\"",
1759 stream, bt_stream_get_id(stream),
1760 bt_stream_get_name(stream));
1761 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1762 goto end;
1763 }
1764
1765 if (!bt_stream_class_packets_have_end_default_clock_snapshot(
1766 sc)) {
1767 BT_LOGE("Unsupported stream: packets have "
1768 "no end clock snapshot: "
1769 "stream-addr=%p, "
1770 "stream-id=%" PRIu64 ", "
1771 "stream-name=\"%s\"",
1772 stream, bt_stream_get_id(stream),
1773 bt_stream_get_name(stream));
1774 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1775 goto end;
1776 }
1777
1778 if (bt_stream_class_supports_discarded_events(sc) &&
1779 !bt_stream_class_discarded_events_have_default_clock_snapshots(sc)) {
1780 BT_LOGE("Unsupported stream: discarded events "
1781 "have no clock snapshots: "
1782 "stream-addr=%p, "
1783 "stream-id=%" PRIu64 ", "
1784 "stream-name=\"%s\"",
1785 stream, bt_stream_get_id(stream),
1786 bt_stream_get_name(stream));
1787 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1788 goto end;
1789 }
1790
1791 if (bt_stream_class_supports_discarded_packets(sc) &&
1792 !bt_stream_class_discarded_packets_have_default_clock_snapshots(sc)) {
1793 BT_LOGE("Unsupported stream: discarded packets "
1794 "have no clock snapshots: "
1795 "stream-addr=%p, "
1796 "stream-id=%" PRIu64 ", "
1797 "stream-name=\"%s\"",
1798 stream, bt_stream_get_id(stream),
1799 bt_stream_get_name(stream));
1800 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1801 goto end;
1802 }
1803
1804 sstate = g_new0(struct trimmer_iterator_stream_state,
1805 1);
1806 if (!sstate) {
1807 status = BT_SELF_MESSAGE_ITERATOR_STATUS_NOMEM;
1808 goto end;
1809 }
1810
1811 sstate->stream = stream;
1812 sstate->stream_act_end_ns_from_origin = INT64_MIN;
1813 g_hash_table_insert(trimmer_it->stream_states,
1814 (void *) stream, sstate);
1815 }
1816 }
1817
1818 /* Retrieve the message's time */
1819 ret = get_msg_ns_from_origin(msg, &ns_from_origin, &skip);
1820 if (G_UNLIKELY(ret)) {
1821 status = BT_SELF_MESSAGE_ITERATOR_STATUS_ERROR;
1822 goto end;
1823 }
1824
1825 if (G_LIKELY(sstate)) {
1826 /* Message associated to a stream */
1827 status = handle_message_with_stream_state(trimmer_it, msg,
1828 sstate, ns_from_origin, reached_end);
1829
1830 /*
1831 * handle_message_with_stream_state() unconditionally
1832 * consumes `msg`.
1833 */
1834 msg = NULL;
1835 } else {
1836 /*
1837 * Message not associated to a stream (message iterator
1838 * inactivity).
1839 */
1840 if (G_UNLIKELY(ns_from_origin > trimmer_it->end.ns_from_origin)) {
1841 BT_MESSAGE_PUT_REF_AND_RESET(msg);
1842 status = end_iterator_streams(trimmer_it);
1843 *reached_end = true;
1844 } else {
1845 push_message(trimmer_it, msg);
1846 status = BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1847 msg = NULL;
1848 }
1849 }
1850
1851 end:
1852 /* We release the message's reference whatever the outcome */
1853 bt_message_put_ref(msg);
1854 return status;
1855 }
1856
1857 static inline
1858 void fill_message_array_from_output_messages(
1859 struct trimmer_iterator *trimmer_it,
1860 bt_message_array_const msgs, uint64_t capacity, uint64_t *count)
1861 {
1862 *count = 0;
1863
1864 /*
1865 * Move auto-seek messages to the output array (which is this
1866 * iterator's base message array).
1867 */
1868 while (capacity > 0 && !g_queue_is_empty(trimmer_it->output_messages)) {
1869 msgs[*count] = pop_message(trimmer_it);
1870 capacity--;
1871 (*count)++;
1872 }
1873
1874 BT_ASSERT(*count > 0);
1875 }
1876
1877 static inline
1878 bt_self_message_iterator_status state_ending(
1879 struct trimmer_iterator *trimmer_it,
1880 bt_message_array_const msgs, uint64_t capacity,
1881 uint64_t *count)
1882 {
1883 bt_self_message_iterator_status status =
1884 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1885
1886 if (g_queue_is_empty(trimmer_it->output_messages)) {
1887 trimmer_it->state = TRIMMER_ITERATOR_STATE_ENDED;
1888 status = BT_SELF_MESSAGE_ITERATOR_STATUS_END;
1889 goto end;
1890 }
1891
1892 fill_message_array_from_output_messages(trimmer_it, msgs,
1893 capacity, count);
1894
1895 end:
1896 return status;
1897 }
1898
1899 static inline
1900 bt_self_message_iterator_status state_trim(struct trimmer_iterator *trimmer_it,
1901 bt_message_array_const msgs, uint64_t capacity,
1902 uint64_t *count)
1903 {
1904 bt_self_message_iterator_status status =
1905 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1906 bt_message_array_const my_msgs;
1907 uint64_t my_count;
1908 uint64_t i;
1909 bool reached_end = false;
1910
1911 while (g_queue_is_empty(trimmer_it->output_messages)) {
1912 status = (int) bt_self_component_port_input_message_iterator_next(
1913 trimmer_it->upstream_iter, &my_msgs, &my_count);
1914 if (G_UNLIKELY(status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK)) {
1915 if (status == BT_SELF_MESSAGE_ITERATOR_STATUS_END) {
1916 status = end_iterator_streams(trimmer_it);
1917 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1918 goto end;
1919 }
1920
1921 trimmer_it->state =
1922 TRIMMER_ITERATOR_STATE_ENDING;
1923 status = state_ending(trimmer_it, msgs,
1924 capacity, count);
1925 }
1926
1927 goto end;
1928 }
1929
1930 BT_ASSERT(my_count > 0);
1931
1932 for (i = 0; i < my_count; i++) {
1933 status = handle_message(trimmer_it, my_msgs[i],
1934 &reached_end);
1935
1936 /*
1937 * handle_message() unconditionally consumes the
1938 * message reference.
1939 */
1940 my_msgs[i] = NULL;
1941
1942 if (G_UNLIKELY(status !=
1943 BT_SELF_MESSAGE_ITERATOR_STATUS_OK)) {
1944 put_messages(my_msgs, my_count);
1945 goto end;
1946 }
1947
1948 if (G_UNLIKELY(reached_end)) {
1949 /*
1950 * This message's time was passed the
1951 * trimming time range's end time: we
1952 * are done. Their might still be
1953 * messages in the output message queue,
1954 * so move to the "ending" state and
1955 * apply it immediately since
1956 * state_trim() is called within the
1957 * "next" method.
1958 */
1959 put_messages(my_msgs, my_count);
1960 trimmer_it->state =
1961 TRIMMER_ITERATOR_STATE_ENDING;
1962 status = state_ending(trimmer_it, msgs,
1963 capacity, count);
1964 goto end;
1965 }
1966 }
1967 }
1968
1969 /*
1970 * There's at least one message in the output message queue:
1971 * move the messages to the output message array.
1972 */
1973 BT_ASSERT(!g_queue_is_empty(trimmer_it->output_messages));
1974 fill_message_array_from_output_messages(trimmer_it, msgs,
1975 capacity, count);
1976
1977 end:
1978 return status;
1979 }
1980
1981 BT_HIDDEN
1982 bt_self_message_iterator_status trimmer_msg_iter_next(
1983 bt_self_message_iterator *self_msg_iter,
1984 bt_message_array_const msgs, uint64_t capacity,
1985 uint64_t *count)
1986 {
1987 struct trimmer_iterator *trimmer_it =
1988 bt_self_message_iterator_get_data(self_msg_iter);
1989 bt_self_message_iterator_status status =
1990 BT_SELF_MESSAGE_ITERATOR_STATUS_OK;
1991
1992 BT_ASSERT(trimmer_it);
1993
1994 if (G_LIKELY(trimmer_it->state == TRIMMER_ITERATOR_STATE_TRIM)) {
1995 status = state_trim(trimmer_it, msgs, capacity, count);
1996 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
1997 goto end;
1998 }
1999 } else {
2000 switch (trimmer_it->state) {
2001 case TRIMMER_ITERATOR_STATE_SET_BOUNDS_NS_FROM_ORIGIN:
2002 status = state_set_trimmer_iterator_bounds(trimmer_it);
2003 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
2004 goto end;
2005 }
2006
2007 status = state_seek_initially(trimmer_it);
2008 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
2009 goto end;
2010 }
2011
2012 status = state_trim(trimmer_it, msgs, capacity, count);
2013 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
2014 goto end;
2015 }
2016
2017 break;
2018 case TRIMMER_ITERATOR_STATE_SEEK_INITIALLY:
2019 status = state_seek_initially(trimmer_it);
2020 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
2021 goto end;
2022 }
2023
2024 status = state_trim(trimmer_it, msgs, capacity, count);
2025 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
2026 goto end;
2027 }
2028
2029 break;
2030 case TRIMMER_ITERATOR_STATE_ENDING:
2031 status = state_ending(trimmer_it, msgs, capacity,
2032 count);
2033 if (status != BT_SELF_MESSAGE_ITERATOR_STATUS_OK) {
2034 goto end;
2035 }
2036
2037 break;
2038 case TRIMMER_ITERATOR_STATE_ENDED:
2039 status = BT_SELF_MESSAGE_ITERATOR_STATUS_END;
2040 break;
2041 default:
2042 abort();
2043 }
2044 }
2045
2046 end:
2047 return status;
2048 }
2049
2050 BT_HIDDEN
2051 void trimmer_msg_iter_finalize(bt_self_message_iterator *self_msg_iter)
2052 {
2053 struct trimmer_iterator *trimmer_it =
2054 bt_self_message_iterator_get_data(self_msg_iter);
2055
2056 BT_ASSERT(trimmer_it);
2057 destroy_trimmer_iterator(trimmer_it);
2058 }
This page took 0.097595 seconds and 5 git commands to generate.