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