简体   繁体   中英

How to export metrics (collected by DropWizard) in Prometheus format from java application?

My aim - create spring boot application, collect metrics using DropWizard and expose endpoint for Prometheus to consume application metrics:

My code:

@SpringBootApplication
@EnableMetrics(proxyTargetClass = true)
public class DemoApplication {

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);
    }

}

package com.example.demo;

import com.codahale.metrics.Counter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.annotation.Timed;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicLong;

@RestController
public class HelloController {
    private AtomicLong atomicLong = new AtomicLong();
    private Counter counter;

    @Autowired
    private MetricRegistry metricRegistry;

    @PostConstruct
    public void init() {
        counter = metricRegistry.counter("counter");
    }

    @GetMapping("/hello")
    @Timed(name = "my-index")
    public String index() {
        counter.inc();

        return "Greetings from Spring Boot!. count=" + atomicLong.incrementAndGet();
    }

}

package com.example.demo;

import com.codahale.metrics.ConsoleReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import com.codahale.metrics.servlets.AdminServlet;
import com.codahale.metrics.servlets.CpuProfileServlet;
import com.codahale.metrics.servlets.MetricsServlet;
import com.ryantenney.metrics.spring.config.annotation.EnableMetrics;
import com.ryantenney.metrics.spring.config.annotation.MetricsConfigurerAdapter;
import io.prometheus.client.dropwizard.DropwizardExports;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class Config /*extends MetricsConfigurerAdapter*/ {
    //@Override
            //public void configureReporters(MetricRegistry metricRegistry) {
        //    // registerReporter allows the MetricsConfigurerAdapter to
        //    // shut down the reporter when the Spring context is closed
        //   // registerReporter(ConsoleReporter
        //   //         .forRegistry(metricRegistry)
        //   //         .build())
        //   //         .start(1, TimeUnit.MINUTES);


        //    new DropwizardExports(metricRegistry).register();
        //}

   @Bean
   public DropwizardExports dropwizardExports(MetricRegistry metricRegistry){
       DropwizardExports dropwizardExports = new DropwizardExports(metricRegistry);
       dropwizardExports.register();
       return dropwizardExports;
   }

    @Bean
    public MetricRegistry metricRegistry() {
        MetricRegistry metricRegistry = new MetricRegistry();
        metricRegistry.registerAll(new GarbageCollectorMetricSet());
        metricRegistry.registerAll(new MemoryUsageGaugeSet());
        metricRegistry.registerAll(new ThreadStatesGaugeSet());
        return metricRegistry;
    }

    @Bean
    public ConsoleReporter consoleReporter(MetricRegistry metricRegistry) {
        ConsoleReporter reporter = ConsoleReporter.forRegistry(metricRegistry).build();
        reporter.start(5, TimeUnit.SECONDS);
        reporter.report();
        return reporter;
    }

    @Bean
    public ServletRegistrationBean<MetricsServlet> registerMetricsServlet(MetricRegistry metricRegistry) {
        return new ServletRegistrationBean<>(new MetricsServlet(metricRegistry), "/metrics/*");
    }

    @Bean
    public ServletRegistrationBean<CpuProfileServlet> registerCpuServlet() {
        return new ServletRegistrationBean<>(new CpuProfileServlet(), "/cpu/*");
    }
}

build.gradle:

plugins {
    id 'org.springframework.boot' version '2.7.1'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation "org.springframework.boot:spring-boot-starter-actuator"
    // Minimum required for metrics.
    implementation ('com.ryantenney.metrics:metrics-spring:3.1.3') {
        exclude group: 'com.codahale.metrics'
        exclude group: 'org.springframework'
    }
    implementation 'io.dropwizard.metrics:metrics-core:4.2.9'
    implementation 'io.dropwizard.metrics:metrics-annotation:4.2.9'
    implementation 'io.dropwizard.metrics:metrics-servlets:4.2.9'

    implementation 'io.prometheus:simpleclient_dropwizard:0.15.0'
    implementation 'io.prometheus:simpleclient_servlet:0.15.0'
    implementation 'io.dropwizard:dropwizard-core:2.1.0'

    implementation 'com.ryantenney.metrics:metrics-spring:3.1.3'
    implementation 'io.prometheus:simpleclient_common:0.16.0'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

I access localhost:8080/metrics and receive following response:

{
  "version": "4.0.0",
  "gauges": {
    "G1-Old-Generation.count": {
      "value": 0
    },
    "G1-Old-Generation.time": {
      "value": 0
    },
    "G1-Young-Generation.count": {
      "value": 7
    },
    "G1-Young-Generation.time": {
      "value": 31
    },
    "blocked.count": {
      "value": 0
    },
    "count": {
      "value": 26
    },
    "daemon.count": {
      "value": 22
    },
    "deadlock.count": {
      "value": 0
    },
    "deadlocks": {
      "value": []
    },
    "heap.committed": {
      "value": 301989888
    },
    "heap.init": {
      "value": 532676608
    },
    "heap.max": {
      "value": 8518631424
    },
    "heap.usage": {
      "value": 0.008041180864688155
    },
    "heap.used": {
      "value": 68499856
    },
    "new.count": {
      "value": 0
    },
    "non-heap.committed": {
      "value": 51707904
    },
    "non-heap.init": {
      "value": 2555904
    },
    "non-heap.max": {
      "value": -1
    },
    "non-heap.usage": {
      "value": -5.0738536E7
    },
    "non-heap.used": {
      "value": 50738536
    },
    "peak.count": {
      "value": 32
    },
    "pools.CodeCache.committed": {
      "value": 10551296
    },
    "pools.CodeCache.init": {
      "value": 2555904
    },
    "pools.CodeCache.max": {
      "value": 50331648
    },
    "pools.CodeCache.usage": {
      "value": 0.2039642333984375
    },
    "pools.CodeCache.used": {
      "value": 10265856
    },
    "pools.Compressed-Class-Space.committed": {
      "value": 5177344
    },
    "pools.Compressed-Class-Space.init": {
      "value": 0
    },
    "pools.Compressed-Class-Space.max": {
      "value": 1073741824
    },
    "pools.Compressed-Class-Space.usage": {
      "value": 0.004625104367733002
    },
    "pools.Compressed-Class-Space.used": {
      "value": 4966168
    },
    "pools.G1-Eden-Space.committed": {
      "value": 188743680
    },
    "pools.G1-Eden-Space.init": {
      "value": 29360128
    },
    "pools.G1-Eden-Space.max": {
      "value": -1
    },
    "pools.G1-Eden-Space.usage": {
      "value": 0.26666666666666666
    },
    "pools.G1-Eden-Space.used": {
      "value": 50331648
    },
    "pools.G1-Eden-Space.used-after-gc": {
      "value": 0
    },
    "pools.G1-Old-Gen.committed": {
      "value": 109051904
    },
    "pools.G1-Old-Gen.init": {
      "value": 503316480
    },
    "pools.G1-Old-Gen.max": {
      "value": 8518631424
    },
    "pools.G1-Old-Gen.usage": {
      "value": 0.0017806278080379123
    },
    "pools.G1-Old-Gen.used": {
      "value": 15168512
    },
    "pools.G1-Old-Gen.used-after-gc": {
      "value": 15168512
    },
    "pools.G1-Survivor-Space.committed": {
      "value": 4194304
    },
    "pools.G1-Survivor-Space.init": {
      "value": 0
    },
    "pools.G1-Survivor-Space.max": {
      "value": -1
    },
    "pools.G1-Survivor-Space.usage": {
      "value": 0.7151832580566406
    },
    "pools.G1-Survivor-Space.used": {
      "value": 2999696
    },
    "pools.G1-Survivor-Space.used-after-gc": {
      "value": 2999696
    },
    "pools.Metaspace.committed": {
      "value": 35979264
    },
    "pools.Metaspace.init": {
      "value": 0
    },
    "pools.Metaspace.max": {
      "value": -1
    },
    "pools.Metaspace.usage": {
      "value": 0.9868604316086066
    },
    "pools.Metaspace.used": {
      "value": 35506512
    },
    "runnable.count": {
      "value": 10
    },
    "terminated.count": {
      "value": 0
    },
    "timed_waiting.count": {
      "value": 5
    },
    "total.committed": {
      "value": 353697792
    },
    "total.init": {
      "value": 535232512
    },
    "total.max": {
      "value": 8518631423
    },
    "total.used": {
      "value": 119238392
    },
    "total_started.count": {
      "value": 47
    },
    "waiting.count": {
      "value": 11
    }
  },
  "counters": {
    "counter": {
      "count": 9
    }
  },
  "histograms": {},
  "meters": {},
  "timers": {}
}

Obviously this output is not applicable for Prometheus (all dots should be replaced with "_" at least)

How can I make output in format ready for prometheus ?

PS

Based on documentation I've understand that class io.prometheus.client.dropwizardDropwizardExports is responsible for generating metric in format ready for Prometheus but I can't understand how.

I have implemented different ways to export metrics to Prometheus:

1.1) Custom implementation for Pushgateway

I wrote code that generates output into a StringBuilder and just follows the documentation https://prometheus.io/docs/instrumenting/exposition_formats/

Finally POST that string to the Pushgateway using a java HttpClient of your choice.

1.2) Custom page to be scraped by Prometheus

I wrote a dynamic page (servlet, jsp, ...) that generates output with plain/text content type and just follows the documentation https://prometheus.io/docs/instrumenting/exposition_formats/

Configure Prometheus to scrape that dynamic page.

  1. use existing library

I made use of https://github.com/prometheus/client_java , which is the official client library for Java. Check the chapter about Exporting in the readme, it is quite good and covers both pushing the metrics as well as getting scraped.

I don't see significant difference but it is working example:

public class JavaDropwizard {
  // Create registry for Dropwizard metrics.
  static final MetricRegistry metrics = new MetricRegistry();
  // Create a Dropwizard counter.
  static final Counter counter = metrics.counter("my.example.counter.total");

  public static void main( String[] args ) throws Exception {
      // Increment the counter.
      counter.inc();

      // Hook the Dropwizard registry into the Prometheus registry
      // via the DropwizardExports collector.
      CollectorRegistry.defaultRegistry.register(new DropwizardExports(metrics));


      // Expose Prometheus metrics.
      Server server = new Server(1234);
      ServletContextHandler context = new ServletContextHandler();
      context.setContextPath("/");
      server.setHandler(context);
      context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics");
      // Add metrics about CPU, JVM memory etc.
      DefaultExports.initialize();
      // Start the webserver.
      server.start();
      server.join();
  }
}

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>io.robustperception.java_examples</groupId>
  <artifactId>java_dropwizard</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
  </properties>
  <name>java_dropwizard</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient</artifactId>
      <version>0.15.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_hotspot</artifactId>
      <version>0.15.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_servlet</artifactId>
      <version>0.15.0</version>
    </dependency>
    <dependency>
      <groupId>io.prometheus</groupId>
      <artifactId>simpleclient_dropwizard</artifactId>
      <version>0.15.0</version>
    </dependency>
    <dependency>
        <groupId>io.dropwizard.metrics</groupId>
        <artifactId>metrics-core</artifactId>
        <version>4.2.9</version>
    </dependency>
    <dependency>
      <groupId>org.eclipse.jetty</groupId>
      <artifactId>jetty-servlet</artifactId>
      <version>8.1.7.v20120910</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
      </plugin>
      <!-- Build a full jar with dependencies --> 
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <archive>
        <manifest>
          <mainClass>io.robustperception.java_examples.JavaDropwizard</mainClass>
        </manifest>
      </archive>
      <descriptorRefs>
        <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
    </configuration>
    <executions>
      <execution>
        <id>make-assembly</id>
        <phase>package</phase>
        <goals>
          <goal>single</goal>
        </goals>
      </execution>
    </executions>
  </plugin>
</plugins>

Finally I've found a root cause:

com.codahale.metrics.servlets.MetricsServlet

should be replaced with

io.prometheus.client.exporter.MetricsServlet

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM