001package io.prometheus.metrics.expositionformats; 002 003import io.prometheus.metrics.config.EscapingScheme; 004import io.prometheus.metrics.model.snapshots.Labels; 005import io.prometheus.metrics.model.snapshots.PrometheusNaming; 006import io.prometheus.metrics.model.snapshots.SnapshotEscaper; 007import java.io.IOException; 008import java.io.Writer; 009import javax.annotation.Nullable; 010 011public class TextFormatUtil { 012 013 static void writeLong(Writer writer, long value) throws IOException { 014 writer.append(Long.toString(value)); 015 } 016 017 static void writeDouble(Writer writer, double d) throws IOException { 018 if (d == Double.POSITIVE_INFINITY) { 019 writer.write("+Inf"); 020 } else if (d == Double.NEGATIVE_INFINITY) { 021 writer.write("-Inf"); 022 } else { 023 writer.write(Double.toString(d)); 024 // FloatingDecimal.getBinaryToASCIIConverter(d).appendTo(writer); 025 } 026 } 027 028 static void writePrometheusTimestamp(Writer writer, long timestampMs, boolean timestampsInMs) 029 throws IOException { 030 if (timestampsInMs) { 031 // correct for prometheus exposition format 032 // https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-details 033 writer.write(Long.toString(timestampMs)); 034 } else { 035 // incorrect for prometheus exposition format - 036 // but we need to support it for backwards compatibility 037 writeOpenMetricsTimestamp(writer, timestampMs); 038 } 039 } 040 041 static void writeOpenMetricsTimestamp(Writer writer, long timestampMs) throws IOException { 042 writer.write(Long.toString(timestampMs / 1000L)); 043 writer.write("."); 044 long ms = timestampMs % 1000; 045 if (ms < 100) { 046 writer.write("0"); 047 } 048 if (ms < 10) { 049 writer.write("0"); 050 } 051 writer.write(Long.toString(ms)); 052 } 053 054 static void writeEscapedString(Writer writer, String s) throws IOException { 055 // optimize for the common case where no escaping is needed 056 int start = 0; 057 // #indexOf is a vectorized intrinsic 058 int backslashIndex = s.indexOf('\\', start); 059 int quoteIndex = s.indexOf('\"', start); 060 int newlineIndex = s.indexOf('\n', start); 061 062 int allEscapesIndex = backslashIndex & quoteIndex & newlineIndex; 063 while (allEscapesIndex != -1) { 064 int escapeStart = Integer.MAX_VALUE; 065 if (backslashIndex != -1) { 066 escapeStart = backslashIndex; 067 } 068 if (quoteIndex != -1) { 069 escapeStart = Math.min(escapeStart, quoteIndex); 070 } 071 if (newlineIndex != -1) { 072 escapeStart = Math.min(escapeStart, newlineIndex); 073 } 074 075 // bulk write up to the first character that needs to be escaped 076 if (escapeStart > start) { 077 writer.write(s, start, escapeStart - start); 078 } 079 char c = s.charAt(escapeStart); 080 start = escapeStart + 1; 081 switch (c) { 082 case '\\': 083 writer.write("\\\\"); 084 backslashIndex = s.indexOf('\\', start); 085 break; 086 case '\"': 087 writer.write("\\\""); 088 quoteIndex = s.indexOf('\"', start); 089 break; 090 case '\n': 091 writer.write("\\n"); 092 newlineIndex = s.indexOf('\n', start); 093 break; 094 } 095 096 allEscapesIndex = backslashIndex & quoteIndex & newlineIndex; 097 } 098 // up until the end nothing needs to be escaped anymore 099 int remaining = s.length() - start; 100 if (remaining > 0) { 101 writer.write(s, start, remaining); 102 } 103 } 104 105 static void writeLabels( 106 Writer writer, 107 Labels labels, 108 @Nullable String additionalLabelName, 109 double additionalLabelValue, 110 boolean metricInsideBraces, 111 EscapingScheme scheme) 112 throws IOException { 113 if (!metricInsideBraces) { 114 writer.write('{'); 115 } 116 for (int i = 0; i < labels.size(); i++) { 117 if (i > 0 || metricInsideBraces) { 118 writer.write(","); 119 } 120 writeName(writer, SnapshotEscaper.getSnapshotLabelName(labels, i, scheme), NameType.Label); 121 writer.write("=\""); 122 writeEscapedString(writer, labels.getValue(i)); 123 writer.write("\""); 124 } 125 if (additionalLabelName != null) { 126 if (!labels.isEmpty()) { 127 writer.write(","); 128 } 129 writer.write(additionalLabelName); 130 writer.write("=\""); 131 writeDouble(writer, additionalLabelValue); 132 writer.write("\""); 133 } 134 writer.write('}'); 135 } 136 137 static void writeName(Writer writer, String name, NameType nameType) throws IOException { 138 switch (nameType) { 139 case Metric: 140 if (PrometheusNaming.isValidLegacyMetricName(name)) { 141 writer.write(name); 142 return; 143 } 144 break; 145 case Label: 146 if (PrometheusNaming.isValidLegacyLabelName(name)) { 147 writer.write(name); 148 return; 149 } 150 break; 151 default: 152 throw new RuntimeException("Invalid name type requested: " + nameType); 153 } 154 writer.write('"'); 155 writeEscapedString(writer, name); 156 writer.write('"'); 157 } 158}