@@ -441,6 +441,7 @@ class metrics_log_format : public log_format {
441441 {
442442 this ->lf_multiline = false ;
443443 this ->lf_is_metric = true ;
444+ this ->lf_time_ordered = false ;
444445 }
445446
446447 const intern_string_t get_name () const override
@@ -500,13 +501,15 @@ class metrics_log_format : public log_format {
500501 if (cell_len > stats.lvs_width ) {
501502 stats.lvs_width = cell_len;
502503 }
503- parse_cell (iter).match (
504- [](empty_cell) {},
505- [&stats](int64_t i) {
506- stats.add_value (static_cast <double >(i));
507- },
508- [&stats](double d) { stats.add_value (d); },
509- [&stats](humanized_cell hc) { stats.add_value (hc.value ); });
504+ parse_cell (iter, parse_context::scan)
505+ .match (
506+ [](empty_cell) {},
507+ [&stats](int64_t i) {
508+ stats.add_value (static_cast <double >(i));
509+ },
510+ [&stats](double d) { stats.add_value (d); },
511+ [&stats](humanized_cell hc) { stats.add_value (hc.value ); },
512+ [](const text_cell& tc) {});
510513 }
511514 if (field_index < this ->mlf_field_defs .size ()) {
512515 return scan_error{fmt::format (
@@ -515,6 +518,15 @@ class metrics_log_format : public log_format {
515518 field_index,
516519 this ->mlf_field_defs .size ())};
517520 }
521+ if (!this ->lf_specialized ) {
522+ auto number_cells = 0 ;
523+ for (const auto & stats : sbc.sbc_value_stats ) {
524+ number_cells += stats.lvs_count ;
525+ }
526+ if (number_cells == 0 ) {
527+ return scan_error{" metric row has no numeric fields" };
528+ }
529+ }
518530
519531 return scan_match{500 };
520532 }
@@ -531,11 +543,9 @@ class metrics_log_format : public log_format {
531543 // post-clear scan arrives here with an empty `dst`. Seed
532544 // from epoch rather than reading `dst.back()` on an empty
533545 // vector.
534- const auto prev_time = dst.empty ()
535- ? std::chrono::microseconds::zero ()
536- : dst.back ().get_time <>();
537- dst.emplace_back (
538- li.li_file_range .fr_offset , prev_time, LEVEL_STATS);
546+ const auto prev_time = dst.empty () ? std::chrono::microseconds::zero ()
547+ : dst.back ().get_time <>();
548+ dst.emplace_back (li.li_file_range .fr_offset , prev_time, LEVEL_STATS);
539549 auto retval = this ->parse_line (line_sf, dst, sbc);
540550 if (!retval.is <scan_match>()) {
541551 dst.pop_back ();
@@ -560,8 +570,7 @@ class metrics_log_format : public log_format {
560570 // subsequent timestamp parses against the wrong zone.
561571 {
562572 auto file_options = lf.get_file_options ();
563- this ->lf_date_time .dts_default_zone
564- = file_options
573+ this ->lf_date_time .dts_default_zone = file_options
565574 ? file_options->second .fo_default_zone .pp_value
566575 : nullptr ;
567576 }
@@ -734,18 +743,24 @@ class metrics_log_format : public log_format {
734743 if (mlf_hidden_columns.count (meta.lvm_name ) != 0 ) {
735744 meta.lvm_user_hidden = true ;
736745 }
737- parse_cell (iter).match (
738- [&](empty_cell) { values.lvv_values .emplace_back (meta); },
739- [&](int64_t i) { values.lvv_values .emplace_back (meta, i); },
740- [&](double d) { values.lvv_values .emplace_back (meta, d); },
741- [&](humanized_cell hc) {
742- // Carry the detected unit on the per-value meta so
743- // downstream renderers can call humanize::format
744- // against the base-unit value.
745- auto cell_meta = meta;
746- cell_meta.lvm_unit_suffix = hc.unit_suffix ;
747- values.lvv_values .emplace_back (cell_meta, hc.value );
748- });
746+ parse_cell (iter, parse_context::annotate)
747+ .match (
748+ [&](empty_cell) { values.lvv_values .emplace_back (meta); },
749+ [&](int64_t i) { values.lvv_values .emplace_back (meta, i); },
750+ [&](double d) { values.lvv_values .emplace_back (meta, d); },
751+ [&](humanized_cell hc) {
752+ // Carry the detected unit on the per-value meta so
753+ // downstream renderers can call humanize::format
754+ // against the base-unit value.
755+ auto cell_meta = meta;
756+ cell_meta.lvm_unit_suffix = hc.unit_suffix ;
757+ values.lvv_values .emplace_back (cell_meta, hc.value );
758+ },
759+ [&](const text_cell& tc) {
760+ values.lvv_values .emplace_back (meta, tc.value );
761+ values.lvv_values .back ().lv_meta .lvm_kind
762+ = value_kind_t ::VALUE_TEXT;
763+ });
749764 values.lvv_values .back ().lv_origin = lr;
750765 }
751766
@@ -776,10 +791,19 @@ class metrics_log_format : public log_format {
776791 double value;
777792 intern_string_t unit_suffix;
778793 };
779- using parsed_cell_t
780- = mapbox::util::variant<empty_cell, int64_t , double , humanized_cell>;
794+ struct text_cell {
795+ std::string value;
796+ };
797+ using parsed_cell_t = mapbox::util::
798+ variant<empty_cell, int64_t , double , humanized_cell, text_cell>;
799+
800+ enum class parse_context {
801+ scan,
802+ annotate,
803+ };
781804
782- static parsed_cell_t parse_cell (const separated_string::iterator& iter)
805+ static parsed_cell_t parse_cell (const separated_string::iterator& iter,
806+ parse_context pc)
783807 {
784808 const auto field = *iter;
785809 switch (iter.kind ()) {
@@ -812,7 +836,19 @@ class metrics_log_format : public log_format {
812836 }
813837 case separated_string::cell_kind::other: {
814838 // Plain text; humanize wouldn't have parsed it.
815- return parsed_cell_t {empty_cell{}};
839+ switch (pc) {
840+ case parse_context::scan:
841+ // During scanning, treat unparseable text as
842+ // empty so it doesn't mess with stats or
843+ // trigger a type change on the column.
844+ return parsed_cell_t {empty_cell{}};
845+ case parse_context::annotate:
846+ // During annotation, preserve the text so the
847+ // renderer can show it and the user can query
848+ // against it.
849+ return parsed_cell_t {text_cell{
850+ separated_string::unescape_quoted (field)}};
851+ }
816852 }
817853 }
818854 return parsed_cell_t {empty_cell{}};
0 commit comments