001package io.prometheus.metrics.core.metrics; 002 003import io.prometheus.metrics.config.PrometheusProperties; 004import io.prometheus.metrics.model.snapshots.InfoSnapshot; 005import io.prometheus.metrics.model.snapshots.Labels; 006import io.prometheus.metrics.model.snapshots.Unit; 007 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.List; 011import java.util.Set; 012import java.util.concurrent.CopyOnWriteArraySet; 013 014/** 015 * Info metric. Example: 016 * <pre>{@code 017 * Info info = Info.builder() 018 * .name("java_runtime_info") 019 * .help("Java runtime info") 020 * .labelNames("env", "version", "vendor", "runtime") 021 * .register(); 022 * 023 * String version = System.getProperty("java.runtime.version", "unknown"); 024 * String vendor = System.getProperty("java.vm.vendor", "unknown"); 025 * String runtime = System.getProperty("java.runtime.name", "unknown"); 026 * 027 * info.addLabelValues("prod", version, vendor, runtime); 028 * info.addLabelValues("dev", version, vendor, runtime); 029 * }</pre> 030 */ 031public class Info extends MetricWithFixedMetadata { 032 033 private final Set<Labels> labels = new CopyOnWriteArraySet<>(); 034 035 private Info(Builder builder) { 036 super(builder); 037 } 038 039 /** 040 * Set the info label values. This will replace any previous values, 041 * i.e. the info metric will only have one data point after calling setLabelValues(). 042 * This is good for a metric like {@code target_info} where you want only one single data point. 043 */ 044 public void setLabelValues(String... labelValues) { 045 if (labelValues.length != labelNames.length) { 046 throw new IllegalArgumentException(getClass().getSimpleName() + " " + getMetadata().getName() + " was created with " + labelNames.length + " label names, but you called setLabelValues() with " + labelValues.length + " label values."); 047 } 048 Labels newLabels = Labels.of(labelNames, labelValues); 049 labels.add(newLabels); 050 labels.retainAll(Collections.singletonList(newLabels)); 051 } 052 053 /** 054 * Create an info data point with the given label values. 055 */ 056 public void addLabelValues(String... labelValues) { 057 if (labelValues.length != labelNames.length) { 058 throw new IllegalArgumentException(getClass().getSimpleName() + " " + getMetadata().getName() + " was created with " + labelNames.length + " label names, but you called addLabelValues() with " + labelValues.length + " label values."); 059 } 060 labels.add(Labels.of(labelNames, labelValues)); 061 } 062 063 /** 064 * Remove the data point with the specified label values. 065 */ 066 public void remove(String... labelValues) { 067 if (labelValues.length != labelNames.length) { 068 throw new IllegalArgumentException(getClass().getSimpleName() + " " + getMetadata().getName() + " was created with " + labelNames.length + " label names, but you called remove() with " + labelValues.length + " label values."); 069 } 070 Labels toBeRemoved = Labels.of(labelNames, labelValues); 071 labels.remove(toBeRemoved); 072 } 073 074 /** 075 * {@inheritDoc} 076 */ 077 @Override 078 public InfoSnapshot collect() { 079 List<InfoSnapshot.InfoDataPointSnapshot> data = new ArrayList<>(labels.size()); 080 if (labelNames.length == 0) { 081 data.add(new InfoSnapshot.InfoDataPointSnapshot(constLabels)); 082 } else { 083 for (Labels label : labels) { 084 data.add(new InfoSnapshot.InfoDataPointSnapshot(label.merge(constLabels))); 085 } 086 } 087 return new InfoSnapshot(getMetadata(), data); 088 } 089 090 public static Builder builder() { 091 return new Builder(PrometheusProperties.get()); 092 } 093 094 public static Builder builder(PrometheusProperties config) { 095 return new Builder(config); 096 } 097 098 public static class Builder extends MetricWithFixedMetadata.Builder<Builder, Info> { 099 100 private Builder(PrometheusProperties config) { 101 super(Collections.emptyList(), config); 102 } 103 104 /** 105 * The {@code _info} suffix will automatically be appended if it's missing. 106 * <pre>{@code 107 * Info info1 = Info.builder() 108 * .name("runtime_info") 109 * .build(); 110 * Info info2 = Info.builder() 111 * .name("runtime") 112 * .build(); 113 * }</pre> 114 * In the example above both {@code info1} and {@code info2} will be named {@code "runtime_info"} in Prometheus. 115 * <p> 116 * Throws an {@link IllegalArgumentException} if 117 * {@link io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) MetricMetadata.isValidMetricName(name)} 118 * is {@code false}. 119 */ 120 @Override 121 public Builder name(String name) { 122 return super.name(stripInfoSuffix(name)); 123 } 124 125 /** 126 * Throws an {@link UnsupportedOperationException} because Info metrics cannot have a unit. 127 */ 128 @Override 129 public Builder unit(Unit unit) { 130 if (unit != null) { 131 throw new UnsupportedOperationException("Info metrics cannot have a unit."); 132 } 133 return this; 134 } 135 136 private static String stripInfoSuffix(String name) { 137 if (name != null && (name.endsWith("_info") || name.endsWith(".info"))) { 138 name = name.substring(0, name.length() - 5); 139 } 140 return name; 141 } 142 143 @Override 144 public Info build() { 145 return new Info(this); 146 } 147 148 @Override 149 protected Builder self() { 150 return this; 151 } 152 } 153}