55#include < prometheus/metric_family.h>
66#include < prometheus/metric_type.h>
77#include < algorithm>
8+ #include < cmath>
89#include < cstddef>
910#include < cstdint>
1011#include < functional>
@@ -103,6 +104,72 @@ std::string SanitizeLabel(std::string label_key)
103104 });
104105}
105106
107+ std::string EscapeJsonString (const std::string &str)
108+ {
109+ std::string out;
110+ out.reserve (str.size () + 2 );
111+ out.push_back (' "' );
112+ for (unsigned char c : str)
113+ {
114+ switch (c)
115+ {
116+ case ' "' :
117+ out.append (" \\\" " );
118+ break ;
119+ case ' \\ ' :
120+ out.append (" \\\\ " );
121+ break ;
122+ case ' \b ' :
123+ out.append (" \\ b" );
124+ break ;
125+ case ' \f ' :
126+ out.append (" \\ f" );
127+ break ;
128+ case ' \n ' :
129+ out.append (" \\ n" );
130+ break ;
131+ case ' \r ' :
132+ out.append (" \\ r" );
133+ break ;
134+ case ' \t ' :
135+ out.append (" \\ t" );
136+ break ;
137+ default :
138+ if (c <= 0x1F )
139+ {
140+ constexpr char kHex [] = " 0123456789abcdef" ;
141+ unsigned int uc = static_cast <unsigned int >(c);
142+ char buf[6 ] = {' \\ ' , ' u' , ' 0' , ' 0' , kHex [(uc >> 4U ) & 0x0FU ], kHex [uc & 0x0FU ]};
143+ out.append (buf, 6 );
144+ }
145+ else
146+ {
147+ out.push_back (static_cast <char >(c));
148+ }
149+ break ;
150+ }
151+ }
152+ out.push_back (' "' );
153+ return out;
154+ }
155+
156+ template <typename T>
157+ std::string NumericVectorToJson (const std::vector<T> &vec)
158+ {
159+ std::string result;
160+ result.push_back (' [' );
161+ for (std::size_t i = 0 ; i < vec.size (); ++i)
162+ {
163+ if (i > 0 )
164+ {
165+ result.push_back (' ,' );
166+ }
167+ result.append (std::to_string (vec[i]));
168+ }
169+ result.push_back (' ]' );
170+ return result;
171+ }
172+
106173} // namespace
107174
108175/* *
@@ -688,19 +755,41 @@ void PrometheusExporterUtils::SetMetricBasic(
688755 reserve_labels += 3 + scope->GetAttributes ().size ();
689756 }
690757 metric.label .reserve (reserve_labels);
758+
759+ struct LabelCollisionEntry
760+ {
761+ std::string original_key;
762+ std::string value;
763+ };
764+ std::vector<std::vector<LabelCollisionEntry>> label_entries;
765+ if (scope != nullptr )
766+ {
767+ label_entries.reserve (reserve_labels);
768+ }
769+
691770 std::string previous_key;
692771 for (auto const &label : labels)
693772 {
694773 auto sanitized = SanitizeLabel (label.first );
695774 int comparison = previous_key.compare (sanitized);
696775 if (metric.label .empty () || comparison < 0 ) // new key
697776 {
698- previous_key = sanitized;
699- metric.label .push_back ({sanitized, AttributeValueToString (label.second )});
777+ previous_key = sanitized;
778+ std::string val = AttributeValueToString (label.second );
779+ if (scope != nullptr )
780+ {
781+ label_entries.push_back ({{label.first , val}});
782+ }
783+ metric.label .push_back ({sanitized, std::move (val)});
700784 }
701785 else if (comparison == 0 ) // key collision after sanitation
702786 {
703- metric.label .back ().value += " ;" + AttributeValueToString (label.second );
787+ std::string val = AttributeValueToString (label.second );
788+ if (scope != nullptr )
789+ {
790+ label_entries.back ().push_back ({label.first , val});
791+ }
792+ metric.label .back ().value += " ;" + val;
704793 }
705794 else // order inversion introduced by sanitation
706795 {
@@ -715,26 +804,51 @@ void PrometheusExporterUtils::SetMetricBasic(
715804 {
716805 return ;
717806 }
807+
808+ auto add_or_merge_scope_label = [&metric, &label_entries](const std::string &sanitized_key,
809+ const std::string &original_key,
810+ const std::string &value) {
811+ for (std::size_t i = 0 ; i < metric.label .size (); ++i)
812+ {
813+ if (metric.label [i].name == sanitized_key)
814+ {
815+ auto &entries = label_entries[i];
816+ entries.push_back ({original_key, value});
817+ std::stable_sort (entries.begin (), entries.end (),
818+ [](const LabelCollisionEntry &a, const LabelCollisionEntry &b) {
819+ return a.original_key < b.original_key ;
820+ });
821+ std::string merged;
822+ for (std::size_t j = 0 ; j < entries.size (); ++j)
823+ {
824+ if (j > 0 )
825+ {
826+ merged.push_back (' ;' );
827+ }
828+ merged.append (entries[j].value );
829+ }
830+ metric.label [i].value = std::move (merged);
831+ return ;
832+ }
833+ }
834+ label_entries.push_back ({{original_key, value}});
835+ metric.label .push_back ({sanitized_key, value});
836+ };
837+
718838 const auto &scope_name = scope->GetName ();
719839 if (!scope_name.empty ())
720840 {
721- metric.label .emplace_back ();
722- metric.label .back ().name = kScopeNameKey ;
723- metric.label .back ().value = scope_name;
841+ add_or_merge_scope_label (kScopeNameKey , kScopeNameKey , scope_name);
724842 }
725843 const auto &scope_version = scope->GetVersion ();
726844 if (!scope_version.empty ())
727845 {
728- metric.label .emplace_back ();
729- metric.label .back ().name = kScopeVersionKey ;
730- metric.label .back ().value = scope_version;
846+ add_or_merge_scope_label (kScopeVersionKey , kScopeVersionKey , scope_version);
731847 }
732848 const auto &scope_schema_url = scope->GetSchemaURL ();
733849 if (!scope_schema_url.empty ())
734850 {
735- metric.label .emplace_back ();
736- metric.label .back ().name = kScopeSchemaUrlKey ;
737- metric.label .back ().value = scope_schema_url;
851+ add_or_merge_scope_label (kScopeSchemaUrlKey , kScopeSchemaUrlKey , scope_schema_url);
738852 }
739853 const auto &scope_attributes = scope->GetAttributes ();
740854 if (!scope_attributes.empty ())
@@ -765,17 +879,10 @@ void PrometheusExporterUtils::SetMetricBasic(
765879 }
766880 return *a.original_key < *b.original_key ;
767881 });
768- for (std:: size_t i = 0 ; i < sorted_scope_attrs. size (); ++i )
882+ for (const auto &scope_attr : sorted_scope_attrs)
769883 {
770- if (i > 0 && sorted_scope_attrs[i].sanitized_key == metric.label .back ().name )
771- {
772- metric.label .back ().value += " ;" + AttributeValueToString (*sorted_scope_attrs[i].value );
773- }
774- else
775- {
776- metric.label .push_back ({std::move (sorted_scope_attrs[i].sanitized_key ),
777- AttributeValueToString (*sorted_scope_attrs[i].value )});
778- }
884+ add_or_merge_scope_label (scope_attr.sanitized_key , *scope_attr.original_key ,
885+ AttributeValueToString (*scope_attr.value ));
779886 }
780887 }
781888}
@@ -812,6 +919,75 @@ std::string PrometheusExporterUtils::AttributeValueToString(
812919 {
813920 result = nostd::get<std::string>(value);
814921 }
922+ else if (nostd::holds_alternative<std::vector<bool >>(value))
923+ {
924+ const auto &vec = nostd::get<std::vector<bool >>(value);
925+ result.push_back (' [' );
926+ for (std::size_t i = 0 ; i < vec.size (); ++i)
927+ {
928+ if (i > 0 )
929+ {
930+ result.push_back (' ,' );
931+ }
932+ result.append (vec[i] ? " true" : " false" );
933+ }
934+ result.push_back (' ]' );
935+ }
936+ else if (nostd::holds_alternative<std::vector<int >>(value))
937+ {
938+ result = NumericVectorToJson (nostd::get<std::vector<int >>(value));
939+ }
940+ else if (nostd::holds_alternative<std::vector<int64_t >>(value))
941+ {
942+ result = NumericVectorToJson (nostd::get<std::vector<int64_t >>(value));
943+ }
944+ else if (nostd::holds_alternative<std::vector<unsigned int >>(value))
945+ {
946+ result = NumericVectorToJson (nostd::get<std::vector<unsigned int >>(value));
947+ }
948+ else if (nostd::holds_alternative<std::vector<uint64_t >>(value))
949+ {
950+ result = NumericVectorToJson (nostd::get<std::vector<uint64_t >>(value));
951+ }
952+ else if (nostd::holds_alternative<std::vector<double >>(value))
953+ {
954+ const auto &vec = nostd::get<std::vector<double >>(value);
955+ result.push_back (' [' );
956+ for (std::size_t i = 0 ; i < vec.size (); ++i)
957+ {
958+ if (i > 0 )
959+ {
960+ result.push_back (' ,' );
961+ }
962+ if (std::isnan (vec[i]))
963+ {
964+ result.append (" \" NaN\" " );
965+ }
966+ else if (std::isinf (vec[i]))
967+ {
968+ result.append (vec[i] < 0 ? " \" -Infinity\" " : " \" Infinity\" " );
969+ }
970+ else
971+ {
972+ result.append (std::to_string (vec[i]));
973+ }
974+ }
975+ result.push_back (' ]' );
976+ }
977+ else if (nostd::holds_alternative<std::vector<std::string>>(value))
978+ {
979+ const auto &vec = nostd::get<std::vector<std::string>>(value);
980+ result.push_back (' [' );
981+ for (std::size_t i = 0 ; i < vec.size (); ++i)
982+ {
983+ if (i > 0 )
984+ {
985+ result.push_back (' ,' );
986+ }
987+ result.append (EscapeJsonString (vec[i]));
988+ }
989+ result.push_back (' ]' );
990+ }
815991 else
816992 {
817993 OTEL_INTERNAL_LOG_WARN (
0 commit comments