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}