You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@storm.apache.org by rz...@apache.org on 2024/06/07 19:18:46 UTC

(storm) branch experimental/prometheus_cluster_details_wip updated (bf59e13b3 -> 72129a03c)

This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a change to branch experimental/prometheus_cluster_details_wip
in repository https://gitbox.apache.org/repos/asf/storm.git


 discard bf59e13b3 WIP
 discard 9b8bc2a43 Fix metric naming
 discard 206275d1f WIP
 discard 4f6545a50 WIP
 discard f27163b18 WIP
     new 72129a03c STORM-4058 - Provide ClusterMetrics via a Prometheus Preparable Reporter

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (bf59e13b3)
            \
             N -- N -- N   refs/heads/experimental/prometheus_cluster_details_wip (72129a03c)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:


(storm) 01/01: STORM-4058 - Provide ClusterMetrics via a Prometheus Preparable Reporter

Posted by rz...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rzo1 pushed a commit to branch experimental/prometheus_cluster_details_wip
in repository https://gitbox.apache.org/repos/asf/storm.git

commit 72129a03cf391a1dc8ced1e273476989d70bc4a7
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Fri Jun 7 16:39:14 2024 +0200

    STORM-4058 - Provide ClusterMetrics via a Prometheus Preparable Reporter
---
 external/storm-metrics-prometheus/README.md        |  25 ++
 external/storm-metrics-prometheus/pom.xml          |  90 +++++++
 .../prometheus/PrometheusPreparableReporter.java   | 141 +++++++++++
 .../prometheus/PrometheusReporterClient.java       | 259 +++++++++++++++++++++
 .../PrometheusPreparableReporterTest.java          | 189 +++++++++++++++
 .../src/test/resources/pushgateway-basicauth.yaml  |   4 +
 .../src/test/resources/pushgateway-ssl.yaml        |  86 +++++++
 pom.xml                                            |   1 +
 8 files changed, 795 insertions(+)

diff --git a/external/storm-metrics-prometheus/README.md b/external/storm-metrics-prometheus/README.md
new file mode 100644
index 000000000..b67f2fb16
--- /dev/null
+++ b/external/storm-metrics-prometheus/README.md
@@ -0,0 +1,25 @@
+# Storm Metrics Prometheus
+
+This module contains a reporter to push [Cluster Metrics](https://storm.apache.org/releases/current/metrics_v2.html) to
+a [Prometheus Pushgateway](https://github.com/prometheus/pushgateway), where it can be scraped by a Prometheus instance.
+
+## Usage
+
+To use, edit your `storm.yaml` config file:
+
+```yaml
+storm.daemon.metrics.reporter.plugins:
+  - "org.apache.storm.metrics.prometheus.PrometheusPreparableReporter"
+storm.daemon.metrics.reporter.interval.secs: 10
+
+# Configuration for the Prometheus Pushgateway
+storm.daemon.metrics.reporter.plugin.prometheus.job: "job_name"
+storm.daemon.metrics.reporter.plugin.prometheus.endpoint: "localhost:9091"
+storm.daemon.metrics.reporter.plugin.prometheus.scheme: "http"
+storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_user: ""
+storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_password: ""
+storm.daemon.metrics.reporter.plugin.prometheus.skip_tls_validation: false
+```
+
+In addition, ensure to put this jar as well as the required transient
+dependencies for prometheus into `/lib` of your Storm installation.
diff --git a/external/storm-metrics-prometheus/pom.xml b/external/storm-metrics-prometheus/pom.xml
new file mode 100644
index 000000000..55d1096bb
--- /dev/null
+++ b/external/storm-metrics-prometheus/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ 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.
+-->
+<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/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>storm</artifactId>
+        <groupId>org.apache.storm</groupId>
+        <version>2.6.3-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storm-metrics-prometheus</artifactId>
+    <packaging>jar</packaging>
+
+    <name>storm-metrics-prometheus</name>
+
+    <properties>
+        <prometheus.client.version>1.3.0</prometheus.client.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.storm</groupId>
+            <artifactId>storm-server</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.prometheus</groupId>
+            <artifactId>prometheus-metrics-core</artifactId>
+            <version>${prometheus.client.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.prometheus</groupId>
+            <artifactId>prometheus-metrics-exporter-pushgateway</artifactId>
+            <version>${prometheus.client.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>testcontainers</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.testcontainers</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${testcontainers.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-pmd-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/external/storm-metrics-prometheus/src/main/java/org/apache/storm/metrics/prometheus/PrometheusPreparableReporter.java b/external/storm-metrics-prometheus/src/main/java/org/apache/storm/metrics/prometheus/PrometheusPreparableReporter.java
new file mode 100644
index 000000000..861d51e74
--- /dev/null
+++ b/external/storm-metrics-prometheus/src/main/java/org/apache/storm/metrics/prometheus/PrometheusPreparableReporter.java
@@ -0,0 +1,141 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The ASF licenses this file to you under the Apache License, Version
+ * 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.apache.storm.metrics.prometheus;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.MetricRegistry;
+import io.prometheus.metrics.exporter.pushgateway.HttpConnectionFactory;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway;
+import io.prometheus.metrics.exporter.pushgateway.Scheme;
+import org.apache.storm.DaemonConfig;
+import org.apache.storm.daemon.metrics.reporters.PreparableReporter;
+import org.apache.storm.utils.ObjectReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+public class PrometheusPreparableReporter implements PreparableReporter {
+
+    public static final Logger LOG = LoggerFactory.getLogger(PrometheusPreparableReporter.class);
+
+
+    static final TrustManager insecureTrustManager = new X509TrustManager() {
+
+        @Override
+        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+            return null;
+        }
+
+        @Override
+        public void checkClientTrusted(X509Certificate[] chain, String authType) {
+        }
+
+        @Override
+        public void checkServerTrusted(X509Certificate[] chain, String authType) {
+        }
+    };
+
+    static final HttpConnectionFactory insecureConnectionFactory = url -> {
+        try {
+            final SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, new TrustManager[]{insecureTrustManager}, null);
+            SSLContext.setDefault(sslContext);
+
+            final HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
+            connection.setHostnameVerifier((hostname, session) -> true);
+            return connection;
+        } catch (NoSuchAlgorithmException | KeyManagementException e) {
+            throw new RuntimeException(e);
+        }
+    };
+
+    private PrometheusReporterClient reporter;
+    private Integer reportingIntervalSecs;
+
+    protected PrometheusReporterClient getReporter() {
+        return reporter;
+    }
+
+    @Override
+    public void prepare(MetricRegistry metricsRegistry, Map<String, Object> daemonConf) {
+        if (daemonConf != null) {
+
+            final String jobName = (String) daemonConf.getOrDefault("storm.daemon.metrics.reporter.plugin.prometheus.job", "storm");
+            final String endpoint = (String) daemonConf.getOrDefault("storm.daemon.metrics.reporter.plugin.prometheus.endpoint", "localhost:9091");
+            final String schemeAsString = (String) daemonConf.getOrDefault("storm.daemon.metrics.reporter.plugin.prometheus.scheme", "http");
+
+            Scheme scheme = Scheme.HTTP;
+
+            try {
+                scheme = Scheme.fromString(schemeAsString);
+            } catch (IllegalArgumentException iae) {
+                LOG.warn("Unsupported scheme. Expecting 'http' or 'https'. Was: " + schemeAsString);
+            }
+
+            final String basicAuthUser = (String) daemonConf.getOrDefault("storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_user", "");
+            final String basicAuthPassword = (String) daemonConf.getOrDefault("storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_password", "");
+            final boolean skipTlsValidation = (boolean) daemonConf.getOrDefault("storm.daemon.metrics.reporter.plugin.prometheus.skip_tls_validation", false);
+
+            final PushGateway.Builder builder = PushGateway.builder();
+
+            builder.address(endpoint).job(jobName);
+
+            if (!basicAuthUser.isBlank() && !basicAuthPassword.isBlank()) {
+                builder.basicAuth(basicAuthUser, basicAuthPassword);
+            }
+
+            builder.scheme(scheme);
+
+            if (scheme == Scheme.HTTPS && skipTlsValidation) {
+                builder.connectionFactory(insecureConnectionFactory);
+            }
+
+            final PushGateway pushGateway = builder.build();
+
+            reporter = new PrometheusReporterClient(metricsRegistry, pushGateway);
+            reportingIntervalSecs = ObjectReader.getInt(daemonConf.get(DaemonConfig.STORM_DAEMON_METRICS_REPORTER_INTERVAL_SECS), 10);
+        } else {
+            LOG.warn("No daemonConfiguration was supplied. Don't initialize.");
+        }
+    }
+
+
+    @Override
+    public void start() {
+        if (reporter != null) {
+            LOG.debug("Starting...");
+            reporter.start(reportingIntervalSecs, TimeUnit.SECONDS);
+        } else {
+            throw new IllegalStateException("Attempt to start without preparing " + getClass().getSimpleName());
+        }
+    }
+
+    @Override
+    public void stop() {
+        if (reporter != null) {
+            LOG.debug("Stopping...");
+            reporter.report();
+            reporter.stop();
+        } else {
+            throw new IllegalStateException("Attempt to stop without preparing " + getClass().getSimpleName());
+        }
+    }
+}
diff --git a/external/storm-metrics-prometheus/src/main/java/org/apache/storm/metrics/prometheus/PrometheusReporterClient.java b/external/storm-metrics-prometheus/src/main/java/org/apache/storm/metrics/prometheus/PrometheusReporterClient.java
new file mode 100644
index 000000000..a403f7ec5
--- /dev/null
+++ b/external/storm-metrics-prometheus/src/main/java/org/apache/storm/metrics/prometheus/PrometheusReporterClient.java
@@ -0,0 +1,259 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The ASF licenses this file to you under the Apache License, Version
+ * 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.apache.storm.metrics.prometheus;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.ScheduledReporter;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import io.prometheus.metrics.exporter.pushgateway.PushGateway;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This reporter pushes common cluster metrics towards a Prometheus Pushgateway.
+ */
+public class PrometheusReporterClient extends ScheduledReporter {
+    private static final Logger LOG = LoggerFactory.getLogger(PrometheusReporterClient.class);
+
+    private static final Map<String, Object> CLUSTER_SUMMARY_METRICS = new HashMap<>();
+
+    private static final TimeUnit DURATION_UNIT = TimeUnit.MILLISECONDS;
+    private static final TimeUnit RATE_UNIT = TimeUnit.SECONDS;
+
+    private final PushGateway prometheus;
+
+    /**
+     * Creates a new {@link PrometheusReporterClient} instance.
+     *
+     * @param registry   the {@link MetricRegistry} containing the metrics this
+     *                   reporter will report
+     * @param prometheus the {@link PushGateway} which is responsible for sending metrics
+     *                   via a transport protocol
+     */
+    protected PrometheusReporterClient(MetricRegistry registry, PushGateway prometheus) {
+        super(registry, "prometheus-reporter", MetricFilter.ALL, RATE_UNIT, DURATION_UNIT, null, true, Collections.emptySet());
+        this.prometheus = prometheus;
+    }
+
+    @Override
+    public void report(SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) {
+        try {
+            if (CLUSTER_SUMMARY_METRICS.isEmpty()) {
+                initClusterMetrics();
+            }
+
+            for (Map.Entry<String, Gauge> e : gauges.entrySet()) {
+
+                final io.prometheus.metrics.core.metrics.Gauge pGauge = (io.prometheus.metrics.core.metrics.Gauge) CLUSTER_SUMMARY_METRICS.get(e.getKey());
+                if (pGauge != null) {
+                    try {
+                        pGauge.set(toDouble(e.getValue().getValue()));
+                    } catch (NumberFormatException ignored) {
+                        LOG.warn("Invalid type for Gauge {}: {}", e.getKey(), e.getValue().getClass().getName());
+                    }
+                }
+            }
+
+            for (Map.Entry<String, Histogram> e : histograms.entrySet()) {
+                final io.prometheus.metrics.core.metrics.Histogram pHisto = (io.prometheus.metrics.core.metrics.Histogram) CLUSTER_SUMMARY_METRICS.get(e.getKey());
+                if (pHisto != null) {
+                    final Snapshot s = e.getValue().getSnapshot();
+                    for (double d : s.getValues()) {
+                        pHisto.observe(d);
+                    }
+                }
+            }
+
+            //Counters, Timers and Meters are not implemented (yet). We don't need them for Cluster Summary Metrics, cf. https://storm.apache.org/releases/current/ClusterMetrics.html
+
+            prometheus.push();
+        } catch (IOException e) {
+            LOG.warn("Failed to push metrics to Prometheus Pushgateway,", e);
+        }
+    }
+
+    private double toDouble(Object obj) {
+        double value;
+        if (obj instanceof Number) {
+            value = ((Number) obj).doubleValue();
+        } else if (obj instanceof Boolean) {
+            value = ((Boolean) obj) ? 1 : 0;
+        } else {
+            value = Double.parseDouble(obj.toString());
+        }
+        return value;
+
+    }
+
+    private static void initClusterMetrics() {
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:num-nimbus-leaders", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_num_nimbus_leaders")
+                .help("Number of nimbuses marked as a leader. This should really only ever be 1 in a healthy cluster, or 0 for a short period of time while a fail over happens.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:num-nimbuses", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_num_nimbuses")
+                .help("Number of nimbuses, leader or standby.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:num-supervisors", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_num_supervisors")
+                .help("Number of supervisors.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:num-topologies", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_num_topologies")
+                .help("Number of topologies.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:num-total-used-workers", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_num_total_used_workers")
+                .help("Number of used workers/slots.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:num-total-workers", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_num_total_workers")
+                .help("Number of workers/slots.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:total-fragmented-cpu-non-negative", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_total_fragmented_cpu_non_negative")
+                .help("Total fragmented CPU (% of core). This is CPU that the system thinks it cannot use because other resources on the node are used up.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.cluster:total-fragmented-memory-non-negative", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("summary_cluster_total_fragmented_memory_non_negative")
+                .help("Total fragmented memory (MB). This is memory that the system thinks it cannot use because other resources on the node are used up.")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("nimbus:available-cpu-non-negative", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("nimbus_available_cpu_non_negative")
+                .help("Available cpu on the cluster (% of a core).")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("nimbus:total-cpu", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("nimbus_total_cpu")
+                .help("total CPU on the cluster (% of a core)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("nimbus:total-memory", io.prometheus.metrics.core.metrics.Gauge.builder()
+                .name("nimbus_total_memory")
+                .help("total memory on the cluster MB")
+                .register());
+
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:assigned-cpu", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_assigned_cpu")
+                .help("CPU scheduled per topology (% of a core)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:assigned-mem-off-heap", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_assigned_mem_off_heap")
+                .help("Off heap memory scheduled per topology (MB)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:assigned-mem-on-heap", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_assigned_mem_on_heap")
+                .help("On heap memory scheduled per topology (MB)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:num-executors", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_num_executors")
+                .help("Number of executors per topology")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:num-tasks", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_num_tasks")
+                .help("Number of tasks per topology")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:num-workers", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_num_workers")
+                .help("Number of workers per topology")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:replication-count", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_replication_count")
+                .help("Replication count per topology")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:requested-cpu", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_requested_cpu")
+                .help("CPU requested per topology (% of a core)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:requested-mem-off-heap", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_requested_mem_off_heap")
+                .help("Off heap memory requested per topology (MB)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:requested-mem-on-heap", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_requested_mem_on_heap")
+                .help("On heap memory requested per topology (MB)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.topologies:uptime-secs", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("summary_topologies_uptime_secs")
+                .help("Uptime per topology (seconds)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:fragmented-cpu", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_fragmented_cpu")
+                .help("fragmented CPU per supervisor (% of a core)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:fragmented-mem", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_fragmented_mem")
+                .help("fragmented memory per supervisor (MB)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:num-used-workers", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_num_used_workers")
+                .help("workers used per supervisor")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:num-workers", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_num_workers")
+                .help("number of workers per supervisor")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:uptime-secs", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_uptime_secs")
+                .help("uptime of supervisors")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:used-cpu", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_used_cpu")
+                .help("CPU used per supervisor (% of a core)")
+                .register());
+
+        CLUSTER_SUMMARY_METRICS.put("summary.supervisors:used-mem", io.prometheus.metrics.core.metrics.Histogram.builder()
+                .name("supervisors_used_mem")
+                .help("memory used per supervisor (MB)")
+                .register());
+
+    }
+
+}
diff --git a/external/storm-metrics-prometheus/src/test/java/org/apache/storm/metrics/prometheus/PrometheusPreparableReporterTest.java b/external/storm-metrics-prometheus/src/test/java/org/apache/storm/metrics/prometheus/PrometheusPreparableReporterTest.java
new file mode 100644
index 000000000..a49a808d7
--- /dev/null
+++ b/external/storm-metrics-prometheus/src/test/java/org/apache/storm/metrics/prometheus/PrometheusPreparableReporterTest.java
@@ -0,0 +1,189 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The ASF licenses this file to you under the Apache License, Version
+ * 2.0 (the "License"); you may not use this file except in compliance with the License.  You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.apache.storm.metrics.prometheus;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.storm.metrics2.SimpleGauge;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.MountableFile;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Testcontainers(disabledWithoutDocker = true)
+public class PrometheusPreparableReporterTest {
+
+    private GenericContainer<?> pushGatewayContainer;
+
+    @BeforeEach
+    public void setUp() {
+        pushGatewayContainer = new GenericContainer<>("prom/pushgateway:v1.8.0")
+                .withExposedPorts(9091)
+                .waitingFor(Wait.forListeningPort());
+    }
+
+    @AfterEach
+    public void tearDown() {
+        pushGatewayContainer.stop();
+    }
+
+    @Test
+    public void testSimple() throws IOException {
+        pushGatewayContainer.start();
+
+        final PrometheusPreparableReporter sut = new PrometheusPreparableReporter();
+
+        final Map<String, Object> daemonConf = Map.of(
+                "storm.daemon.metrics.reporter.plugin.prometheus.job", "test_simple",
+                "storm.daemon.metrics.reporter.plugin.prometheus.endpoint", "localhost:" + pushGatewayContainer.getMappedPort(9091),
+                "storm.daemon.metrics.reporter.plugin.prometheus.scheme", "http"
+        );
+
+        runTest(sut, daemonConf);
+
+    }
+
+    @Test
+    public void testBasicAuth() throws IOException {
+        pushGatewayContainer
+                .withCopyFileToContainer(MountableFile.forClasspathResource("/pushgateway-basicauth.yaml"), "/pushgateway/pushgateway-basicauth.yaml")
+                .withCommand("--web.config.file", "pushgateway-basicauth.yaml")
+                .start();
+
+        final PrometheusPreparableReporter sut = new PrometheusPreparableReporter();
+
+        final Map<String, Object> daemonConf = Map.of(
+                "storm.daemon.metrics.reporter.plugin.prometheus.job", "test_simple",
+                "storm.daemon.metrics.reporter.plugin.prometheus.endpoint", "localhost:" + pushGatewayContainer.getMappedPort(9091),
+                "storm.daemon.metrics.reporter.plugin.prometheus.scheme", "http",
+                "storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_user", "my_user",
+                "storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_password", "secret_password"
+        );
+
+        runTest(sut, daemonConf);
+    }
+
+    @Test
+    public void testTls() throws IOException {
+        pushGatewayContainer
+                .withCopyFileToContainer(MountableFile.forClasspathResource("/pushgateway-ssl.yaml"), "/pushgateway/pushgateway-ssl.yaml")
+                .withCommand("--web.config.file", "pushgateway-ssl.yaml")
+                .start();
+
+        final PrometheusPreparableReporter sut = new PrometheusPreparableReporter();
+
+        final Map<String, Object> daemonConf = Map.of(
+                "storm.daemon.metrics.reporter.plugin.prometheus.job", "test_simple",
+                "storm.daemon.metrics.reporter.plugin.prometheus.endpoint", "localhost:" + pushGatewayContainer.getMappedPort(9091),
+                "storm.daemon.metrics.reporter.plugin.prometheus.scheme", "https",
+                "storm.daemon.metrics.reporter.plugin.prometheus.skip_tls_validation", true
+        );
+
+        runTest(sut, daemonConf);
+    }
+
+
+    private void runTest(PrometheusPreparableReporter sut, Map<String, Object> daemonConf) throws IOException {
+        // We fake the metrics here. In a real Storm environment, these metrics are generated.
+        final MetricRegistry r = new MetricRegistry();
+        final SimpleGauge<Integer> supervisor = new SimpleGauge<>(5);
+        r.register("summary.cluster:num-supervisors", supervisor);
+        r.register("nimbus:total-memory", new SimpleGauge<>(5.6));
+        r.register("nimbus:total-cpu", new SimpleGauge<>("500"));
+
+        sut.prepare(r, daemonConf);
+
+        //manually trigger a reporting here, in a real Storm environment, this is called by a scheduled executor.
+        sut.getReporter().report();
+
+        assertMetrics(
+                List.of(
+                        "# HELP summary_cluster_num_supervisors Number of supervisors.",
+                        "# TYPE summary_cluster_num_supervisors gauge",
+                        "summary_cluster_num_supervisors{instance=\"\",job=\"test_simple\"} 5",
+                        "# HELP nimbus_total_memory total memory on the cluster MB",
+                        "# TYPE nimbus_total_memory gauge",
+                        "nimbus_total_memory{instance=\"\",job=\"test_simple\"} 5.6",
+                        "# HELP nimbus_total_cpu total CPU on the cluster (% of a core)",
+                        "# TYPE nimbus_total_cpu gauge",
+                        "nimbus_total_cpu{instance=\"\",job=\"test_simple\"} 500"
+                ), daemonConf.get("storm.daemon.metrics.reporter.plugin.prometheus.scheme") + "://" + daemonConf.get("storm.daemon.metrics.reporter.plugin.prometheus.endpoint") + "/metrics", daemonConf);
+
+        //update a metric
+        supervisor.set(100);
+
+        //manually trigger a reporting here, in a real Storm environment, this is called by a scheduled executor.
+        sut.getReporter().report();
+
+        assertMetrics(
+                List.of(
+                        "# HELP summary_cluster_num_supervisors Number of supervisors.",
+                        "# TYPE summary_cluster_num_supervisors gauge",
+                        "summary_cluster_num_supervisors{instance=\"\",job=\"test_simple\"} 100",
+                        "# HELP nimbus_total_memory total memory on the cluster MB",
+                        "# TYPE nimbus_total_memory gauge",
+                        "nimbus_total_memory{instance=\"\",job=\"test_simple\"} 5.6",
+                        "# HELP nimbus_total_cpu total CPU on the cluster (% of a core)",
+                        "# TYPE nimbus_total_cpu gauge",
+                        "nimbus_total_cpu{instance=\"\",job=\"test_simple\"} 500"
+                ), daemonConf.get("storm.daemon.metrics.reporter.plugin.prometheus.scheme") + "://" + daemonConf.get("storm.daemon.metrics.reporter.plugin.prometheus.endpoint") + "/metrics", daemonConf);
+    }
+
+    private void assertMetrics(List<String> elements, String endpoint, Map<String, Object> conf) throws IOException {
+        final String content = readContent(endpoint, conf);
+        Assertions.assertNotNull(content);
+        final Set<String> contentLinesSet = new HashSet<>(Arrays.asList(content.split("\n")));
+        elements.forEach(find -> Assertions.assertTrue(contentLinesSet.contains(find), "Did not find: " + find));
+    }
+
+    private String readContent(String url, Map<String, Object> conf) throws IOException {
+        final URL obj = new URL(url);
+        final HttpURLConnection con = (HttpURLConnection) obj.openConnection();
+        con.setRequestMethod("GET");
+
+        if (conf.containsKey("storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_user")) {
+
+            String auth = conf.get("storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_user") + ":" + conf.get("storm.daemon.metrics.reporter.plugin.prometheus.basic_auth_password");
+            String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
+            String authHeaderValue = "Basic " + encodedAuth;
+            con.setRequestProperty("Authorization", authHeaderValue);
+        }
+
+
+        try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
+            String inputLine;
+            StringBuilder response = new StringBuilder();
+
+            while ((inputLine = in.readLine()) != null) {
+                response.append(inputLine).append("\n");
+            }
+            return response.toString();
+        }
+
+    }
+
+}
diff --git a/external/storm-metrics-prometheus/src/test/resources/pushgateway-basicauth.yaml b/external/storm-metrics-prometheus/src/test/resources/pushgateway-basicauth.yaml
new file mode 100644
index 000000000..8b4751eb7
--- /dev/null
+++ b/external/storm-metrics-prometheus/src/test/resources/pushgateway-basicauth.yaml
@@ -0,0 +1,4 @@
+basic_auth_users:
+  # Note: The bcrypt hash of the password was generated with the following command line:
+  # python -c 'import bcrypt; print(bcrypt.hashpw(b"secret_password", bcrypt.gensalt(rounds=10)).decode("ascii"))'
+  my_user: $2b$10$kmIxr/4wpcORDXnKLvTMC.WPGqT8nqjBm8AI3MqGkzcSrWJioTfUG
\ No newline at end of file
diff --git a/external/storm-metrics-prometheus/src/test/resources/pushgateway-ssl.yaml b/external/storm-metrics-prometheus/src/test/resources/pushgateway-ssl.yaml
new file mode 100644
index 000000000..3e4208bbb
--- /dev/null
+++ b/external/storm-metrics-prometheus/src/test/resources/pushgateway-ssl.yaml
@@ -0,0 +1,86 @@
+tls_server_config:
+  # cert and key have been generated with the following command:
+  # openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 36500 -nodes -subj "/CN=localhost"
+  cert: |
+    -----BEGIN CERTIFICATE-----
+    MIIFCzCCAvOgAwIBAgIUPwSov6+heI4uY6+fvB1N+1EN3FwwDQYJKoZIhvcNAQEL
+    BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTI0MDUxMDE0MzY1NloYDzIxMjQw
+    NDE2MTQzNjU2WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEB
+    AQUAA4ICDwAwggIKAoICAQCnrySCLrYo5ad6w2/Tp/toGKnN4YW9C74eqmlqntht
+    VbGNkBha4FpEuuadjB64bH3dTSwXWg3ZMxUnjZplRCdosM7+beApzh8ZR+/Ju/qk
+    CPEw4N1J+NVZKzE7brt9rLKT+Cttjf4K8luUWnlVdOIl2UjqejCougV29TerctlD
+    svx7jUAoyauPhxbVC8Fmww8rCEox3xbv0MEe2bsc0hQP2opTRfXHwRKBq0hmA9x4
+    1FEqSl7gzQwbg7hHH/AfgxXSsQqRazIDzZEhDNePF0O9PYALrTJxonBrQ4uGBwJE
+    NQmlyIDnmynYmp5dOuXY8nspansOq7pQkubsg0qYiQ9VwLLZ/ApmFnvcY2uqSpta
+    TwqLalDYbUMqK61DtG6kHx3rHTokuLRFiXAqP++QdUkajqKtoH7quvKBAfzgrtHj
+    WJhNkDbaGaYlpyIlekyrxFdp25T06BEHzpFjylRGhuAaz2tbo1n7ynQ6KusdHEAf
+    l51JXQSS6yU/1Wy0yXo5yuKOykj0ey15s0AoH5yMHhEhAUVG0SKxtcWnnzAFcA9k
+    DYagco+IjQ+wRuX4jdM5S/l2Kmu8tvW0O7olNyxdWh2gzH/gLmt8ZthNEyTmb4mM
+    7kPNYjjcwrbs+oNc/Qfwk66+pn+vwYmRuZJolTZvGAhp9Es3OC19suDEgwz2bLYI
+    4wIDAQABo1MwUTAdBgNVHQ4EFgQUMx8SJAoEhboJXjRR4iz+/I2tUhkwHwYDVR0j
+    BBgwFoAUMx8SJAoEhboJXjRR4iz+/I2tUhkwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+    hkiG9w0BAQsFAAOCAgEASkE0xnofeUBGTZQK4BdRbqYgSaL9XSKi0UBH7Jw7a+nr
+    vdNu1VqOYRuSjI1FH2aYFKIaKEOipd8Z/nb1LjYArCerC51Mf/pl1mEDiUVyxECL
+    8F/IRj4xWwglbMMHpZw9wGYKAyG/QIpU/skbKEptAfUNb25kAVqhjuQ2vBb8w1kz
+    GdLf9pGXRCUefYtJIhgLVMDLhR7XVI8tsL2KfBE9fAMeSO/YAr1sa1wVKdqsmxQD
+    StQoecib3IhspO8QbRSJ10pb6p0sffTyU3jxDonv6b+E1jAslS0FQOxCUHnjwqG/
+    TuwW1MPxl4QeOpX00cI9ReZd2qla6+aaxZDccbpDmHtJJ3nKoFVwknUYEqTu+B7y
+    qZF2iyBtIaPJmdouMISMvlEsFdR4vkcD+2eCWMLlZDkOfDifIF1ny+ni8xU7UELa
+    XoDOKdIey7A/ddKi86mUdvjp5DRD85ghpSByn20UTdSmvbjHqpdlTPuFSyvix9nk
+    3KGJxS7Ra0hqOdGE8JHTmIXAFIjkOEEliNnmGRfd2l6EmDBEXufymlVgvz/DHlgF
+    krIsjl2SB9AUJckwmj17LdYN6pq9cUdaq7+7SIr12XCxPXyomIBSjlNyPCCy7u5L
+    nxDNTKImHmmupjoLCJ8MKpZ4fuva+kI372R47l2zMkwBWiEwn95C6+JB2kaFEiQ=
+    -----END CERTIFICATE-----
+  key: |
+    -----BEGIN PRIVATE KEY-----
+    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
+    v8yTmHG7BmzLiznu3uPyiEfAPQ3KQ9E=
+    -----END PRIVATE KEY-----
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index de54eef23..b14b036c5 100644
--- a/pom.xml
+++ b/pom.xml
@@ -495,6 +495,7 @@
                 <module>external/storm-redis</module>
                 <module>external/storm-elasticsearch</module>
                 <module>external/storm-metrics</module>
+                <module>external/storm-metrics-prometheus</module>
                 <module>external/storm-kafka-client</module>
                 <module>external/storm-kafka-migration</module>
                 <module>external/storm-kafka-monitor</module>