Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
= PrometheusCollector
:description: Learn how to configure the PrometheusCollector in OpenNMS {page-component-title} to collect performance data via HTTP(S) using the Prometheus Exposition format.

The PrometheusCollector collects performance metrics via HTTP(S) using the text-based https://github.com/prometheus/docs/blob/master/content/docs/instrumenting/exposition_formats.md#text-based-format[Prometheus Exposition format].
Many applications have adopted it and it is in the process of being standardized in the https://openmetrics.io/[OpenMetrics] project.
The PrometheusCollector collects performance metrics via HTTP(S), supporting both the classic https://github.com/prometheus/docs/blob/main/docs/instrumenting/exposition_formats.md#prometheus-text-format[Prometheus Text Format] and the newer https://github.com/prometheus/docs/blob/main/docs/specs/om/open_metrics_spec.md[OpenMetrics 1.0 Text Format].

It can scrape endpoints that provide either format or both. When both are available, it automatically prioritizes OpenMetrics. This protocol negotiation is performed via quality/priority values in the HTTP header `Accept`.

This collector provides tools for parsing and mapping the metrics to the collection model used by {page-component-title}.

=== Limitations

The PrometheusCollector only processes a subset of the metric types defined in both formats, namely counters and gauges. Histograms and summaries are *not* supported and will be ignored. Metric timestamps are also ignored.

== Collector facts

[options="autowidth"]
Expand Down
6 changes: 6 additions & 0 deletions features/prometheus-collector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,11 @@
<version>1.57</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
Comment on lines +145 to +150

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without that dependency the mock framework in use throws a ClassNotFoundException

</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public void walk() {
((org.hawkular.agent.prometheus.types.Histogram) metric), metricIndex);
break;

case UNTYPED:
case UNKNOWN:
// if we can't tell what it is, it's a gauge.
log.debug("UNtyped metric: '{}'",metric);
walker.walkGaugeMetric(convertedMetricFamily, (Gauge) metric, metricIndex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@
*/
package org.hawkular.agent.prometheus;

import java.time.Instant;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Util {

private static final Logger LOGGER = LoggerFactory.getLogger(Util.class);

private Util() {
// prevent instantiation
}

public static double convertStringToDouble(String valueString) {
double doubleValue;
Expand All @@ -39,4 +50,19 @@ public static String convertDoubleToString(double value) {
}
return String.format("%f", value);
}

public static Instant tryConvertTimestamp(CharSequence timestamp) {
if (timestamp == null || timestamp.isEmpty()) {
return null;
}
try {
double timestampDouble = Double.parseDouble(timestamp.toString());
long epochSecond = (long) timestampDouble;
return Instant.ofEpochSecond(epochSecond);
}
catch (NumberFormatException e) {
LOGGER.warn("Invalid timestamp: " + timestamp);
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* Licensed to The OpenNMS Group, Inc (TOG) under one or more
* contributor license agreements. See the LICENSE.md file
* distributed with this work for additional information
* regarding copyright ownership.
*
* TOG licenses this file to You under the GNU Affero General
* Public License Version 3 (the "License") or (at your option)
* any later version. You may not use this file except in
* compliance with the License. You may obtain a copy of the
* License at:
*
* https://www.gnu.org/licenses/agpl-3.0.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
package org.hawkular.agent.prometheus.text;

import java.util.LinkedHashMap;
import java.util.Map;

import org.hawkular.agent.prometheus.Util;

class LineParser {

private enum ParseState {
NAME,
ENDOFNAME,
STARTOFLABELNAME,
LABELNAME,
LABELVALUEEQUALS,
LABELVALUEQUOTE,
LABELVALUE,
LABELVALUESLASH,
NEXTLABEL,
ENDOFLABELS,
VALUE,
TIMESTAMP
}

private final StringBuilder name = new StringBuilder();
private final StringBuilder labelname = new StringBuilder();
private final StringBuilder labelvalue = new StringBuilder();
private final StringBuilder value = new StringBuilder();
private final StringBuilder timestamp = new StringBuilder();
private final Map<String, String> labels = new LinkedHashMap<>();
private ParseState state;

public void reset() {
name.setLength(0);
labelname.setLength(0);
labelvalue.setLength(0);
value.setLength(0);
timestamp.setLength(0);
labels.clear();
state = ParseState.NAME;
}

public TextSample parse(String line) {
reset();

for (int c = 0; c < line.length(); c++) {
char charAt = line.charAt(c);

switch (state) {
case NAME:
if (charAt == '{') {
state = ParseState.STARTOFLABELNAME;
} else if (charAt == ' ' || charAt == '\t') {
state = ParseState.ENDOFNAME;
} else {
name.append(charAt);
}
break;
case ENDOFNAME:
if (charAt == ' ' || charAt == '\t') {
// do nothing
} else if (charAt == '{') {
state = ParseState.STARTOFLABELNAME;
} else {
value.append(charAt);
state = ParseState.VALUE;
}
break;
case STARTOFLABELNAME:
if (charAt == ' ' || charAt == '\t') {
// do nothing
} else if (charAt == '}') {
state = ParseState.ENDOFLABELS;
} else {
labelname.append(charAt);
state = ParseState.LABELNAME;
}
break;
case LABELNAME:
if (charAt == '=') {
state = ParseState.LABELVALUEQUOTE;
} else if (charAt == '}') {
state = ParseState.ENDOFLABELS;
} else if (charAt == ' ' || charAt == '\t') {
state = ParseState.LABELVALUEEQUALS;
} else {
labelname.append(charAt);
}
break;
case LABELVALUEEQUALS:
if (charAt == '=') {
state = ParseState.LABELVALUEQUOTE;
} else if (charAt == ' ' || charAt == '\t') {
// do nothing
} else {
throw new IllegalStateException("Invalid line: " + line);
}
break;
case LABELVALUEQUOTE:
if (charAt == '"') {
state = ParseState.LABELVALUE;
} else if (charAt == ' ' || charAt == '\t') {
// do nothing
} else {
throw new IllegalStateException("Invalid line: " + line);
}
break;
case LABELVALUE:
if (charAt == '\\') {
state = ParseState.LABELVALUESLASH;
} else if (charAt == '"') {
labels.put(labelname.toString(), labelvalue.toString());
labelname.setLength(0);
labelvalue.setLength(0);
state = ParseState.NEXTLABEL;
} else {
labelvalue.append(charAt);
}
break;
case LABELVALUESLASH:
state = ParseState.LABELVALUE;
if (charAt == '\\') {
labelvalue.append('\\');
} else if (charAt == 'n') {
labelvalue.append('\n');
} else if (charAt == '"') {
labelvalue.append('"');
} else {
labelvalue.append('\\').append(charAt);
}
break;
case NEXTLABEL:
if (charAt == ',') {
state = ParseState.LABELNAME;
} else if (charAt == '}') {
state = ParseState.ENDOFLABELS;
} else if (charAt == ' ' || charAt == '\t') {
// do nothing
} else {
throw new IllegalStateException("Invalid line: " + line);
}
break;
case ENDOFLABELS:
if (charAt == ' ' || charAt == '\t') {
// do nothing
} else {
value.append(charAt);
state = ParseState.VALUE;
}
break;
case VALUE:
if (charAt == ' ' || charAt == '\t') {
state = ParseState.TIMESTAMP;
} else {
value.append(charAt);
}
break;
case TIMESTAMP:
if (charAt == ' ' || charAt == '\t') {
return buildSample(line);
}
else {
timestamp.append(charAt);
}
break;
}
}

return buildSample(line);
}

private TextSample buildSample(String line) {
return new TextSample.Builder()
.setLine(line)
.setName(name.toString())
.setValue(value.toString())
.setTimestamp(Util.tryConvertTimestamp(timestamp))
.addLabels(new LinkedHashMap<>(labels))
.build();
}

}
Loading
Loading