a270fd4820ad046c62e2529a8a452ca457a7b3bf
[babeltrace.git] / plugins / text / text.c
1 /*
2 * text.c
3 *
4 * Babeltrace CTF Text Output Plugin
5 *
6 * Copyright 2016 Jérémie Galarneau <jeremie.galarneau@efficios.com>
7 * Copyright 2016 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
8 *
9 * Author: Jérémie Galarneau <jeremie.galarneau@efficios.com>
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a copy
12 * of this software and associated documentation files (the "Software"), to deal
13 * in the Software without restriction, including without limitation the rights
14 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 * copies of the Software, and to permit persons to whom the Software is
16 * furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30 #include <babeltrace/plugin/plugin-dev.h>
31 #include <babeltrace/component/component.h>
32 #include <babeltrace/component/private-component.h>
33 #include <babeltrace/component/component-sink.h>
34 #include <babeltrace/component/port.h>
35 #include <babeltrace/component/private-port.h>
36 #include <babeltrace/component/connection.h>
37 #include <babeltrace/component/private-connection.h>
38 #include <babeltrace/component/notification/notification.h>
39 #include <babeltrace/component/notification/iterator.h>
40 #include <babeltrace/component/notification/event.h>
41 #include <babeltrace/values.h>
42 #include <babeltrace/compiler.h>
43 #include <babeltrace/common-internal.h>
44 #include <plugins-common.h>
45 #include <stdio.h>
46 #include <stdbool.h>
47 #include <glib.h>
48 #include "text.h"
49 #include <assert.h>
50
51 static
52 const char *plugin_options[] = {
53 "color",
54 "output-path",
55 "debug-info-dir",
56 "debug-info-target-prefix",
57 "debug-info-full-path",
58 "no-delta",
59 "clock-cycles",
60 "clock-seconds",
61 "clock-date",
62 "clock-gmt",
63 "verbose",
64 "name-default", /* show/hide */
65 "name-payload",
66 "name-context",
67 "name-scope",
68 "name-header",
69 "field-default", /* show/hide */
70 "field-trace",
71 "field-trace:hostname",
72 "field-trace:domain",
73 "field-trace:procname",
74 "field-trace:vpid",
75 "field-loglevel",
76 "field-emf",
77 };
78
79 static
80 void destroy_text_data(struct text_component *text)
81 {
82 bt_put(text->input_iterator);
83 (void) g_string_free(text->string, TRUE);
84 g_free(text->options.output_path);
85 g_free(text->options.debug_info_dir);
86 g_free(text->options.debug_info_target_prefix);
87 g_free(text);
88 }
89
90 static
91 struct text_component *create_text(void)
92 {
93 struct text_component *text;
94
95 text = g_new0(struct text_component, 1);
96 if (!text) {
97 goto end;
98 }
99 text->string = g_string_new("");
100 if (!text->string) {
101 goto error;
102 }
103 end:
104 return text;
105
106 error:
107 g_free(text);
108 return NULL;
109 }
110
111 static
112 void finalize_text(struct bt_private_component *component)
113 {
114 void *data = bt_private_component_get_user_data(component);
115
116 destroy_text_data(data);
117 }
118
119 static
120 enum bt_component_status handle_notification(struct text_component *text,
121 struct bt_notification *notification)
122 {
123 enum bt_component_status ret = BT_COMPONENT_STATUS_OK;
124
125 if (!text) {
126 ret = BT_COMPONENT_STATUS_ERROR;
127 goto end;
128 }
129
130 switch (bt_notification_get_type(notification)) {
131 case BT_NOTIFICATION_TYPE_PACKET_BEGIN:
132 puts("<packet>");
133 break;
134 case BT_NOTIFICATION_TYPE_PACKET_END:
135 puts("</packet>");
136 break;
137 case BT_NOTIFICATION_TYPE_EVENT:
138 {
139 struct bt_ctf_event *event = bt_notification_event_get_event(
140 notification);
141
142 if (!event) {
143 ret = BT_COMPONENT_STATUS_ERROR;
144 goto end;
145 }
146 ret = text_print_event(text, event);
147 bt_put(event);
148 if (ret != BT_COMPONENT_STATUS_OK) {
149 goto end;
150 }
151 break;
152 }
153 case BT_NOTIFICATION_TYPE_STREAM_END:
154 puts("</stream>");
155 break;
156 default:
157 puts("Unhandled notification type");
158 }
159 end:
160 return ret;
161 }
162
163 static
164 enum bt_component_status text_accept_port_connection(
165 struct bt_private_component *component,
166 struct bt_private_port *self_port,
167 struct bt_port *other_port)
168 {
169 enum bt_component_status ret = BT_COMPONENT_STATUS_OK;
170 struct bt_private_connection *connection;
171 struct text_component *text;
172
173 text = bt_private_component_get_user_data(component);
174 assert(text);
175 assert(!text->input_iterator);
176 connection = bt_private_port_get_private_connection(self_port);
177 assert(connection);
178 text->input_iterator =
179 bt_private_connection_create_notification_iterator(connection);
180
181 if (!text->input_iterator) {
182 ret = BT_COMPONENT_STATUS_ERROR;
183 }
184
185 bt_put(connection);
186 return ret;
187 }
188
189 static
190 enum bt_component_status run(struct bt_private_component *component)
191 {
192 enum bt_component_status ret;
193 struct bt_notification *notification = NULL;
194 struct bt_notification_iterator *it;
195 struct text_component *text =
196 bt_private_component_get_user_data(component);
197
198 it = text->input_iterator;
199
200 if (likely(text->processed_first_event)) {
201 enum bt_notification_iterator_status it_ret;
202
203 it_ret = bt_notification_iterator_next(it);
204 switch (it_ret) {
205 case BT_NOTIFICATION_ITERATOR_STATUS_ERROR:
206 ret = BT_COMPONENT_STATUS_ERROR;
207 goto end;
208 case BT_NOTIFICATION_ITERATOR_STATUS_END:
209 ret = BT_COMPONENT_STATUS_END;
210 BT_PUT(text->input_iterator);
211 goto end;
212 default:
213 break;
214 }
215 }
216 notification = bt_notification_iterator_get_notification(it);
217 if (!notification) {
218 ret = BT_COMPONENT_STATUS_ERROR;
219 goto end;
220 }
221
222 ret = handle_notification(text, notification);
223 text->processed_first_event = true;
224 end:
225 bt_put(notification);
226 return ret;
227 }
228
229 static
230 enum bt_component_status add_params_to_map(struct bt_value *plugin_opt_map)
231 {
232 enum bt_component_status ret = BT_COMPONENT_STATUS_OK;
233 unsigned int i;
234
235 for (i = 0; i < BT_ARRAY_SIZE(plugin_options); i++) {
236 const char *key = plugin_options[i];
237 enum bt_value_status status;
238
239 status = bt_value_map_insert(plugin_opt_map, key, bt_value_null);
240 switch (status) {
241 case BT_VALUE_STATUS_OK:
242 break;
243 default:
244 ret = BT_COMPONENT_STATUS_ERROR;
245 goto end;
246 }
247 }
248 end:
249 return ret;
250 }
251
252 static
253 bool check_param_exists(const char *key, struct bt_value *object, void *data)
254 {
255 struct text_component *text = data;
256 struct bt_value *plugin_opt_map = text->plugin_opt_map;
257
258 if (!bt_value_map_get(plugin_opt_map, key)) {
259 fprintf(text->err,
260 "[warning] Parameter \"%s\" unknown to \"text\" plugin\n", key);
261 }
262 return true;
263 }
264
265 static
266 enum bt_component_status apply_one_string(const char *key,
267 struct bt_value *params,
268 char **option)
269 {
270 enum bt_component_status ret = BT_COMPONENT_STATUS_OK;
271 struct bt_value *value = NULL;
272 enum bt_value_status status;
273 const char *str;
274
275 value = bt_value_map_get(params, key);
276 if (!value) {
277 goto end;
278 }
279 if (bt_value_is_null(value)) {
280 goto end;
281 }
282 status = bt_value_string_get(value, &str);
283 switch (status) {
284 case BT_VALUE_STATUS_OK:
285 break;
286 default:
287 ret = BT_COMPONENT_STATUS_ERROR;
288 goto end;
289 }
290 *option = g_strdup(str);
291 end:
292 bt_put(value);
293 return ret;
294 }
295
296 static
297 enum bt_component_status apply_one_bool(const char *key,
298 struct bt_value *params,
299 bool *option,
300 bool *found)
301 {
302 enum bt_component_status ret = BT_COMPONENT_STATUS_OK;
303 struct bt_value *value = NULL;
304 enum bt_value_status status;
305
306 value = bt_value_map_get(params, key);
307 if (!value) {
308 goto end;
309 }
310 status = bt_value_bool_get(value, option);
311 switch (status) {
312 case BT_VALUE_STATUS_OK:
313 break;
314 default:
315 ret = BT_COMPONENT_STATUS_ERROR;
316 goto end;
317 }
318 if (found) {
319 *found = true;
320 }
321 end:
322 bt_put(value);
323 return ret;
324 }
325
326 static
327 void warn_wrong_color_param(struct text_component *text)
328 {
329 fprintf(text->err,
330 "[warning] Accepted values for the \"color\" parameter are:\n \"always\", \"auto\", \"never\"\n");
331 }
332
333 static
334 enum bt_component_status apply_params(struct text_component *text,
335 struct bt_value *params)
336 {
337 enum bt_component_status ret = BT_COMPONENT_STATUS_OK;
338 enum bt_value_status status;
339 bool value, found;
340 char *str = NULL;
341
342 text->plugin_opt_map = bt_value_map_create();
343 if (!text->plugin_opt_map) {
344 ret = BT_COMPONENT_STATUS_ERROR;
345 goto end;
346 }
347 ret = add_params_to_map(text->plugin_opt_map);
348 if (ret != BT_COMPONENT_STATUS_OK) {
349 goto end;
350 }
351 /* Report unknown parameters. */
352 status = bt_value_map_foreach(params, check_param_exists, text);
353 switch (status) {
354 case BT_VALUE_STATUS_OK:
355 break;
356 default:
357 ret = BT_COMPONENT_STATUS_ERROR;
358 goto end;
359 }
360 /* Known parameters. */
361 text->options.color = TEXT_COLOR_OPT_AUTO;
362 if (bt_value_map_has_key(params, "color")) {
363 struct bt_value *color_value;
364 const char *color;
365
366 color_value = bt_value_map_get(params, "color");
367 if (!color_value) {
368 goto end;
369 }
370
371 status = bt_value_string_get(color_value, &color);
372 if (status) {
373 warn_wrong_color_param(text);
374 } else {
375 if (strcmp(color, "never") == 0) {
376 text->options.color = TEXT_COLOR_OPT_NEVER;
377 } else if (strcmp(color, "auto") == 0) {
378 text->options.color = TEXT_COLOR_OPT_AUTO;
379 } else if (strcmp(color, "always") == 0) {
380 text->options.color = TEXT_COLOR_OPT_ALWAYS;
381 } else {
382 warn_wrong_color_param(text);
383 }
384 }
385
386 bt_put(color_value);
387 }
388
389 ret = apply_one_string("output-path",
390 params,
391 &text->options.output_path);
392 if (ret != BT_COMPONENT_STATUS_OK) {
393 goto end;
394 }
395
396 ret = apply_one_string("debug-info-dir",
397 params,
398 &text->options.debug_info_dir);
399 if (ret != BT_COMPONENT_STATUS_OK) {
400 goto end;
401 }
402
403 ret = apply_one_string("debug-info-target-prefix",
404 params,
405 &text->options.debug_info_target_prefix);
406 if (ret != BT_COMPONENT_STATUS_OK) {
407 goto end;
408 }
409
410 value = false; /* Default. */
411 ret = apply_one_bool("debug-info-full-path", params, &value, NULL);
412 if (ret != BT_COMPONENT_STATUS_OK) {
413 goto end;
414 }
415 text->options.debug_info_full_path = value;
416
417 value = false; /* Default. */
418 ret = apply_one_bool("no-delta", params, &value, NULL);
419 if (ret != BT_COMPONENT_STATUS_OK) {
420 goto end;
421 }
422 text->options.print_delta_field = !value; /* Reverse logic. */
423
424 value = false; /* Default. */
425 ret = apply_one_bool("clock-cycles", params, &value, NULL);
426 if (ret != BT_COMPONENT_STATUS_OK) {
427 goto end;
428 }
429 text->options.print_timestamp_cycles = value;
430
431 value = false; /* Default. */
432 ret = apply_one_bool("clock-seconds", params, &value, NULL);
433 if (ret != BT_COMPONENT_STATUS_OK) {
434 goto end;
435 }
436 text->options.clock_seconds = value;
437
438 value = false; /* Default. */
439 ret = apply_one_bool("clock-date", params, &value, NULL);
440 if (ret != BT_COMPONENT_STATUS_OK) {
441 goto end;
442 }
443 text->options.clock_date = value;
444
445 value = false; /* Default. */
446 ret = apply_one_bool("clock-gmt", params, &value, NULL);
447 if (ret != BT_COMPONENT_STATUS_OK) {
448 goto end;
449 }
450 text->options.clock_gmt = value;
451
452 value = false; /* Default. */
453 ret = apply_one_bool("verbose", params, &value, NULL);
454 if (ret != BT_COMPONENT_STATUS_OK) {
455 goto end;
456 }
457 text->options.verbose = value;
458
459 /* Names. */
460 ret = apply_one_string("name-default", params, &str);
461 if (ret != BT_COMPONENT_STATUS_OK) {
462 goto end;
463 }
464 if (!str) {
465 text->options.name_default = TEXT_DEFAULT_UNSET;
466 } else if (!strcmp(str, "show")) {
467 text->options.name_default = TEXT_DEFAULT_SHOW;
468 } else if (!strcmp(str, "hide")) {
469 text->options.name_default = TEXT_DEFAULT_HIDE;
470 } else {
471 ret = BT_COMPONENT_STATUS_ERROR;
472 goto end;
473 }
474 g_free(str);
475 str = NULL;
476
477 switch (text->options.name_default) {
478 case TEXT_DEFAULT_UNSET:
479 text->options.print_payload_field_names = true;
480 text->options.print_context_field_names = true;
481 text->options.print_header_field_names = false;
482 text->options.print_scope_field_names = false;
483 break;
484 case TEXT_DEFAULT_SHOW:
485 text->options.print_payload_field_names = true;
486 text->options.print_context_field_names = true;
487 text->options.print_header_field_names = true;
488 text->options.print_scope_field_names = true;
489 break;
490 case TEXT_DEFAULT_HIDE:
491 text->options.print_payload_field_names = false;
492 text->options.print_context_field_names = false;
493 text->options.print_header_field_names = false;
494 text->options.print_scope_field_names = false;
495 break;
496 default:
497 ret = BT_COMPONENT_STATUS_ERROR;
498 goto end;
499 }
500
501 value = false;
502 found = false;
503 ret = apply_one_bool("name-payload", params, &value, &found);
504 if (ret != BT_COMPONENT_STATUS_OK) {
505 goto end;
506 }
507 if (found) {
508 text->options.print_payload_field_names = value;
509 }
510
511 value = false;
512 found = false;
513 ret = apply_one_bool("name-context", params, &value, &found);
514 if (ret != BT_COMPONENT_STATUS_OK) {
515 goto end;
516 }
517 if (found) {
518 text->options.print_context_field_names = value;
519 }
520
521 value = false;
522 found = false;
523 ret = apply_one_bool("name-header", params, &value, &found);
524 if (ret != BT_COMPONENT_STATUS_OK) {
525 goto end;
526 }
527 if (found) {
528 text->options.print_header_field_names = value;
529 }
530
531 value = false;
532 found = false;
533 ret = apply_one_bool("name-scope", params, &value, &found);
534 if (ret != BT_COMPONENT_STATUS_OK) {
535 goto end;
536 }
537 if (found) {
538 text->options.print_scope_field_names = value;
539 }
540
541 /* Fields. */
542 ret = apply_one_string("field-default", params, &str);
543 if (ret != BT_COMPONENT_STATUS_OK) {
544 goto end;
545 }
546 if (!str) {
547 text->options.field_default = TEXT_DEFAULT_UNSET;
548 } else if (!strcmp(str, "show")) {
549 text->options.field_default = TEXT_DEFAULT_SHOW;
550 } else if (!strcmp(str, "hide")) {
551 text->options.field_default = TEXT_DEFAULT_HIDE;
552 } else {
553 ret = BT_COMPONENT_STATUS_ERROR;
554 goto end;
555 }
556 g_free(str);
557 str = NULL;
558
559 switch (text->options.field_default) {
560 case TEXT_DEFAULT_UNSET:
561 text->options.print_trace_field = false;
562 text->options.print_trace_hostname_field = true;
563 text->options.print_trace_domain_field = false;
564 text->options.print_trace_procname_field = true;
565 text->options.print_trace_vpid_field = true;
566 text->options.print_loglevel_field = false;
567 text->options.print_emf_field = false;
568 text->options.print_emf_field = false;
569 break;
570 case TEXT_DEFAULT_SHOW:
571 text->options.print_trace_field = true;
572 text->options.print_trace_hostname_field = true;
573 text->options.print_trace_domain_field = true;
574 text->options.print_trace_procname_field = true;
575 text->options.print_trace_vpid_field = true;
576 text->options.print_loglevel_field = true;
577 text->options.print_emf_field = true;
578 text->options.print_emf_field = true;
579 break;
580 case TEXT_DEFAULT_HIDE:
581 text->options.print_trace_field = false;
582 text->options.print_trace_hostname_field = false;
583 text->options.print_trace_domain_field = false;
584 text->options.print_trace_procname_field = false;
585 text->options.print_trace_vpid_field = false;
586 text->options.print_loglevel_field = false;
587 text->options.print_emf_field = false;
588 text->options.print_emf_field = false;
589 break;
590 default:
591 ret = BT_COMPONENT_STATUS_ERROR;
592 goto end;
593 }
594
595 value = false;
596 found = false;
597 ret = apply_one_bool("field-trace", params, &value, &found);
598 if (ret != BT_COMPONENT_STATUS_OK) {
599 goto end;
600 }
601 if (found) {
602 text->options.print_trace_field = value;
603 }
604
605 value = false;
606 found = false;
607 ret = apply_one_bool("field-trace:hostname", params, &value, &found);
608 if (ret != BT_COMPONENT_STATUS_OK) {
609 goto end;
610 }
611 if (found) {
612 text->options.print_trace_hostname_field = value;
613 }
614
615 value = false;
616 found = false;
617 ret = apply_one_bool("field-trace:domain", params, &value, &found);
618 if (ret != BT_COMPONENT_STATUS_OK) {
619 goto end;
620 }
621 if (found) {
622 text->options.print_trace_domain_field = value;
623 }
624
625 value = false;
626 found = false;
627 ret = apply_one_bool("field-trace:procname", params, &value, &found);
628 if (ret != BT_COMPONENT_STATUS_OK) {
629 goto end;
630 }
631 if (found) {
632 text->options.print_trace_procname_field = value;
633 }
634
635 value = false;
636 found = false;
637 ret = apply_one_bool("field-trace:vpid", params, &value, &found);
638 if (ret != BT_COMPONENT_STATUS_OK) {
639 goto end;
640 }
641 if (found) {
642 text->options.print_trace_vpid_field = value;
643 }
644
645 value = false;
646 found = false;
647 ret = apply_one_bool("field-loglevel", params, &value, &found);
648 if (ret != BT_COMPONENT_STATUS_OK) {
649 goto end;
650 }
651 if (found) {
652 text->options.print_loglevel_field = value;
653 }
654
655 value = false;
656 found = false;
657 ret = apply_one_bool("field-emf", params, &value, &found);
658 if (ret != BT_COMPONENT_STATUS_OK) {
659 goto end;
660 }
661 if (found) {
662 text->options.print_emf_field = value;
663 }
664
665 value = false;
666 found = false;
667 ret = apply_one_bool("field-emf", params, &value, &found);
668 if (ret != BT_COMPONENT_STATUS_OK) {
669 goto end;
670 }
671 if (found) {
672 text->options.print_emf_field = value;
673 }
674
675 end:
676 bt_put(text->plugin_opt_map);
677 text->plugin_opt_map = NULL;
678 g_free(str);
679 return ret;
680 }
681
682 static
683 void set_use_colors(struct text_component *text)
684 {
685 switch (text->options.color) {
686 case TEXT_COLOR_OPT_ALWAYS:
687 text->use_colors = true;
688 break;
689 case TEXT_COLOR_OPT_AUTO:
690 text->use_colors = text->out == stdout &&
691 bt_common_colors_supported();
692 break;
693 case TEXT_COLOR_OPT_NEVER:
694 text->use_colors = false;
695 break;
696 }
697 }
698
699 static
700 void init_stream_packet_context_quarks(void)
701 {
702 stream_packet_context_quarks[Q_TIMESTAMP_BEGIN] =
703 g_quark_from_string("timestamp_begin");
704 stream_packet_context_quarks[Q_TIMESTAMP_BEGIN] =
705 g_quark_from_string("timestamp_begin");
706 stream_packet_context_quarks[Q_TIMESTAMP_END] =
707 g_quark_from_string("timestamp_end");
708 stream_packet_context_quarks[Q_EVENTS_DISCARDED] =
709 g_quark_from_string("events_discarded");
710 stream_packet_context_quarks[Q_CONTENT_SIZE] =
711 g_quark_from_string("content_size");
712 stream_packet_context_quarks[Q_PACKET_SIZE] =
713 g_quark_from_string("packet_size");
714 stream_packet_context_quarks[Q_PACKET_SEQ_NUM] =
715 g_quark_from_string("packet_seq_num");
716 }
717
718 static
719 enum bt_component_status text_component_init(
720 struct bt_private_component *component,
721 struct bt_value *params,
722 UNUSED_VAR void *init_method_data)
723 {
724 enum bt_component_status ret;
725 struct text_component *text = create_text();
726
727 if (!text) {
728 ret = BT_COMPONENT_STATUS_NOMEM;
729 goto end;
730 }
731
732 text->out = stdout;
733 text->err = stderr;
734
735 text->delta_cycles = -1ULL;
736 text->last_cycles_timestamp = -1ULL;
737
738 text->delta_real_timestamp = -1ULL;
739 text->last_real_timestamp = -1ULL;
740
741 ret = apply_params(text, params);
742 if (ret != BT_COMPONENT_STATUS_OK) {
743 goto error;
744 }
745
746 set_use_colors(text);
747
748 ret = bt_private_component_set_user_data(component, text);
749 if (ret != BT_COMPONENT_STATUS_OK) {
750 goto error;
751 }
752
753 init_stream_packet_context_quarks();
754
755 end:
756 return ret;
757 error:
758 destroy_text_data(text);
759 return ret;
760 }
761
762 /* Initialize plug-in entry points. */
763 BT_PLUGIN(text);
764 BT_PLUGIN_DESCRIPTION("Babeltrace text output plug-in.");
765 BT_PLUGIN_AUTHOR("Jérémie Galarneau");
766 BT_PLUGIN_LICENSE("MIT");
767 BT_PLUGIN_SINK_COMPONENT_CLASS(text, run);
768 BT_PLUGIN_SINK_COMPONENT_CLASS_INIT_METHOD(text, text_component_init);
769 BT_PLUGIN_SINK_COMPONENT_CLASS_ACCEPT_PORT_CONNECTION_METHOD(text, text_accept_port_connection);
770 BT_PLUGIN_SINK_COMPONENT_CLASS_FINALIZE_METHOD(text, finalize_text);
771 BT_PLUGIN_SINK_COMPONENT_CLASS_DESCRIPTION(text,
772 "Formats CTF-IR to text. Formerly known as ctf-text.");
This page took 0.042918 seconds and 3 git commands to generate.