You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by or...@apache.org on 2020/09/02 19:32:55 UTC

[qpid-broker-j] 08/10: QPID-8454:[Broker-J] Add Prometheus integration

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

orudyy pushed a commit to branch 7.1.x
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git

commit d547af8d35306a22207d6ae25af3ff94cd7c6893
Author: Dedeepya T <de...@yahoo.co.in>
AuthorDate: Tue Aug 25 15:11:50 2020 +0100

    QPID-8454:[Broker-J] Add Prometheus integration
    
    (cherry picked from commit e9794802df6ee22bd3b9dc55faffc26d74459c71)
---
 .../dependency-verification/DEPENDENCIES_REFERENCE |   6 +
 .../java/org/apache/qpid/server/model/Broker.java  |  80 +++--
 .../qpid/server/model/BrokerAttributeInjector.java |  20 +-
 .../org/apache/qpid/server/model/BrokerLogger.java |   4 +-
 .../model/ConfiguredObjectInjectedStatistic.java   |  20 +-
 .../model/ConfiguredObjectMethodStatistic.java     |  12 +
 .../server/model/ConfiguredObjectStatistic.java    |   4 +
 .../org/apache/qpid/server/model/Connection.java   |  14 +-
 .../org/apache/qpid/server/model/Consumer.java     |   4 +-
 .../org/apache/qpid/server/model/Exchange.java     |  10 +-
 .../apache/qpid/server/model/ManagedStatistic.java |   2 +
 .../java/org/apache/qpid/server/model/Queue.java   |  36 +--
 .../java/org/apache/qpid/server/model/Session.java |   2 +-
 .../qpid/server/model/VirtualHostLogger.java       |   4 +-
 .../apache/qpid/server/model/port/AmqpPort.java    |  12 +-
 .../apache/qpid/server/plugin/ContentFactory.java} |  13 +-
 .../virtualhost/QueueManagingVirtualHost.java      |  28 +-
 .../hierarchy/InjectedAttributeTest.java           |  16 +-
 .../testmodels/hierarchy/TestAbstractCarImpl.java  |  21 ++
 .../hierarchy/TestAbstractEngineImpl.java          |   7 +
 .../hierarchy/TestAbstractInstrumentPanelImpl.java |   2 +
 .../hierarchy/TestAbstractSensorImpl.java          |   9 +
 .../server/model/testmodels/hierarchy/TestCar.java |  14 +
 .../model/testmodels/hierarchy/TestEngine.java     |   6 +
 .../model/testmodels/hierarchy/TestSensor.java     |   5 +
 .../hierarchy/TestTemperatureSensorImpl.java       |   9 +
 broker-plugins/management-http/pom.xml             |   5 +
 .../server/management/plugin/HttpManagement.java   |  18 ++
 .../plugin/HttpManagementConfiguration.java        |   5 +
 .../management/plugin/servlet/ContentServlet.java  |  95 ++++++
 .../plugin/servlet/rest/AbstractServlet.java       |  21 +-
 broker-plugins/prometheus-exporter/pom.xml         |  70 ++++
 .../IncludeDisabledStatisticPredicate.java         |  26 +-
 .../server/prometheus/IncludeMetricPredicate.java  |  26 +-
 .../prometheus/PrometheusContentFactory.java       | 105 ++++++
 .../qpid/server/prometheus/QpidCollector.java      | 257 +++++++++++++++
 .../prometheus/PrometheusContentFactoryTest.java   | 176 ++++++++++
 .../qpid/server/prometheus/QpidCollectorTest.java  | 359 +++++++++++++++++++++
 .../src/docbkx/Java-Broker-Management-Channels.xml |   1 +
 .../channels/Java-Broker-Management-Metrics.xml    |  48 +++
 pom.xml                                            |  20 ++
 .../metrics/BrokerMetricsAuthenticationTest.java   |  52 +++
 .../qpid/tests/http/metrics/BrokerMetricsTest.java |  80 +++++
 .../qpid/tests/http/metrics/TestMetricsHelper.java |  93 ++++++
 .../tests/http/metrics/VirtualHostMetricsTest.java |  25 +-
 45 files changed, 1712 insertions(+), 130 deletions(-)

diff --git a/apache-qpid-broker-j/src/main/assembly/dependency-verification/DEPENDENCIES_REFERENCE b/apache-qpid-broker-j/src/main/assembly/dependency-verification/DEPENDENCIES_REFERENCE
index c50e39d..d92150c 100644
--- a/apache-qpid-broker-j/src/main/assembly/dependency-verification/DEPENDENCIES_REFERENCE
+++ b/apache-qpid-broker-j/src/main/assembly/dependency-verification/DEPENDENCIES_REFERENCE
@@ -27,6 +27,10 @@ Apache Qpid Broker-J Bundles
 From: 'an unknown organization'
   - Guava: Google Core Libraries for Java (https://github.com/google/guava/guava) com.google.guava:guava:bundle:27.0-jre
     License: The Apache Software License, Version 2.0  (http://www.apache.org/licenses/LICENSE-2.0.txt)
+  - Prometheus Java Simpleclient (http://github.com/prometheus/client_java/simpleclient) io.prometheus:simpleclient:bundle:0.9.0
+    License: The Apache Software License, Version 2.0  (http://www.apache.org/licenses/LICENSE-2.0.txt)
+  - Prometheus Java Simpleclient Common (http://github.com/prometheus/client_java/simpleclient_common) io.prometheus:simpleclient_common:bundle:0.9.0
+    License: The Apache Software License, Version 2.0  (http://www.apache.org/licenses/LICENSE-2.0.txt)
   - dgrid (http://webjars.org) org.webjars.bower:dgrid:jar:1.2.1
     License: BSD 3-Clause  (https://spdx.org/licenses/BSD 3-Clause#licenseText)
   - dstore (http://webjars.org) org.webjars.bower:dstore:jar:1.1.2
@@ -111,6 +115,8 @@ From: 'The Apache Software Foundation' (https://www.apache.org/)
     License: Apache License, Version 2.0  (https://www.apache.org/licenses/LICENSE-2.0.txt)
   - Apache Qpid Broker-J Memory Message Store Plug-in (http://qpid.apache.org/components/broker-plugins/qpid-broker-plugins-memory-store) org.apache.qpid:qpid-broker-plugins-memory-store:jar
     License: Apache License, Version 2.0  (https://www.apache.org/licenses/LICENSE-2.0.txt)
+  - qpid-broker-plugins-prometheus-exporter (http://qpid.apache.org/components/broker-plugins/qpid-broker-plugins-prometheus-exporter) org.apache.qpid:qpid-broker-plugins-prometheus-exporter:jar
+    License: Apache License, Version 2.0  (https://www.apache.org/licenses/LICENSE-2.0.txt)
   - Apache Qpid Broker-J WebSocket Plug-in (http://qpid.apache.org/components/broker-plugins/qpid-broker-plugins-websocket) org.apache.qpid:qpid-broker-plugins-websocket:jar
     License: Apache License, Version 2.0  (https://www.apache.org/licenses/LICENSE-2.0.txt)
 
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java b/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
index 62e2519..24bdccb 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/Broker.java
@@ -189,36 +189,52 @@ public interface Broker<X extends Broker<X>> extends ConfiguredObject<X>, EventL
     String getModelVersion();
 
     @SuppressWarnings("unused")
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Inbound",
-                      description = "Total size of all messages received by the Broker.")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.BYTES,
+            label = "Inbound",
+            description = "Total size of all messages received by the Broker.",
+            metricName = "inbound_bytes_count")
     long getBytesIn();
 
     @SuppressWarnings("unused")
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Outbound",
-                      description = "Total size of all messages delivered by the Broker.")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.BYTES,
+            label = "Outbound",
+            description = "Total size of all messages delivered by the Broker.",
+            metricName = "outbound_bytes_count")
     long getBytesOut();
 
     @SuppressWarnings("unused")
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Inbound",
-                      description = "Total number of messages received by the Broker.")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.MESSAGES,
+            label = "Inbound",
+            description = "Total number of messages received by the Broker.",
+            metricName = "inbound_messages_count")
     long getMessagesIn();
 
     @SuppressWarnings("unused")
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Outbound",
-                      description = "Total number of messages delivered by the Broker.")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.MESSAGES,
+            label = "Outbound",
+            description = "Total number of messages delivered by the Broker.",
+            metricName = "outbound_messages_count")
     long getMessagesOut();
 
 
     @SuppressWarnings("unused")
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES,
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.MESSAGES,
             label = "Transacted Inbound",
-            description = "Total number of messages delivered by the Broker within a transaction.")
+            description = "Total number of messages delivered by the Broker within a transaction.",
+            metricName = "inbound_transacted_messages_count")
     long getTransactedMessagesIn();
 
     @SuppressWarnings("unused")
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES,
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.MESSAGES,
             label = "Transacted Outbound",
-            description = "Total number of messages received by the Broker within a transaction.")
+            description = "Total number of messages received by the Broker within a transaction.",
+            metricName = "outbound_transacted_messages_count")
     long getTransactedMessagesOut();
 
     @ManagedOperation(nonModifying = true,
@@ -249,37 +265,44 @@ public interface Broker<X extends Broker<X>> extends ConfiguredObject<X>, EventL
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
-                      units = StatisticUnit.COUNT,
-                      label = "Live threads",
-                      description = "Number of live threads")
+            units = StatisticUnit.COUNT,
+            label = "Live threads",
+            description = "Number of live threads",
+            metricName = "live_threads_total",
+            metricDisabled = true)
     int getNumberOfLiveThreads();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
-                      units = StatisticUnit.BYTES,
-                      label = "Used Heap Memory Size",
-                      description = "Size of used heap memory")
+            units = StatisticUnit.BYTES,
+            label = "Used Heap Memory Size",
+            description = "Size of used heap memory",
+            metricDisabled = true)
     long getUsedHeapMemorySize();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
-                      units = StatisticUnit.BYTES,
-                      label = "Used Direct Memory Size",
-                      description = "Size of used direct memory")
+            units = StatisticUnit.BYTES,
+            label = "Used Direct Memory Size",
+            description = "Size of used direct memory",
+            metricDisabled = true)
     long getUsedDirectMemorySize();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
-                      units = StatisticUnit.BYTES,
-                      label = "Direct Memory Total Capacity",
-                      description = "Total capacity of direct memory allocated for the Broker process")
+            units = StatisticUnit.BYTES,
+            label = "Direct Memory Total Capacity",
+            description = "Total capacity of direct memory allocated for the Broker process",
+            metricName = "direct_memory_capacity_bytes_total",
+            metricDisabled = true)
     long getDirectMemoryTotalCapacity();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
-                      units = StatisticUnit.COUNT,
-                      label = "Number Of Object Pending Finalization",
-                      description = "Number of objects pending finalization")
+            units = StatisticUnit.COUNT,
+            label = "Number Of Object Pending Finalization",
+            description = "Number of objects pending finalization",
+            metricDisabled = true)
     int getNumberOfObjectsPendingFinalization();
 
     @SuppressWarnings("unused")
@@ -300,7 +323,8 @@ public interface Broker<X extends Broker<X>> extends ConfiguredObject<X>, EventL
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
             units = StatisticUnit.BYTES,
             label = "Maximum recorded size of inbound messages",
-            description = "Maximum size of messages published into the Broker since start-up.")
+            description = "Maximum size of messages published into the Broker since start-up.",
+            metricName = "inbound_message_size_high_watermark")
     long getInboundMessageSizeHighWatermark();
 
     @ManagedOperation(nonModifying = true,
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/BrokerAttributeInjector.java b/broker-core/src/main/java/org/apache/qpid/server/model/BrokerAttributeInjector.java
index 61be55f..a80a041 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/BrokerAttributeInjector.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/BrokerAttributeInjector.java
@@ -167,7 +167,9 @@ public class BrokerAttributeInjector implements ConfiguredObjectAttributeInjecto
                                                                 _typeValidator,
                                                                 StatisticUnit.BYTES,
                                                                 StatisticType.POINT_IN_TIME,
-                                                                memoryPoolMXBean.getName() + " Memory Used");
+                                                                memoryPoolMXBean.getName() + " Memory Used",
+                                                                null,
+                                                                true);
                 statistics.add(injectedStatistic);
             }
             catch (NoSuchMethodException e)
@@ -195,7 +197,9 @@ public class BrokerAttributeInjector implements ConfiguredObjectAttributeInjecto
                                                                 StatisticUnit.COUNT,
                                                                 StatisticType.CUMULATIVE,
                                                                 garbageCollectorMXBean.getName()
-                                                                + " GC Collection Time");
+                                                                + " GC Collection Time",
+                                                                null,
+                                                                true);
                 statistics.add(injectedStatistic);
             }
             catch (NoSuchMethodException e)
@@ -218,7 +222,9 @@ public class BrokerAttributeInjector implements ConfiguredObjectAttributeInjecto
                                                                 StatisticUnit.COUNT,
                                                                 StatisticType.CUMULATIVE,
                                                                 garbageCollectorMXBean.getName()
-                                                                + " GC Collection Count");
+                                                                + " GC Collection Count",
+                                                                null,
+                                                                true);
                 statistics.add(injectedStatistic);
             }
             catch (NoSuchMethodException e)
@@ -263,7 +269,9 @@ public class BrokerAttributeInjector implements ConfiguredObjectAttributeInjecto
                                                                 StatisticUnit.TIME_DURATION,
                                                                 StatisticType.CUMULATIVE,
                                                                 _operatingSystemMXBeanClass.getName()
-                                                                + " Process CPU Time");
+                                                                + " Process CPU Time",
+                                                                "process_cpu_time_nanoseconds",
+                                                                true);
                 statistics.add(injectedStatistic);
 
             }
@@ -308,7 +316,9 @@ public class BrokerAttributeInjector implements ConfiguredObjectAttributeInjecto
                                                                 StatisticUnit.COUNT,
                                                                 StatisticType.POINT_IN_TIME,
                                                                 _operatingSystemMXBean.getName()
-                                                                + " Process CPU Load");
+                                                                + " Process CPU Load",
+                                                                null,
+                                                                true);
                 statistics.add(injectedStatistic);
 
             }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/BrokerLogger.java b/broker-core/src/main/java/org/apache/qpid/server/model/BrokerLogger.java
index 07e61cd..8c4ad5b 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/BrokerLogger.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/BrokerLogger.java
@@ -28,9 +28,9 @@ public interface BrokerLogger<X extends BrokerLogger<X>> extends ConfiguredObjec
 
     void stopLogging();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Errors")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Errors", metricName = "errors_count")
     long getErrorCount();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Warnings")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Warnings", metricName = "warnings_count")
     long getWarnCount();
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectInjectedStatistic.java b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectInjectedStatistic.java
index 95c8507..f1d9c6b 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectInjectedStatistic.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectInjectedStatistic.java
@@ -41,6 +41,8 @@ final public class ConfiguredObjectInjectedStatistic<C extends ConfiguredObject,
     private final StatisticType _type;
     private final String _label;
     private final Object[] _staticParams;
+    private final String _metricName;
+    private final boolean _metricDisabled;
 
     public ConfiguredObjectInjectedStatistic(final String name,
                                              final Method method,
@@ -49,7 +51,9 @@ final public class ConfiguredObjectInjectedStatistic<C extends ConfiguredObject,
                                              final TypeValidator typeValidator,
                                              final StatisticUnit units,
                                              final StatisticType type,
-                                             final String label)
+                                             final String label,
+                                             final String metricName,
+                                             final boolean metricDisabled)
     {
         super(name,
               (Class<T>) AttributeValueConverter.getTypeFromMethod(method), method.getGenericReturnType(), typeValidator);
@@ -57,6 +61,8 @@ final public class ConfiguredObjectInjectedStatistic<C extends ConfiguredObject,
         _type = type;
         _label = label;
         _staticParams = staticParams == null ? new Object[0] : staticParams;
+        _metricName = metricName;
+        _metricDisabled = metricDisabled;
         if(!(method.getParameterTypes().length == 1 + _staticParams.length
              && ConfiguredObject.class.isAssignableFrom(method.getParameterTypes()[0])
              && Modifier.isStatic(method.getModifiers())
@@ -147,4 +153,16 @@ final public class ConfiguredObjectInjectedStatistic<C extends ConfiguredObject,
         }
 
     }
+
+    @Override
+    public String getMetricName()
+    {
+        return _metricName;
+    }
+
+    @Override
+    public boolean isMetricDisabled()
+    {
+        return _metricDisabled;
+    }
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodStatistic.java b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodStatistic.java
index 2cc2fe9..528d80e 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodStatistic.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodStatistic.java
@@ -66,4 +66,16 @@ public final class ConfiguredObjectMethodStatistic<C extends ConfiguredObject, T
     {
         return _annotation.label();
     }
+
+    @Override
+    public String getMetricName()
+    {
+        return _annotation.metricName();
+    }
+
+    @Override
+    public boolean isMetricDisabled()
+    {
+        return _annotation.metricDisabled();
+    }
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
index cc22d62..4585b42 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
@@ -29,4 +29,8 @@ public interface ConfiguredObjectStatistic<C extends ConfiguredObject, T extends
     StatisticType getStatisticType();
 
     String getLabel();
+
+    String getMetricName();
+
+    boolean isMetricDisabled();
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java b/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java
index 62cefc1..0638e53 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/Connection.java
@@ -117,24 +117,24 @@ public interface Connection<X extends Connection<X>> extends ConfiguredObject<X>
     // See also QPID-7689: https://issues.apache.org/jira/browse/QPID-7689?focusedCommentId=16022923#comment-16022923
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Inbound",
-                      description = "Total size of all messages received by this connection.")
+                      description = "Total size of all messages received by this connection.", metricName = "inbound_bytes_count")
     long getBytesIn();
 
     // currently this reports outbound message content size without header.
     // See also QPID-7689: https://issues.apache.org/jira/browse/QPID-7689?focusedCommentId=16022923#comment-16022923
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Outbound",
-                      description = "Total size of all messages delivered by this connection.")
+                      description = "Total size of all messages delivered by this connection.", metricName = "outbound_bytes_count")
     long getBytesOut();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Inbound",
-                      description = "Total number of messages delivered by this connection.")
+                      description = "Total number of messages delivered by this connection.", metricName = "inbound_messages_count")
     long getMessagesIn();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Outbound",
-                      description = "Total number of messages received by this connection.")
+                      description = "Total number of messages received by this connection.", metricName = "outbound_messages_count")
     long getMessagesOut();
 
     @SuppressWarnings("unused")
@@ -162,7 +162,7 @@ public interface Connection<X extends Connection<X>> extends ConfiguredObject<X>
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Sessions",
-                      description = "Current number of sessions belonging to this connection.")
+                      description = "Current number of sessions belonging to this connection.", metricName = "sessions_total")
     int getSessionCount();
 
     @SuppressWarnings("unused")
@@ -188,12 +188,12 @@ public interface Connection<X extends Connection<X>> extends ConfiguredObject<X>
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Transacted Inbound",
-            description = "Total number of messages delivered by this connection within a transaction.")
+            description = "Total number of messages delivered by this connection within a transaction.", metricName = "transacted_inbound_messages_count")
     long getTransactedMessagesIn();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Transacted Outbound",
-            description = "Total number of messages received by this connection within a transaction.")
+            description = "Total number of messages received by this connection within a transaction.", metricName = "transacted_outbound_messages_count")
     long getTransactedMessagesOut();
 
     //children
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java b/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java
index 9db4b21..240d8a6 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/Consumer.java
@@ -70,10 +70,10 @@ public interface Consumer<X extends Consumer<X,T>, T extends ConsumerTarget> ext
                         + "consumers.  Priority 2147483647 is the highest priority.")
     int getPriority();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Outbound")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Outbound", metricName = "outbound_bytes_count")
     long getBytesOut();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Outbound")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Outbound", metricName = "outbound_messages_count")
     long getMessagesOut();
 
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.BYTES, label = "Prefetch")
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java b/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java
index 800f57c..5e2d68f 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/Exchange.java
@@ -88,27 +88,27 @@ public interface Exchange<X extends Exchange<X>> extends ConfiguredObject<X>, Me
     // Statistics
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Bindings",
-                      description = "Current number of bindings to this exchange.")
+                      description = "Current number of bindings to this exchange.", metricName = "bindings_total")
     long getBindingCount();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Dropped",
-                      description = "Total size of all unroutable messages dropped by this exchange.")
+                      description = "Total size of all unroutable messages dropped by this exchange.", metricName = "dropped_bytes_count")
     long getBytesDropped();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Inbound",
-                      description = "Total size of messages received by this exchange.")
+                      description = "Total size of messages received by this exchange.", metricName = "inbound_bytes_count")
     long getBytesIn();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Dropped",
-                      description = "Number of unroutable messages dropped by this exchange.")
+                      description = "Number of unroutable messages dropped by this exchange.", metricName = "dropped_messages_count")
     long getMessagesDropped();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Inbound",
-                      description = "Number of messages received by this exchange.")
+                      description = "Number of messages received by this exchange.", metricName = "inbound_messages_count")
     long getMessagesIn();
 
 
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ManagedStatistic.java b/broker-core/src/main/java/org/apache/qpid/server/model/ManagedStatistic.java
index d9bb129..1bcd52e 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ManagedStatistic.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/ManagedStatistic.java
@@ -33,4 +33,6 @@ public @interface ManagedStatistic
     String label() default "";
     StatisticUnit units();
     StatisticType statisticType();
+    String metricName()  default "";
+    boolean metricDisabled() default false;
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java b/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java
index 3920a14..bfb7e16 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/Queue.java
@@ -370,17 +370,17 @@ public interface Queue<X extends Queue<X>> extends ConfiguredObject<X>,
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Bindings",
-                      description = "Current number of bindings to this queue.")
+                      description = "Current number of bindings to this queue.", metricName = "bindings_total")
     int getBindingCount();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Consumers",
-                      description = "Current number of consumers attached to this queue.")
+                      description = "Current number of consumers attached to this queue.", metricName = "consumers_total")
     int getConsumerCount();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Consumers with credit",
-                      description = "Current number of consumers attached to this queue with credit")
+                      description = "Current number of consumers attached to this queue with credit", metricName = "consumers_with_credit_total")
     int getConsumerCountWithCredit();
 
     @SuppressWarnings("unused")
@@ -405,42 +405,42 @@ public interface Queue<X extends Queue<X>> extends ConfiguredObject<X>,
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.BYTES, label = "Queue Depth",
-                      description = "Current size of all messages enqueued by this queue.")
+                      description = "Current size of all messages enqueued by this queue.", metricName = "depth_bytes_total")
     long getQueueDepthBytes();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.MESSAGES, label = "Queue Depth",
-                      description = "Current number of messages enqueued by this queue.")
+                      description = "Current number of messages enqueued by this queue.", metricName = "depth_messages_total")
     int getQueueDepthMessages();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Delivered",
-                      description = "Total size of all messages delivered by this queue.")
+                      description = "Total size of all messages delivered by this queue.", metricName = "dequeued_bytes_count")
     long getTotalDequeuedBytes();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Delivered",
-                      description = "Total number of messages delivered by this queue.")
+                      description = "Total number of messages delivered by this queue.", metricName = "dequeued_messages_count")
     long getTotalDequeuedMessages();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Enqueued",
-                      description = "Total size of all messages received by this queue.")
+                      description = "Total size of all messages received by this queue.", metricName = "enqueue_bytes_count")
     long getTotalEnqueuedBytes();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Enqueued",
-                      description = "Total number of messages received by this queue.")
+                      description = "Total number of messages received by this queue.", metricName = "enqueued_messages_count")
     long getTotalEnqueuedMessages();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Expired",
-            description = "Total size of all messages expired by message time-to-live on this queue.")
+            description = "Total size of all messages expired by message time-to-live on this queue.", metricName = "expired_bytes_count")
     long getTotalExpiredBytes();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Expired",
-            description = "Total number of messages expired by message time-to-live on this queue.")
+            description = "Total number of messages expired by message time-to-live on this queue.", metricName = "expired_messages_count")
     long getTotalExpiredMessages();
 
 
@@ -466,37 +466,37 @@ public interface Queue<X extends Queue<X>> extends ConfiguredObject<X>,
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.BYTES, label = "Available HWM",
-                      description = "Maximum recorded size of available messages.")
+                      description = "Maximum recorded size of available messages.", metricName = "available_bytes_high_water_mark")
     long getAvailableBytesHighWatermark();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.MESSAGES, label = "Available HWM",
-                      description = "Maximum recorded number of available messages.")
+                      description = "Maximum recorded number of available messages.", metricName = "available_messages_high_water_mark")
     int getAvailableMessagesHighWatermark();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.BYTES, label = "Queue Depth HWM",
-                      description = "Maximum recorded size of enqueued messages.")
+                      description = "Maximum recorded size of enqueued messages.", metricName = "depth_bytes_high_water_mark")
     long getQueueDepthBytesHighWatermark();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.MESSAGES, label = "Queue Depth HWM",
-                      description = "Maximum recorded number of enqueued messages.")
+                      description = "Maximum recorded number of enqueued messages.", metricName = "depth_messages_high_water_mark")
     int getQueueDepthMessagesHighWatermark();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.TIME_DURATION, label = "Oldest Message",
-                      description = "Current age of oldest message on the queue.")
+                      description = "Current age of oldest message on the queue.", metricName = "oldest_message_age_milliseconds")
     long getOldestMessageAge();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Malformed",
-            description = "Total size of enqueued malformed messages.")
+            description = "Total size of enqueued malformed messages.", metricName = "malformed_bytes_count")
     long getTotalMalformedBytes();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Malformed",
-            description = "Total number of enqueued malformed messages.")
+            description = "Total number of enqueued malformed messages.", metricName = "malformed_messages_count")
     long getTotalMalformedMessages();
 
     @ManagedOperation(description = "move messages from this queue to another", changesConfiguredObjectState = false)
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/Session.java b/broker-core/src/main/java/org/apache/qpid/server/model/Session.java
index af1084e..343e5ba 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/Session.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/Session.java
@@ -55,7 +55,7 @@ public interface Session<X extends Session<X>> extends ConfiguredObject<X>
     boolean isProducerFlowBlocked();
 
 
-    @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Consumers")
+    @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Consumers", metricName = "consumers_total")
     long getConsumerCount();
 
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.MESSAGES, label = "Prefetched")
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostLogger.java b/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostLogger.java
index c30f6a7..6599a0f 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostLogger.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/VirtualHostLogger.java
@@ -25,9 +25,9 @@ public interface VirtualHostLogger <X extends VirtualHostLogger<X>> extends Conf
 {
     void stopLogging();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Errors")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Errors", metricName = "errors_count")
     long getErrorCount();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Warnings")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Warnings", metricName = "warnings_count")
     long getWarnCount();
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java b/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java
index f88b99a..530d603 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/model/port/AmqpPort.java
@@ -159,12 +159,16 @@ public interface AmqpPort<X extends AmqpPort<X>> extends Port<X>
     @ManagedAttribute( defaultValue = "${" + PORT_MAX_OPEN_CONNECTIONS + "}" )
     int getMaxOpenConnections();
 
-    @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Open Connections",
-                      description = "Current number of connections made through this port")
+    @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT,
+            label = "Open Connections",
+            description = "Current number of connections made through this port",
+            metricName = "open_connections_total")
     int getConnectionCount();
 
-    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Total Connections",
-            description = "Total number of connections made through this port since broker startup")
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT,
+            label = "Total Connections",
+            description = "Total number of connections made through this port since broker startup",
+            metricName = "aggregate_connection_count")
     long getTotalConnectionCount();
 
     @DerivedAttribute(description = "Maximum time allowed for a new connection to send a protocol header."
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java b/broker-core/src/main/java/org/apache/qpid/server/plugin/ContentFactory.java
similarity index 75%
copy from broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java
copy to broker-core/src/main/java/org/apache/qpid/server/plugin/ContentFactory.java
index 41e07f2..bc0ad33 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/plugin/ContentFactory.java
@@ -1,5 +1,4 @@
 /*
- *
  * 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
@@ -18,13 +17,15 @@
  * under the License.
  *
  */
-package org.apache.qpid.server.model.testmodels.hierarchy;
+
+package org.apache.qpid.server.plugin;
+
+import java.util.Map;
 
 import org.apache.qpid.server.model.ConfiguredObject;
-import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.Content;
 
-@ManagedObject( defaultType = TestTemperatureSensorImpl.TEST_TEMPERATURE_SENSOR_TYPE)
-public interface TestSensor<X extends TestSensor<X>> extends ConfiguredObject<X>
+public interface ContentFactory extends Pluggable
 {
-
+    Content createContent(ConfiguredObject<?> object, Map<String, String[]> filter);
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueManagingVirtualHost.java b/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueManagingVirtualHost.java
index 9f1b318..4cd276a 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueManagingVirtualHost.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/virtualhost/QueueManagingVirtualHost.java
@@ -210,69 +210,69 @@ public interface QueueManagingVirtualHost<X extends QueueManagingVirtualHost<X>>
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Queues",
-                      description = "Current number of queues on this virtualhost.")
+                      description = "Current number of queues on this virtualhost.", metricName = "queues_total")
     long getQueueCount();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Exchanges",
-                      description = "Current number of exchanges on this virtualhost.")
+                      description = "Current number of exchanges on this virtualhost.", metricName = "exchanges_total")
     long getExchangeCount();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT, label = "Connections",
-                      description = "Current number of messaging connections made to this virtualhost.")
+                      description = "Current number of messaging connections made to this virtualhost.", metricName = "connections_total")
     long getConnectionCount();
 
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT, label = "Total Connections",
-            description = "Total number of messaging connections made to this virtualhost since broker startup")
+            description = "Total number of messaging connections made to this virtualhost since broker startup", metricName = "aggregate_connection_count")
     long getTotalConnectionCount();
 
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Inbound",
-                      description = "Total size of all messages received by this virtualhost.")
+                      description = "Total size of all messages received by this virtualhost.", metricName = "inbound_bytes_count")
     long getBytesIn();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.BYTES, label = "Outbound",
-                      description = "Total size of all messages delivered by this virtualhost.")
+                      description = "Total size of all messages delivered by this virtualhost.", metricName = "outbound_bytes_count")
     long getBytesOut();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Inbound",
-                      description = "Total number of messages received by this virtualhost.")
+                      description = "Total number of messages received by this virtualhost.", metricName = "inbound_messages_count")
     long getMessagesIn();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES, label = "Outbound",
-                      description = "Total number of messages delivered by this virtualhost.")
+                      description = "Total number of messages delivered by this virtualhost.", metricName = "outbound_messages_count")
     long getMessagesOut();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES,
             label = "Transacted Inbound",
-            description = "Total number of messages delivered by this virtualhost within a transaction.")
+            description = "Total number of messages delivered by this virtualhost within a transaction.", metricName = "inbound_transacted_messages_count")
     long getTransactedMessagesIn();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.MESSAGES,
             label = "Transacted Outbound",
-            description = "Total number of messages received by this virtualhost within a transaction.")
+            description = "Total number of messages received by this virtualhost within a transaction.", metricName = "outbound_transacted_messages_count")
     long getTransactedMessagesOut();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.BYTES, label = "Queue Depth",
-            description = "Current size of all messages enqueued by this virtualhost.")
+            description = "Current size of all messages enqueued by this virtualhost.", metricName = "queue_depth_bytes_total")
     long getTotalDepthOfQueuesBytes();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.MESSAGES, label = "Queue Depth",
-                      description = "Current number of messages enqueued by this virtualhost.")
+                      description = "Current number of messages enqueued by this virtualhost.", metricName = "queue_depth_messages_total")
     long getTotalDepthOfQueuesMessages();
 
     @SuppressWarnings("unused")
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.BYTES, label = "In-Memory Message Bytes",
-                      description="Current size of all messages cached in-memory.")
+                      description="Current size of all messages cached in-memory.", metricName = "in_memory_message_size_bytes_total")
     long getInMemoryMessageSize();
 
     @SuppressWarnings("unused")
@@ -284,7 +284,7 @@ public interface QueueManagingVirtualHost<X extends QueueManagingVirtualHost<X>>
     @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME,
             units = StatisticUnit.BYTES,
             label = "Maximum recorded size of inbound messages",
-            description = "Maximum size of message published into the Virtual Host since start-up.")
+            description = "Maximum size of message published into the Virtual Host since start-up.", metricName = "inbound_message_size_high_water_mark")
     long getInboundMessageSizeHighWatermark();
 
     @Override
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/InjectedAttributeTest.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/InjectedAttributeTest.java
index 90ce83e..8afa95e 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/InjectedAttributeTest.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/InjectedAttributeTest.java
@@ -292,14 +292,16 @@ public class InjectedAttributeTest extends UnitTestBase
                                                                            TYPE_VALIDATOR,
                                                                            StatisticUnit.COUNT,
                                                                            StatisticType.POINT_IN_TIME,
-                                                                           "What is 6 x 9?");
+                                                                           "What is 6 x 9?",
+                                                                           null,
+                                                                           false);
 
         TestModel model = new TestModel(null, new TestInjector(statInjector));
 
         TestCar<?> testCar = new TestStandardCarImpl(Collections.<String,Object>singletonMap("name", "Arthur"), model);
 
         final Map<String, Object> statistics = testCar.getStatistics();
-        assertEquals("incorrect number of statistics", (long) 1, (long) statistics.size());
+        assertEquals("incorrect number of statistics", (long) 3, (long) statistics.size());
         assertEquals("incorrect statistic value", 42, statistics.get("meaningOfLife"));
     }
 
@@ -317,7 +319,9 @@ public class InjectedAttributeTest extends UnitTestBase
                                                                            TYPE_VALIDATOR,
                                                                            StatisticUnit.COUNT,
                                                                            StatisticType.POINT_IN_TIME,
-                                                                           "One");
+                                                                           "One",
+                                                                           null,
+                                                                           false);
         final ConfiguredObjectInjectedStatistic<?, ?> statInjector2 =
                 new ConfiguredObjectInjectedStatistic<TestCar<?>, Integer>("whatISent2",
                                                                            method,
@@ -325,13 +329,15 @@ public class InjectedAttributeTest extends UnitTestBase
                                                                            TYPE_VALIDATOR,
                                                                            StatisticUnit.COUNT,
                                                                            StatisticType.POINT_IN_TIME,
-                                                                           "Two");
+                                                                           "Two",
+                                                                           null,
+                                                                           false);
         TestModel model = new TestModel(null, new TestInjector(statInjector1, statInjector2));
 
         TestCar<?> testCar = new TestStandardCarImpl(Collections.<String,Object>singletonMap("name", "Arthur"), model);
 
         final Map<String, Object> statistics = testCar.getStatistics();
-        assertEquals("incorrect number of statistics", (long) 2, (long) statistics.size());
+        assertEquals("incorrect number of statistics", (long) 4, (long) statistics.size());
         assertEquals("incorrect statistic value", 1, statistics.get("whatISent1"));
         assertEquals("incorrect statistic value", 2, statistics.get("whatISent2"));
     }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractCarImpl.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractCarImpl.java
index 447c324..f01a6a4 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractCarImpl.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractCarImpl.java
@@ -22,6 +22,7 @@ package org.apache.qpid.server.model.testmodels.hierarchy;
 
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.qpid.server.configuration.IllegalConfigurationException;
 import org.apache.qpid.server.configuration.updater.CurrentThreadTaskExecutor;
@@ -37,6 +38,8 @@ public abstract class TestAbstractCarImpl<X extends TestAbstractCarImpl<X>> exte
     @ManagedAttributeField
     private Colour _interiorColour;
 
+    private AtomicInteger _mileage = new AtomicInteger();
+
     private volatile boolean _rejectStateChange;
 
     public TestAbstractCarImpl(final Map<String, Object> attributes)
@@ -102,4 +105,22 @@ public abstract class TestAbstractCarImpl<X extends TestAbstractCarImpl<X>> exte
     {
         _rejectStateChange = rejectStateChange;
     }
+
+    @Override
+    public int getMileage()
+    {
+        return _mileage.get();
+    }
+
+    @Override
+    public int move(final int value)
+    {
+        return _mileage.addAndGet(value);
+    }
+
+    @Override
+    public int getAge()
+    {
+        return 0;
+    }
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractEngineImpl.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractEngineImpl.java
index 722ddc8..5e84a12 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractEngineImpl.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractEngineImpl.java
@@ -34,6 +34,7 @@ import org.apache.qpid.server.model.StateTransition;
 
 public class TestAbstractEngineImpl<X extends TestAbstractEngineImpl<X>> extends AbstractConfiguredObject<X> implements TestEngine<X>
 {
+    public static final int TEST_TEMPERATURE = 50;
     @ManagedAttributeField
     private ListenableFuture<Void> _beforeCloseFuture = Futures.immediateFuture(null);
 
@@ -109,4 +110,10 @@ public class TestAbstractEngineImpl<X extends TestAbstractEngineImpl<X>> extends
         setState(State.ACTIVE);
         return (ListenableFuture<Void>) _stateChangeFuture;
     }
+
+    @Override
+    public int getTemperature()
+    {
+        return TEST_TEMPERATURE;
+    }
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractInstrumentPanelImpl.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractInstrumentPanelImpl.java
index 5ef7001..73db9d3 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractInstrumentPanelImpl.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractInstrumentPanelImpl.java
@@ -21,6 +21,7 @@
 package org.apache.qpid.server.model.testmodels.hierarchy;
 
 import java.util.Map;
+import java.util.Random;
 
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -51,4 +52,5 @@ public class TestAbstractInstrumentPanelImpl<X extends TestAbstractInstrumentPan
         setState(State.ACTIVE);
         return Futures.immediateFuture(null);
     }
+
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractSensorImpl.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractSensorImpl.java
index dc41c23..00ff611 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractSensorImpl.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestAbstractSensorImpl.java
@@ -21,6 +21,7 @@
 package org.apache.qpid.server.model.testmodels.hierarchy;
 
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
@@ -33,6 +34,8 @@ public class TestAbstractSensorImpl<X extends TestAbstractSensorImpl<X>> extends
         implements TestSensor<X>
 {
 
+    private AtomicInteger _alertCount;
+
     protected TestAbstractSensorImpl(final TestInstrumentPanel<?> parent,
                                      final Map<String, Object> attributes)
     {
@@ -51,4 +54,10 @@ public class TestAbstractSensorImpl<X extends TestAbstractSensorImpl<X>> extends
         setState(State.ACTIVE);
         return Futures.immediateFuture(null);
     }
+
+    @Override
+    public int getNumberOfAlerts()
+    {
+        return _alertCount.getAndIncrement();
+    }
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestCar.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestCar.java
index a184227..8588d60 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestCar.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestCar.java
@@ -25,7 +25,10 @@ import org.apache.qpid.server.model.ManagedAttribute;
 import org.apache.qpid.server.model.ManagedContextDefault;
 import org.apache.qpid.server.model.ManagedObject;
 import org.apache.qpid.server.model.ManagedOperation;
+import org.apache.qpid.server.model.ManagedStatistic;
 import org.apache.qpid.server.model.Param;
+import org.apache.qpid.server.model.StatisticType;
+import org.apache.qpid.server.model.StatisticUnit;
 
 @ManagedObject( defaultType = TestStandardCarImpl.TEST_STANDARD_CAR_TYPE)
 public interface TestCar<X extends TestCar<X>> extends ConfiguredObject<X>
@@ -60,4 +63,15 @@ public interface TestCar<X extends TestCar<X>> extends ConfiguredObject<X>
 
     void setRejectStateChange(boolean b);
 
+    @ManagedStatistic(statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT)
+    int getMileage();
+
+    @ManagedStatistic(metricName = "age",
+            statisticType = StatisticType.CUMULATIVE,
+            units = StatisticUnit.TIME_DURATION,
+            metricDisabled = true)
+    int getAge();
+
+    @ManagedOperation(changesConfiguredObjectState = false)
+    int move(@Param(name = "mileage", mandatory = true) int mileage);
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestEngine.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestEngine.java
index d5284cb..97229b2 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestEngine.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestEngine.java
@@ -25,6 +25,9 @@ import com.google.common.util.concurrent.ListenableFuture;
 import org.apache.qpid.server.model.ConfiguredObject;
 import org.apache.qpid.server.model.ManagedAttribute;
 import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.ManagedStatistic;
+import org.apache.qpid.server.model.StatisticType;
+import org.apache.qpid.server.model.StatisticUnit;
 
 @ManagedObject(category = true, defaultType = TestElecEngineImpl.TEST_ELEC_ENGINE_TYPE)
 public interface TestEngine<X extends TestEngine<X>> extends ConfiguredObject<X>
@@ -52,4 +55,7 @@ public interface TestEngine<X extends TestEngine<X>> extends ConfiguredObject<X>
     Object getStateChangeException();
     void setStateChangeException(RuntimeException exception);
 
+    @ManagedStatistic(statisticType = StatisticType.POINT_IN_TIME, units = StatisticUnit.COUNT)
+    int getTemperature();
+
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java
index 41e07f2..c2bbc79 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestSensor.java
@@ -22,9 +22,14 @@ package org.apache.qpid.server.model.testmodels.hierarchy;
 
 import org.apache.qpid.server.model.ConfiguredObject;
 import org.apache.qpid.server.model.ManagedObject;
+import org.apache.qpid.server.model.ManagedStatistic;
+import org.apache.qpid.server.model.StatisticType;
+import org.apache.qpid.server.model.StatisticUnit;
 
 @ManagedObject( defaultType = TestTemperatureSensorImpl.TEST_TEMPERATURE_SENSOR_TYPE)
 public interface TestSensor<X extends TestSensor<X>> extends ConfiguredObject<X>
 {
 
+    @ManagedStatistic(metricName = "alert_count", statisticType = StatisticType.CUMULATIVE, units = StatisticUnit.COUNT)
+    int getNumberOfAlerts();
 }
diff --git a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestTemperatureSensorImpl.java b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestTemperatureSensorImpl.java
index f2c176b..96f1fcf 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestTemperatureSensorImpl.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/model/testmodels/hierarchy/TestTemperatureSensorImpl.java
@@ -21,6 +21,7 @@
 package org.apache.qpid.server.model.testmodels.hierarchy;
 
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.qpid.server.model.ManagedObject;
 import org.apache.qpid.server.model.ManagedObjectFactoryConstructor;
@@ -32,9 +33,17 @@ public class TestTemperatureSensorImpl extends TestAbstractSensorImpl<TestTemper
 
     public static final String TEST_TEMPERATURE_SENSOR_TYPE = "temperature";
 
+    private AtomicInteger _alertCount = new AtomicInteger();
+
     @ManagedObjectFactoryConstructor
     protected TestTemperatureSensorImpl(final Map<String, Object> attributes,final TestInstrumentPanel<?> parent)
     {
         super(parent, attributes);
     }
+
+    @Override
+    public int getNumberOfAlerts()
+    {
+        return _alertCount.getAndIncrement();
+    }
 }
diff --git a/broker-plugins/management-http/pom.xml b/broker-plugins/management-http/pom.xml
index 9fa84d0..fa5a233 100644
--- a/broker-plugins/management-http/pom.xml
+++ b/broker-plugins/management-http/pom.xml
@@ -101,6 +101,11 @@
       </exclusions>
     </dependency>
 
+    <dependency>
+      <groupId>org.apache.qpid</groupId>
+      <artifactId>qpid-broker-plugins-prometheus-exporter</artifactId>
+    </dependency>
+
     <!-- test dependencies -->
     <dependency>
       <groupId>org.apache.qpid</groupId>
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
index 39709df..43da9db 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagement.java
@@ -90,6 +90,7 @@ import org.apache.qpid.server.management.plugin.filter.RewriteRequestForUncompre
 import org.apache.qpid.server.management.plugin.portunification.TlsOrPlainConnectionFactory;
 import org.apache.qpid.server.management.plugin.servlet.FileServlet;
 import org.apache.qpid.server.management.plugin.servlet.RootServlet;
+import org.apache.qpid.server.management.plugin.servlet.ContentServlet;
 import org.apache.qpid.server.management.plugin.servlet.rest.ApiDocsServlet;
 import org.apache.qpid.server.management.plugin.servlet.rest.BrokerQueryServlet;
 import org.apache.qpid.server.management.plugin.servlet.rest.JsonValueServlet;
@@ -119,6 +120,8 @@ import org.apache.qpid.server.model.TrustStore;
 import org.apache.qpid.server.model.adapter.AbstractPluginAdapter;
 import org.apache.qpid.server.model.port.HttpPort;
 import org.apache.qpid.server.model.port.PortManager;
+import org.apache.qpid.server.plugin.ContentFactory;
+import org.apache.qpid.server.plugin.QpidServiceLoader;
 import org.apache.qpid.server.transport.PortBindFailureException;
 import org.apache.qpid.server.transport.network.security.ssl.SSLUtil;
 import org.apache.qpid.server.util.DaemonThreadFactory;
@@ -412,6 +415,21 @@ public class HttpManagement extends AbstractPluginAdapter<HttpManagement> implem
 
         root.addServlet(new ServletHolder(new TimeZoneServlet()), "/service/timezones");
 
+        final Iterable<ContentFactory> contentFactories = new QpidServiceLoader().instancesOf(ContentFactory.class);
+        contentFactories.forEach(f->{
+            ServletHolder metricsServlet = new ServletHolder(new ContentServlet(f));
+            String path = f.getType().toLowerCase();
+            root.addServlet(metricsServlet, "/" + path);
+            root.addServlet(metricsServlet, "/" + path  + "/*");
+
+            if (getContextValue(Boolean.class, HTTP_MANAGEMENT_ENABLE_CONTENT_AUTHENTICATION))
+            {
+                root.addFilter(restAuthorizationFilter, "/" + path, EnumSet.of(DispatcherType.REQUEST));
+                root.addFilter(restAuthorizationFilter, "/" + path  + "/*", EnumSet.of(DispatcherType.REQUEST));
+            }
+
+        });
+
         root.getSessionHandler().getSessionCookieConfig().setName(JSESSIONID_COOKIE_PREFIX + lastPort);
         root.getSessionHandler().getSessionCookieConfig().setHttpOnly(true);
         root.getSessionHandler().setMaxInactiveInterval(getSessionTimeout());
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java
index a5aadae..d33f2b8 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/HttpManagementConfiguration.java
@@ -85,6 +85,11 @@ public interface HttpManagementConfiguration<X extends HttpManagementConfigurati
     @ManagedContextDefault( name = SASL_EXCHANGE_EXPIRY_CONTEXT_NAME)
     long DEFAULT_SASL_EXCHANGE_EXPIRY = 60000L;
 
+    String HTTP_MANAGEMENT_ENABLE_CONTENT_AUTHENTICATION = "qpid.httpManagement.enableMetricContentAuthentication";
+    @SuppressWarnings("unused")
+    @ManagedContextDefault(name = HTTP_MANAGEMENT_ENABLE_CONTENT_AUTHENTICATION)
+    boolean DEFAULT_HTTP_MANAGEMENT_ENABLE_CONTENT_AUTHENTICATION = false;
+
     AuthenticationProvider getAuthenticationProvider(HttpServletRequest request);
     Port<?> getPort(HttpServletRequest request);
 }
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/ContentServlet.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/ContentServlet.java
new file mode 100644
index 0000000..36ea0a7
--- /dev/null
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/ContentServlet.java
@@ -0,0 +1,95 @@
+/*
+ * 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.qpid.server.management.plugin.servlet;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.qpid.server.management.plugin.servlet.rest.AbstractServlet;
+import org.apache.qpid.server.model.Broker;
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectFinder;
+import org.apache.qpid.server.model.Content;
+import org.apache.qpid.server.model.VirtualHost;
+import org.apache.qpid.server.plugin.ContentFactory;
+
+public class ContentServlet extends AbstractServlet
+{
+    private static final long serialVersionUID = 1L;
+    private final ContentFactory _contentFactory;
+
+    public ContentServlet(final ContentFactory contentFactory)
+    {
+        super();
+        _contentFactory = contentFactory;
+    }
+
+    @Override
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response,
+                      final ConfiguredObject<?> managedObject) throws IOException
+    {
+
+        ConfiguredObject root = managedObject;
+        String pathInfo = request.getPathInfo();
+        if (managedObject instanceof Broker && null != pathInfo && !pathInfo.isEmpty())
+        {
+            final ConfiguredObjectFinder finder = getConfiguredObjectFinder(managedObject);
+            final ConfiguredObject virtualHost = finder.findObjectFromPath(pathInfo.substring(1), VirtualHost.class);
+            if (null == virtualHost)
+            {
+                sendError(response, HttpServletResponse.SC_NOT_FOUND);
+                return;
+            }
+            else
+            {
+                root = virtualHost;
+            }
+        }
+        else if (managedObject instanceof VirtualHost && null != pathInfo && !pathInfo.isEmpty())
+        {
+            sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        final Map<String, String[]> parameters = request.getParameterMap();
+        Content content = _contentFactory.createContent(root, parameters);
+        try
+        {
+            writeContent(content, request, response);
+        }
+        finally
+        {
+            content.release();
+        }
+    }
+
+    @Override
+    protected void doPost(final HttpServletRequest req,
+                          final HttpServletResponse resp,
+                          final ConfiguredObject<?> managedObject)
+            throws IOException
+    {
+        doGet(req, resp, managedObject);
+    }
+
+}
diff --git a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java
index 76d87f1..e324c7a 100644
--- a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java
+++ b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/servlet/rest/AbstractServlet.java
@@ -284,8 +284,22 @@ public abstract class AbstractServlet extends HttpServlet
     protected void writeTypedContent(Content content, HttpServletRequest request, HttpServletResponse response)
             throws IOException
     {
-        Map<String, Object> headers = getResponseHeaders(content);
+        try
+        {
+            writeContent(content, request, response);
+        }
+        catch (IOException e)
+        {
+            LOGGER.warn("Unexpected exception processing request", e);
+            sendJsonErrorResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
+        }
+    }
 
+    protected void writeContent(final Content content,
+                                final HttpServletRequest request,
+                                final HttpServletResponse response) throws IOException
+    {
+        Map<String, Object> headers = new HashMap<>(getResponseHeaders(content));
         try (OutputStream os = getOutputStream(request, response, headers))
         {
             response.setStatus(HttpServletResponse.SC_OK);
@@ -295,11 +309,6 @@ public abstract class AbstractServlet extends HttpServlet
             }
             content.write(os);
         }
-        catch (IOException e)
-        {
-            LOGGER.warn("Unexpected exception processing request", e);
-            sendJsonErrorResponse(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
-        }
     }
 
     private OutputStream getOutputStream(final HttpServletRequest request,
diff --git a/broker-plugins/prometheus-exporter/pom.xml b/broker-plugins/prometheus-exporter/pom.xml
new file mode 100644
index 0000000..9f9885f
--- /dev/null
+++ b/broker-plugins/prometheus-exporter/pom.xml
@@ -0,0 +1,70 @@
+<?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">
+    <parent>
+        <artifactId>qpid-broker-parent</artifactId>
+        <groupId>org.apache.qpid</groupId>
+        <version>7.1.9-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>qpid-broker-plugins-prometheus-exporter</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>qpid-broker-codegen</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>qpid-broker-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.prometheus</groupId>
+            <artifactId>simpleclient</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.prometheus</groupId>
+            <artifactId>simpleclient_common</artifactId>
+        </dependency>
+
+        <!-- test dependencies -->
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>qpid-test-utils</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.qpid</groupId>
+            <artifactId>qpid-broker-core</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/IncludeDisabledStatisticPredicate.java
similarity index 59%
copy from broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
copy to broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/IncludeDisabledStatisticPredicate.java
index cc22d62..f675e55 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
+++ b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/IncludeDisabledStatisticPredicate.java
@@ -1,5 +1,4 @@
 /*
- *
  * 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
@@ -18,15 +17,26 @@
  * under the License.
  *
  */
-package org.apache.qpid.server.model;
 
-public interface ConfiguredObjectStatistic<C extends ConfiguredObject, T extends Object> extends ConfiguredObjectAttributeOrStatistic<C,T>
-{
-    String getDescription();
+package org.apache.qpid.server.prometheus;
+
+import java.util.function.Predicate;
 
-    StatisticUnit getUnits();
+import org.apache.qpid.server.model.ConfiguredObjectStatistic;
+
+public class IncludeDisabledStatisticPredicate implements Predicate<ConfiguredObjectStatistic<?,?>>
+
+{
+    private final boolean _includeDisabled;
 
-    StatisticType getStatisticType();
+    IncludeDisabledStatisticPredicate(final boolean includeDisabled)
+    {
+        _includeDisabled = includeDisabled;
+    }
 
-    String getLabel();
+    @Override
+    public boolean test(final ConfiguredObjectStatistic s)
+    {
+        return _includeDisabled || !s.isMetricDisabled();
+    }
 }
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/IncludeMetricPredicate.java
similarity index 60%
copy from broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
copy to broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/IncludeMetricPredicate.java
index cc22d62..e921352 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
+++ b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/IncludeMetricPredicate.java
@@ -1,5 +1,4 @@
 /*
- *
  * 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
@@ -18,15 +17,26 @@
  * under the License.
  *
  */
-package org.apache.qpid.server.model;
 
-public interface ConfiguredObjectStatistic<C extends ConfiguredObject, T extends Object> extends ConfiguredObjectAttributeOrStatistic<C,T>
-{
-    String getDescription();
+package org.apache.qpid.server.prometheus;
 
-    StatisticUnit getUnits();
+import java.util.Set;
+import java.util.function.Predicate;
+
+public class IncludeMetricPredicate implements Predicate<String>
+{
+    private final Set<String> _allowedNames;
+    private final boolean _isEmpty;
 
-    StatisticType getStatisticType();
+    public IncludeMetricPredicate(final Set<String> allowedNames)
+    {
+        _allowedNames = allowedNames;
+        _isEmpty = _allowedNames.isEmpty();
+    }
 
-    String getLabel();
+    @Override
+    public boolean test(final String name)
+    {
+        return _isEmpty || _allowedNames.contains(name);
+    }
 }
diff --git a/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/PrometheusContentFactory.java b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/PrometheusContentFactory.java
new file mode 100644
index 0000000..af98978
--- /dev/null
+++ b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/PrometheusContentFactory.java
@@ -0,0 +1,105 @@
+/*
+ * 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.qpid.server.prometheus;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+
+import io.prometheus.client.exporter.common.TextFormat;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.Content;
+import org.apache.qpid.server.model.RestContentHeader;
+import org.apache.qpid.server.plugin.ContentFactory;
+import org.apache.qpid.server.plugin.PluggableService;
+
+@PluggableService
+public class PrometheusContentFactory implements ContentFactory
+{
+    static final String INCLUDE_DISABLED = "includeDisabled";
+    static final String INCLUDE_METRIC = "name[]";
+    static final String INCLUDE_DISABLED_CONTEXT_VARIABLE = "qpid.metrics.includeDisabled";
+
+    @Override
+    public Content createContent(final ConfiguredObject<?> object, final Map<String, String[]> filter)
+    {
+        final String[] includeDisabledValues = filter.get(INCLUDE_DISABLED);
+        boolean includeDisabled = includeDisabledValues!= null && includeDisabledValues.length == 1 && Boolean.parseBoolean(includeDisabledValues[0]);
+        if (!includeDisabled)
+        {
+            Boolean val = object.getContextValue(Boolean.class, INCLUDE_DISABLED_CONTEXT_VARIABLE);
+            if (val != null)
+            {
+                includeDisabled = val;
+            }
+        }
+
+        final String[] includedMetricNames = filter.get(INCLUDE_METRIC);
+
+        final IncludeMetricPredicate metricIncludeFilter =
+                new IncludeMetricPredicate(includedMetricNames == null || includedMetricNames.length == 0
+                                                   ? Collections.emptySet()
+                                                   : new HashSet<>(Arrays.asList(includedMetricNames)));
+        final QpidCollector qpidCollector = new QpidCollector(object,
+                                                              new IncludeDisabledStatisticPredicate(includeDisabled),
+                                                              metricIncludeFilter);
+
+        return new Content()
+        {
+            @Override
+            public void write(final OutputStream outputStream) throws IOException
+            {
+                try (final Writer writer = new OutputStreamWriter(outputStream))
+                {
+                    TextFormat.write004(writer, Collections.enumeration(qpidCollector.collect()));
+                    writer.flush();
+                }
+            }
+
+            @Override
+            public void release()
+            {
+
+            }
+
+            @SuppressWarnings("unused")
+            @RestContentHeader("Content-Type")
+            public String getContentType()
+            {
+                return TextFormat.CONTENT_TYPE_004;
+            }
+
+        };
+
+    }
+
+    @Override
+    public String getType()
+    {
+        return "metrics";
+    }
+}
diff --git a/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/QpidCollector.java b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/QpidCollector.java
new file mode 100644
index 0000000..3997700
--- /dev/null
+++ b/broker-plugins/prometheus-exporter/src/main/java/org/apache/qpid/server/prometheus/QpidCollector.java
@@ -0,0 +1,257 @@
+/*
+ * 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.qpid.server.prometheus;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+import io.prometheus.client.Collector;
+import io.prometheus.client.CounterMetricFamily;
+import io.prometheus.client.GaugeMetricFamily;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectStatistic;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.model.StatisticType;
+import org.apache.qpid.server.model.StatisticUnit;
+
+public class QpidCollector extends Collector
+{
+    private final static MetricFamilySamples IGNORED = new MetricFamilySamples(null, null, null, null);
+    static final String COUNT_SUFFIX = "count";
+    static final String TOTAL_SUFFIX = "total";
+    private final Predicate<ConfiguredObjectStatistic<?,?>> _includeStatisticFilter;
+    private final Predicate<String> _includeMetricFilter;
+    private ConfiguredObject<?> _root;
+    private Model _model;
+
+
+    QpidCollector(final ConfiguredObject<?> root,
+                  final Predicate<ConfiguredObjectStatistic<?,?>> includeStatisticFilter,
+                  final Predicate<String> includeMetricFilter)
+    {
+        _root = root;
+        _model = _root.getModel();
+        _includeStatisticFilter = includeStatisticFilter;
+        _includeMetricFilter = includeMetricFilter;
+    }
+
+    @Override
+    public List<MetricFamilySamples> collect()
+    {
+        final List<MetricFamilySamples> metricFamilySamples = new ArrayList<>();
+        addObjectMetrics(_root, Collections.emptyList(), new HashMap<>(), metricFamilySamples);
+        addChildrenMetrics(metricFamilySamples, _root, Collections.singletonList("name"));
+        return metricFamilySamples;
+    }
+
+    private void addObjectMetrics(final ConfiguredObject<?> object,
+                                  final List<String> labelNames,
+                                  final Map<String, MetricFamilySamples> metricFamilyMap,
+                                  final List<MetricFamilySamples> metricFamilySamples)
+    {
+        final Map<String, Object> statsMap = object.getStatistics();
+        for (final Map.Entry<String, Object> entry : statsMap.entrySet())
+        {
+            MetricFamilySamples family = metricFamilyMap.get(entry.getKey());
+            if (family == null)
+            {
+                family = createMetricFamilySamples(entry.getKey(), object, labelNames);
+                metricFamilyMap.put(entry.getKey(), family);
+                if (family != IGNORED)
+                {
+                    metricFamilySamples.add(family);
+                }
+            }
+            if (family != IGNORED)
+            {
+                final List<String> labelsValues = buildLabelValues(object);
+                final double doubleValue = toDoubleValue(entry.getValue());
+                family.samples.add(new MetricFamilySamples.Sample(family.name, labelNames, labelsValues, doubleValue));
+            }
+        }
+    }
+
+    private MetricFamilySamples createMetricFamilySamples(final String statisticName,
+                                                          final ConfiguredObject<?> object,
+                                                          final List<String> labelNames)
+    {
+        final ConfiguredObjectStatistic<?, ?> configuredObjectStatistic =
+                findConfiguredObjectStatistic(statisticName, object.getTypeClass());
+        if (configuredObjectStatistic == null || !_includeStatisticFilter.test(configuredObjectStatistic))
+        {
+            return IGNORED;
+        }
+        final StatisticType type = configuredObjectStatistic.getStatisticType();
+        final String familyName = getFamilyName(object.getCategoryClass(), configuredObjectStatistic);
+
+        if (!_includeMetricFilter.test(familyName))
+        {
+            return IGNORED;
+        }
+
+        if (type == StatisticType.CUMULATIVE)
+        {
+            return new CounterMetricFamily(familyName, configuredObjectStatistic.getDescription(), labelNames);
+        }
+        else
+        {
+            return new GaugeMetricFamily(familyName, configuredObjectStatistic.getDescription(), labelNames);
+        }
+    }
+
+    private ConfiguredObjectStatistic<?, ?> findConfiguredObjectStatistic(final String statisticName,
+                                                                          final Class<? extends ConfiguredObject> typeClass)
+    {
+        final Collection<ConfiguredObjectStatistic<?, ?>> statisticsDefinitions =
+                _model.getTypeRegistry().getStatistics(typeClass);
+        return statisticsDefinitions.stream()
+                                    .filter(s -> statisticName.equals(s.getName()))
+                                    .findFirst()
+                                    .orElse(null);
+    }
+
+    private List<String> buildLabelValues(final ConfiguredObject<?> object)
+    {
+        final List<String> labelsValues = new ArrayList<>();
+        ConfiguredObject o = object;
+        while (o != null && o != _root)
+        {
+            labelsValues.add(o.getName());
+            o = o.getParent();
+        }
+        return labelsValues;
+    }
+
+    private void addChildrenMetrics(final List<MetricFamilySamples> metricFamilySamples,
+                                    final ConfiguredObject<?> object,
+                                    final List<String> childLabelNames)
+    {
+        final Class<? extends ConfiguredObject> category = object.getCategoryClass();
+        for (final Class<? extends ConfiguredObject> childClass : _model.getChildTypes(category))
+        {
+            final Collection<? extends ConfiguredObject> children = object.getChildren(childClass);
+            if (children != null && !children.isEmpty())
+            {
+                final Map<String, MetricFamilySamples> childrenMetricFamilyMap = new HashMap<>();
+                for (final ConfiguredObject<?> child : children)
+                {
+                    addObjectMetrics(child, childLabelNames, childrenMetricFamilyMap, metricFamilySamples);
+                    final List<String> labelNames = new ArrayList<>(childLabelNames);
+                    final String label = String.format("%s_name", toSnakeCase(childClass.getSimpleName()));
+                    labelNames.add(label);
+                    addChildrenMetrics(metricFamilySamples, child, labelNames);
+                }
+            }
+        }
+    }
+
+    static String toSnakeCase(final String simpleName)
+    {
+        final StringBuilder sb = new StringBuilder();
+        final char[] chars = simpleName.toCharArray();
+        for (int i = 0; i < chars.length; i++)
+        {
+            final char ch = chars[i];
+            if (Character.isUpperCase(ch))
+            {
+                if (i > 0)
+                {
+                    sb.append('_');
+                }
+                sb.append(Character.toLowerCase(ch));
+            }
+            else
+            {
+                sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
+    private double toDoubleValue(final Object value)
+    {
+        if (value instanceof Number)
+        {
+            return ((Number) value).doubleValue();
+        }
+        return 0;
+    }
+
+    static String getFamilyName(final Class<? extends ConfiguredObject> categoryClass,
+                                ConfiguredObjectStatistic<?, ?> statistics)
+    {
+        String metricName = statistics.getMetricName();
+        if (metricName == null || metricName.isEmpty())
+        {
+            metricName = generateMetricName(statistics);
+        }
+
+        return String.format("qpid_%s_%s",
+                             toSnakeCase(categoryClass.getSimpleName()),
+                             metricName);
+    }
+
+    private static String generateMetricName(final ConfiguredObjectStatistic<?, ?> statistics)
+    {
+        String metricName = toSnakeCase(statistics.getName());
+        String suffix;
+        switch (statistics.getStatisticType())
+        {
+            case CUMULATIVE:
+                suffix = generateMetricSuffix(statistics, COUNT_SUFFIX, metricName);
+                break;
+            case POINT_IN_TIME:
+                suffix = generateMetricSuffix(statistics, TOTAL_SUFFIX, metricName);
+                break;
+            default:
+                suffix = "";
+        }
+
+        return metricName + suffix;
+    }
+
+    private static String generateMetricSuffix(final ConfiguredObjectStatistic<?, ?> statistics,
+                                               final String typeSuffix,
+                                               final String metricName)
+    {
+        String suffix = "";
+        if (!statistics.getName().toLowerCase().contains(typeSuffix)
+            && statistics.getUnits() != StatisticUnit.ABSOLUTE_TIME
+            && statistics.getUnits() != StatisticUnit.TIME_DURATION)
+        {
+            if (statistics.getUnits() == StatisticUnit.MESSAGES || statistics.getUnits() == StatisticUnit.BYTES)
+            {
+                final String units = statistics.getUnits().toString() + "s";
+                if (!metricName.contains(units))
+                {
+                    suffix = "_" + units;
+                }
+            }
+            suffix = suffix + "_" + typeSuffix;
+        }
+        return suffix;
+    }
+}
diff --git a/broker-plugins/prometheus-exporter/src/test/java/org/apache/qpid/server/prometheus/PrometheusContentFactoryTest.java b/broker-plugins/prometheus-exporter/src/test/java/org/apache/qpid/server/prometheus/PrometheusContentFactoryTest.java
new file mode 100644
index 0000000..7959a77
--- /dev/null
+++ b/broker-plugins/prometheus-exporter/src/test/java/org/apache/qpid/server/prometheus/PrometheusContentFactoryTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.qpid.server.prometheus;
+
+import static org.apache.qpid.server.prometheus.PrometheusContentFactory.INCLUDE_DISABLED;
+import static org.apache.qpid.server.prometheus.PrometheusContentFactory.INCLUDE_METRIC;
+import static org.apache.qpid.server.prometheus.PrometheusContentFactory.INCLUDE_DISABLED_CONTEXT_VARIABLE;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.Content;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestCar;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestKitCarImpl;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestModel;
+
+public class PrometheusContentFactoryTest
+{
+    public static final String QPID_TEST_CAR_MILEAGE_COUNT = "qpid_test_car_mileage_count";
+    public static final String QPID_TEST_CAR_AGE = "qpid_test_car_age";
+    public static final String PROMETHEUS_COMMENT = "#";
+    private ConfiguredObject _root;
+    private PrometheusContentFactory _prometheusContentFactory;
+
+    @Before
+    public void setUp()
+    {
+        final Model model = TestModel.getInstance();
+        final Map<String, Object> carAttributes = new HashMap<>();
+        carAttributes.put(ConfiguredObject.NAME, "MyPrometheusCar");
+        carAttributes.put(ConfiguredObject.TYPE, TestKitCarImpl.TEST_KITCAR_TYPE);
+
+        @SuppressWarnings("unchecked") final TestCar<?> car =
+                model.getObjectFactory().create(TestCar.class, carAttributes, null);
+        _root = car;
+        _prometheusContentFactory = new PrometheusContentFactory();
+    }
+
+    @Test
+    public void testCreateContent() throws Exception
+    {
+        final Content content = _prometheusContentFactory.createContent(_root, Collections.emptyMap());
+        assertThat(content, is(notNullValue()));
+        Collection<String> metrics;
+        try (final ByteArrayOutputStream output = new ByteArrayOutputStream())
+        {
+            content.write(output);
+            metrics = getMetricLines(output.toByteArray());
+        }
+        assertThat(metrics, is(notNullValue()));
+
+        assertThat(metrics.size(), is(equalTo(1)));
+        String metric = metrics.iterator().next();
+        assertThat(metric, startsWith(QPID_TEST_CAR_MILEAGE_COUNT));
+    }
+
+    @Test
+    public void testCreateContentIncludeDisabled() throws Exception
+    {
+        final Content content = _prometheusContentFactory.createContent(_root, Collections.singletonMap(INCLUDE_DISABLED, new String[]{"true"}));
+        assertThat(content, is(notNullValue()));
+        Collection<String> metrics;
+        try (final ByteArrayOutputStream output = new ByteArrayOutputStream())
+        {
+            content.write(output);
+            metrics = getMetricLines(output.toByteArray());
+        }
+        assertThat(metrics, is(notNullValue()));
+
+        assertThat(metrics.size(), is(equalTo(2)));
+        Map<String, String> metricsMap = convertMetricsToMap(metrics);
+        assertThat(metricsMap.containsKey(QPID_TEST_CAR_MILEAGE_COUNT), equalTo(Boolean.TRUE));
+        assertThat(metricsMap.containsKey(QPID_TEST_CAR_AGE), equalTo(Boolean.TRUE));
+    }
+
+    @Test
+    public void testCreateContentIncludeDisabledUsingContextVariable() throws Exception
+    {
+        _root.setContextVariable(INCLUDE_DISABLED_CONTEXT_VARIABLE, "true");
+        final Content content = _prometheusContentFactory.createContent(_root, Collections.emptyMap());
+        assertThat(content, is(notNullValue()));
+        Collection<String> metrics;
+        try (final ByteArrayOutputStream output = new ByteArrayOutputStream())
+        {
+            content.write(output);
+            metrics = getMetricLines(output.toByteArray());
+        }
+        assertThat(metrics, is(notNullValue()));
+        assertThat(metrics.size(), is(equalTo(2)));
+        Map<String, String> metricsMap = convertMetricsToMap(metrics);
+        assertThat(metricsMap.containsKey(QPID_TEST_CAR_MILEAGE_COUNT), equalTo(Boolean.TRUE));
+        assertThat(metricsMap.containsKey(QPID_TEST_CAR_AGE), equalTo(Boolean.TRUE));
+    }
+
+    @Test
+    public void testCreateContentIncludeName() throws Exception
+    {
+        final Map<String, String[]> filter = new HashMap<>();
+        filter.put(INCLUDE_DISABLED, new String[]{"true"});
+        filter.put(INCLUDE_METRIC, new String[]{QPID_TEST_CAR_AGE});
+        final Content content = _prometheusContentFactory.createContent(_root, filter);
+        assertThat(content, is(notNullValue()));
+        Collection<String> metrics;
+        try (final ByteArrayOutputStream output = new ByteArrayOutputStream())
+        {
+            content.write(output);
+            metrics = getMetricLines(output.toByteArray());
+        }
+        assertThat(metrics, is(notNullValue()));
+
+        assertThat(metrics.size(), is(equalTo(1)));
+        String metric = metrics.iterator().next();
+        assertThat(metric, startsWith(QPID_TEST_CAR_AGE));
+    }
+
+    private static Collection<String> getMetricLines(final byte[] metricsBytes) throws IOException
+    {
+        final List<String> results = new ArrayList<>();
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(metricsBytes))))
+        {
+            String line;
+            while ((line = reader.readLine()) != null)
+            {
+                if (!(line.startsWith(PROMETHEUS_COMMENT) || line.isEmpty()))
+                {
+                    results.add(line);
+                }
+            }
+        }
+        return results;
+    }
+
+    private Map<String, String> convertMetricsToMap(final Collection<String> metrics)
+    {
+        return metrics.stream().map(m -> m.split(" ")).collect(Collectors.toMap(m -> m[0], m -> m[1]));
+    }
+
+}
diff --git a/broker-plugins/prometheus-exporter/src/test/java/org/apache/qpid/server/prometheus/QpidCollectorTest.java b/broker-plugins/prometheus-exporter/src/test/java/org/apache/qpid/server/prometheus/QpidCollectorTest.java
new file mode 100644
index 0000000..5efbc59
--- /dev/null
+++ b/broker-plugins/prometheus-exporter/src/test/java/org/apache/qpid/server/prometheus/QpidCollectorTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.qpid.server.prometheus;
+
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.closeTo;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import io.prometheus.client.Collector;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.qpid.server.model.ConfiguredObject;
+import org.apache.qpid.server.model.ConfiguredObjectStatistic;
+import org.apache.qpid.server.model.Model;
+import org.apache.qpid.server.model.StatisticType;
+import org.apache.qpid.server.model.StatisticUnit;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestAbstractEngineImpl;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestCar;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestDigitalInstrumentPanelImpl;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestElecEngineImpl;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestEngine;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestInstrumentPanel;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestKitCarImpl;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestModel;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestPetrolEngineImpl;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestSensor;
+import org.apache.qpid.server.model.testmodels.hierarchy.TestTemperatureSensorImpl;
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class QpidCollectorTest extends UnitTestBase
+{
+    private static final String CAR_NAME = "myCar";
+    private static final String ELECTRIC_ENGINE_NAME = "myEngine";
+    private static final String INSTRUMENT_PANEL_NAME = "instrumentPanel";
+    private static final String PETROL_ENGINE_NAME = "myPetrolModel";
+    private static final String SENSOR = "sensor";
+    private static final int DESIRED_MILEAGE = 100;
+    private static final String QPID_TEST_CAR_MILEAGE_COUNT = "qpid_test_car_mileage_count";
+    private static final String QPID_TEST_ENGINE_TEMPERATURE_TOTAL = "qpid_test_engine_temperature_total";
+    private static final String QPID_TEST_SENSOR_ALERT_COUNT = "qpid_test_sensor_alert_count";
+    private static final String QPID_TEST_CAR_AGE_COUNT = "qpid_test_car_age";
+
+    private TestCar<?> _root;
+    private QpidCollector _qpidCollector;
+    private static final StatisticUnit[] UNITS = new StatisticUnit[]{
+            StatisticUnit.BYTES,
+            StatisticUnit.MESSAGES,
+            StatisticUnit.COUNT,
+            StatisticUnit.ABSOLUTE_TIME,
+            StatisticUnit.TIME_DURATION};
+    private static final String[] UNIT_SUFFIXES = new String[]{"_bytes", "_messages", "", "", ""};
+
+    @Before
+    public void setUp()
+    {
+        final Model model = TestModel.getInstance();
+        final Map<String, Object> carAttributes = new HashMap<>();
+        carAttributes.put(ConfiguredObject.NAME, CAR_NAME);
+        carAttributes.put(ConfiguredObject.TYPE, TestKitCarImpl.TEST_KITCAR_TYPE);
+
+        @SuppressWarnings("unchecked") final TestCar<?> car =
+                model.getObjectFactory().create(TestCar.class, carAttributes, null);
+        _root = car;
+        _qpidCollector = new QpidCollector(_root, new IncludeDisabledStatisticPredicate(false), s->true);
+    }
+
+    @Test
+    public void testCollectForHierarchyOfTwoObjects()
+    {
+        createTestEngine(ELECTRIC_ENGINE_NAME, TestElecEngineImpl.TEST_ELEC_ENGINE_TYPE);
+        _root.move(DESIRED_MILEAGE);
+
+        final List<Collector.MetricFamilySamples> metrics = _qpidCollector.collect();
+
+        final String[] expectedFamilyNames = {QPID_TEST_CAR_MILEAGE_COUNT, QPID_TEST_ENGINE_TEMPERATURE_TOTAL};
+        final Map<String, Collector.MetricFamilySamples> metricsMap =
+                convertMetricFamilySamplesIntoMapAndAssert(metrics, expectedFamilyNames);
+
+        final Collector.MetricFamilySamples carMetricFamilySamples = metricsMap.get(QPID_TEST_CAR_MILEAGE_COUNT);
+        assertMetricFamilySamplesSize(carMetricFamilySamples, 1);
+        final Collector.MetricFamilySamples.Sample carSample = carMetricFamilySamples.samples.get(0);
+        assertThat(carSample.value, closeTo(DESIRED_MILEAGE, 0.01));
+        assertThat(carSample.labelNames.size(), is(equalTo(0)));
+
+        final Collector.MetricFamilySamples engineMetricFamilySamples = metricsMap.get(
+                QPID_TEST_ENGINE_TEMPERATURE_TOTAL);
+        assertMetricFamilySamplesSize(engineMetricFamilySamples, 1);
+        final Collector.MetricFamilySamples.Sample engineSample = engineMetricFamilySamples.samples.get(0);
+        assertThat(engineSample.labelNames, is(equalTo(Collections.singletonList("name"))));
+        assertThat(engineSample.labelValues, is(equalTo(Collections.singletonList(ELECTRIC_ENGINE_NAME))));
+        assertThat(engineSample.value, Matchers.closeTo(TestAbstractEngineImpl.TEST_TEMPERATURE, 0.01));
+    }
+
+    @Test
+    public void testCollectForHierarchyOfThreeObjects()
+    {
+        final TestInstrumentPanel instrumentPanel = getTestInstrumentPanel();
+        createTestSensor(instrumentPanel);
+
+        final List<Collector.MetricFamilySamples> metrics = _qpidCollector.collect();
+
+        final String[] expectedFamilyNames =
+                {QPID_TEST_CAR_MILEAGE_COUNT, QPID_TEST_SENSOR_ALERT_COUNT};
+        final Map<String, Collector.MetricFamilySamples> metricsMap =
+                convertMetricFamilySamplesIntoMapAndAssert(metrics, expectedFamilyNames);
+
+        final Collector.MetricFamilySamples carMetricFamilySamples = metricsMap.get(QPID_TEST_CAR_MILEAGE_COUNT);
+        assertMetricFamilySamplesSize(carMetricFamilySamples, 1);
+        final Collector.MetricFamilySamples.Sample carSample = carMetricFamilySamples.samples.get(0);
+        assertThat(carSample.labelNames.size(), is(equalTo(0)));
+        assertThat(carSample.labelValues.size(), is(equalTo(0)));
+        assertThat(carSample.value, closeTo(0, 0.01));
+
+        final Collector.MetricFamilySamples sensorlMetricFamilySamples =
+                metricsMap.get(QPID_TEST_SENSOR_ALERT_COUNT);
+        assertMetricFamilySamplesSize(sensorlMetricFamilySamples, 1);
+        final Collector.MetricFamilySamples.Sample sensorSample = sensorlMetricFamilySamples.samples.get(0);
+        assertThat(sensorSample.labelNames, is(equalTo(Arrays.asList("name", "test_instrument_panel_name"))));
+        assertThat(sensorSample.labelValues, is(equalTo(Arrays.asList(SENSOR, INSTRUMENT_PANEL_NAME))));
+    }
+
+    @Test
+    public void testCollectForSiblingObjects()
+    {
+        createTestEngine(ELECTRIC_ENGINE_NAME, TestElecEngineImpl.TEST_ELEC_ENGINE_TYPE);
+        createTestEngine(PETROL_ENGINE_NAME, TestPetrolEngineImpl.TEST_PETROL_ENGINE_TYPE);
+
+        final List<Collector.MetricFamilySamples> metrics = _qpidCollector.collect();
+
+        final String[] expectedFamilyNames = {QPID_TEST_CAR_MILEAGE_COUNT, QPID_TEST_ENGINE_TEMPERATURE_TOTAL};
+        final Map<String, Collector.MetricFamilySamples> metricsMap =
+                convertMetricFamilySamplesIntoMapAndAssert(metrics, expectedFamilyNames);
+
+        final Collector.MetricFamilySamples carMetricFamilySamples = metricsMap.get(QPID_TEST_CAR_MILEAGE_COUNT);
+        assertMetricFamilySamplesSize(carMetricFamilySamples, 1);
+        final Collector.MetricFamilySamples.Sample carSample = carMetricFamilySamples.samples.get(0);
+        assertThat(carSample.labelNames.size(), is(equalTo(0)));
+        assertThat(carSample.labelValues.size(), is(equalTo(0)));
+
+        final Collector.MetricFamilySamples engineMetricFamilySamples = metricsMap.get(
+                QPID_TEST_ENGINE_TEMPERATURE_TOTAL);
+        assertMetricFamilySamplesSize(engineMetricFamilySamples, 2);
+        final String[] engineNames = {PETROL_ENGINE_NAME, ELECTRIC_ENGINE_NAME};
+        for (String engineName : engineNames)
+        {
+            final Collector.MetricFamilySamples.Sample sample =
+                    findSampleByLabelValue(engineMetricFamilySamples, engineName);
+            assertThat(sample.labelNames, is(equalTo(Collections.singletonList("name"))));
+            assertThat(sample.labelValues, is(equalTo(Collections.singletonList(engineName))));
+            assertThat(sample.value, Matchers.closeTo(TestAbstractEngineImpl.TEST_TEMPERATURE, 0.01));
+        }
+    }
+
+    @Test
+    public void testCollectWithFilter(){
+        createTestEngine(ELECTRIC_ENGINE_NAME, TestElecEngineImpl.TEST_ELEC_ENGINE_TYPE);
+        _root.move(DESIRED_MILEAGE);
+
+        _qpidCollector = new QpidCollector(_root,
+                                           new IncludeDisabledStatisticPredicate(true),
+                                           new IncludeMetricPredicate(Collections.singleton(QPID_TEST_CAR_AGE_COUNT)));
+        final List<Collector.MetricFamilySamples> metrics = _qpidCollector.collect();
+
+        final String[] expectedFamilyNames = {QPID_TEST_CAR_AGE_COUNT};
+        final Map<String, Collector.MetricFamilySamples> metricsMap =
+                convertMetricFamilySamplesIntoMapAndAssert(metrics, expectedFamilyNames);
+
+
+        final Collector.MetricFamilySamples engineMetricFamilySamples = metricsMap.get(QPID_TEST_CAR_AGE_COUNT);
+        assertMetricFamilySamplesSize(engineMetricFamilySamples, 1);
+        final Collector.MetricFamilySamples.Sample engineSample = engineMetricFamilySamples.samples.get(0);
+        assertThat(engineSample.labelNames, is(equalTo(Collections.emptyList())));
+        assertThat(engineSample.labelValues, is(equalTo(Collections.emptyList())));
+        assertThat(engineSample.value, Matchers.closeTo(0.0, 0.01));
+
+    }
+
+
+    private Collector.MetricFamilySamples.Sample findSampleByLabelValue(final Collector.MetricFamilySamples metricFamilySamples,
+                                                                        final String nameLabelValue)
+    {
+        final List<Collector.MetricFamilySamples.Sample> found = metricFamilySamples.samples
+                .stream()
+                .filter(s -> s.labelValues != null
+                             && s.labelValues.size() > 0
+                             && nameLabelValue.equals(s.labelValues.get(0)))
+                .collect(Collectors.toList());
+        assertThat(found.size(), is(equalTo(1)));
+        return found.get(0);
+    }
+
+    private void createTestEngine(final String engineName, final String engineType)
+    {
+        final Map<String, Object> engineAttributes = new HashMap<>();
+        engineAttributes.put(ConfiguredObject.NAME, engineName);
+        engineAttributes.put(ConfiguredObject.TYPE, engineType);
+        _root.createChild(TestEngine.class, engineAttributes);
+    }
+
+    private void createTestSensor(final TestInstrumentPanel instrumentPanel)
+    {
+        final Map<String, Object> sensorAttributes = new HashMap<>();
+        sensorAttributes.put(ConfiguredObject.NAME, SENSOR);
+        sensorAttributes.put(ConfiguredObject.TYPE, TestTemperatureSensorImpl.TEST_TEMPERATURE_SENSOR_TYPE);
+        instrumentPanel.createChild(TestSensor.class, sensorAttributes);
+    }
+
+    private TestInstrumentPanel getTestInstrumentPanel()
+    {
+        final Map<String, Object> instrumentPanelAttributes = new HashMap<>();
+        instrumentPanelAttributes.put(ConfiguredObject.NAME, INSTRUMENT_PANEL_NAME);
+        instrumentPanelAttributes.put(ConfiguredObject.TYPE,
+                                      TestDigitalInstrumentPanelImpl.TEST_DIGITAL_INSTRUMENT_PANEL_TYPE);
+        return _root.createChild(TestInstrumentPanel.class, instrumentPanelAttributes);
+    }
+
+    private Map<String, Collector.MetricFamilySamples> convertMetricFamilySamplesIntoMap(List<Collector.MetricFamilySamples> metricFamilySamples)
+    {
+        Map<String, Collector.MetricFamilySamples> result = new HashMap<>();
+        for (Collector.MetricFamilySamples metricFamilySample : metricFamilySamples)
+        {
+            final String name = metricFamilySample.name;
+
+            if (result.put(name, metricFamilySample) != null)
+            {
+                fail(String.format("Duplicate family name : %s", name));
+            }
+        }
+        return result;
+    }
+
+    private void assertMetricFamilySamples(final Collector.MetricFamilySamples metricFamilySamples)
+    {
+        assertThat(metricFamilySamples, is(notNullValue()));
+        assertThat(metricFamilySamples.samples, is(notNullValue()));
+
+        for (final Collector.MetricFamilySamples.Sample sample : metricFamilySamples.samples)
+        {
+            assertThat(sample, is(notNullValue()));
+            assertThat(sample.name, is(equalTo(metricFamilySamples.name)));
+        }
+    }
+
+
+    private void assertMetricFamilySamplesSize(final Collector.MetricFamilySamples metricFamilySamples,
+                                               final int expectedSamplesSize)
+    {
+        assertThat(metricFamilySamples.samples.size(), equalTo(expectedSamplesSize));
+    }
+
+    private Map<String, Collector.MetricFamilySamples> convertMetricFamilySamplesIntoMapAndAssert(final List<Collector.MetricFamilySamples> metrics,
+                                                                                                  final String[] expectedFamilyNames)
+    {
+        assertThat(metrics.size(), equalTo(expectedFamilyNames.length));
+        final Map<String, Collector.MetricFamilySamples> metricsMap = convertMetricFamilySamplesIntoMap(metrics);
+
+        for (String expectedFamily : expectedFamilyNames)
+        {
+            assertMetricFamilySamples(metricsMap.get(expectedFamily));
+        }
+        return metricsMap;
+    }
+
+    @Test
+    public void testToSnakeCase()
+    {
+        assertThat(QpidCollector.toSnakeCase("carEngineOilChanges"), is(equalTo("car_engine_oil_changes")));
+    }
+
+    @Test
+    public void getFamilyNameForCumulativeStatistic()
+    {
+        for (int i = 0; i < UNITS.length; i++)
+        {
+            final ConfiguredObjectStatistic<?, ?> statistics = mock(ConfiguredObjectStatistic.class);
+            when(statistics.getUnits()).thenReturn(UNITS[i]);
+            when(statistics.getStatisticType()).thenReturn(StatisticType.CUMULATIVE);
+            when(statistics.getName()).thenReturn("diagnosticData");
+            final String familyName = QpidCollector.getFamilyName(TestCar.class, statistics);
+            final String expectedName =
+                    String.format("qpid_test_car_diagnostic_data%s%s", UNIT_SUFFIXES[i], getSuffix(UNITS[i],QpidCollector.COUNT_SUFFIX));
+            assertThat(String.format("unexpected metric name for units %s", UNITS[i]),
+                       familyName,
+                       is(equalTo(expectedName)));
+        }
+    }
+
+    @Test
+    public void getFamilyNameForCumulativeStatisticContainingCountInName()
+    {
+        final ConfiguredObjectStatistic<?, ?> statistics = mock(ConfiguredObjectStatistic.class);
+        when(statistics.getUnits()).thenReturn(StatisticUnit.BYTES);
+        when(statistics.getStatisticType()).thenReturn(StatisticType.CUMULATIVE);
+        when(statistics.getName()).thenReturn("CountOfDiagnosticData");
+        final String familyName = QpidCollector.getFamilyName(TestCar.class, statistics);
+        assertThat(familyName, is(equalTo("qpid_test_car_count_of_diagnostic_data")));
+    }
+
+    @Test
+    public void getFamilyNameForPointInTimeStatistic()
+    {
+        for (int i = 0; i < UNITS.length; i++)
+        {
+            final ConfiguredObjectStatistic<?, ?> statistics = mock(ConfiguredObjectStatistic.class);
+            when(statistics.getUnits()).thenReturn(UNITS[i]);
+            when(statistics.getStatisticType()).thenReturn(StatisticType.POINT_IN_TIME);
+            when(statistics.getName()).thenReturn("diagnosticData");
+            final String familyName = QpidCollector.getFamilyName(TestCar.class, statistics);
+
+            final String expectedName =
+                    String.format("qpid_test_car_diagnostic_data%s%s", UNIT_SUFFIXES[i],  getSuffix(UNITS[i],QpidCollector.TOTAL_SUFFIX));
+            assertThat(String.format("unexpected metric name for units %s", UNITS[i]),
+                       familyName,
+                       is(equalTo(expectedName)));
+        }
+    }
+
+    String getSuffix(final StatisticUnit unit,final String requiredSuffix)
+    {
+        String suffix = "_" + requiredSuffix;
+        if(unit.equals(StatisticUnit.ABSOLUTE_TIME) || unit.equals(StatisticUnit.TIME_DURATION)){
+            suffix = "";
+        }
+        return suffix;
+    }
+}
diff --git a/doc/java-broker/src/docbkx/Java-Broker-Management-Channels.xml b/doc/java-broker/src/docbkx/Java-Broker-Management-Channels.xml
index 9211056..13e7fa2 100644
--- a/doc/java-broker/src/docbkx/Java-Broker-Management-Channels.xml
+++ b/doc/java-broker/src/docbkx/Java-Broker-Management-Channels.xml
@@ -37,5 +37,6 @@
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="management/channels/Java-Broker-Management-Channel-HTTP.xml"/>
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="management/channels/Java-Broker-Management-Channel-Web-Console.xml"/>
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="management/channels/Java-Broker-Management-Channel-REST-API.xml"/>
+  <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="management/channels/Java-Broker-Management-Metrics.xml"/>
   <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="management/channels/Java-Broker-Management-Channel-AMQP-Intrinsic.xml"/>
 </chapter>
diff --git a/doc/java-broker/src/docbkx/management/channels/Java-Broker-Management-Metrics.xml b/doc/java-broker/src/docbkx/management/channels/Java-Broker-Management-Metrics.xml
new file mode 100644
index 0000000..c8aeb9d
--- /dev/null
+++ b/doc/java-broker/src/docbkx/management/channels/Java-Broker-Management-Metrics.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<!--
+  ~ 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.
+  ~
+  -->
+
+<section xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="Java-Broker-Management-Metrics">
+  <title>Prometheus Metrics</title>
+    <para>This section describes the metrics endpoints exposing broker statistics in
+      <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://prometheus.io/">Prometheus format</link>.
+      The metrics endpoint is intended for scraping by Prometheus server to collect the Broker telemetry.</para>
+    <para>The Prometheus metric endpoints are mapped under /metrics path and /metrics/*.
+      The latter allows to get the Virtual Host statistics by specify the path to the virtual host as
+      /metrics/&lt;virtual host node name&gt;/&lt; virtual host name&gt;.
+      The former allow to get all Broker statistics or Virtual Host statistics when called with HOST header
+      set to the Virtual Host name</para>
+    <para>
+      The metrics endpoints allow anonymous access by default. If required, an authentication can be enabled for the
+      metrics endpoints by setting http management context variable
+      <literal>qpid.httpManagement.enableMetricContentAuthentication</literal> to <literal>true</literal>.
+    </para>
+
+    <para>The Broker JVM statistics are disabled by default. The metrics endpoints can be called with parameter
+      <literal>includeDisabled</literal> set to <literal>true</literal> to include JVM  broker metrics into endpoint
+      output.
+    </para>
+    <note>
+      <para>For more information about Prometheus, check out the
+        <link xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="https://prometheus.io/docs/introduction/overview/">prometheus documentation</link>.
+      </para>
+    </note>
+
+</section>
diff --git a/pom.xml b/pom.xml
index 274b957..c49bcb8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -154,6 +154,7 @@
     <h2.version>1.4.199</h2.version>
     <apache-directory-version>2.0.0-M23</apache-directory-version>
     <kerby-version>1.0.1</kerby-version>
+    <prometheus-client-version>0.9.0</prometheus-client-version>
   </properties>
 
   <modules>
@@ -179,6 +180,7 @@
     <module>broker-plugins/websocket</module>
     <module>broker-plugins/amqp-1-0-bdb-store</module>
     <module>broker-plugins/amqp-1-0-jdbc-store</module>
+    <module>broker-plugins/prometheus-exporter</module>
     <module>tools</module>
 
     <module>qpid-systests-parent</module>
@@ -469,6 +471,12 @@
         <version>${project.version}</version>
       </dependency>
 
+      <dependency>
+        <groupId>org.apache.qpid</groupId>
+        <artifactId>qpid-broker-plugins-prometheus-exporter</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+
       <!-- External dependencies -->
       <dependency>
         <groupId>org.apache.qpid</groupId>
@@ -750,6 +758,18 @@
         <scope>test</scope>
         <version>${kerby-version}</version>
       </dependency>
+
+      <!-- prometheus client dependencies -->
+      <dependency>
+        <groupId>io.prometheus</groupId>
+        <artifactId>simpleclient</artifactId>
+        <version>${prometheus-client-version}</version>
+      </dependency>
+      <dependency>
+        <groupId>io.prometheus</groupId>
+        <artifactId>simpleclient_common</artifactId>
+        <version>${prometheus-client-version}</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
diff --git a/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/BrokerMetricsAuthenticationTest.java b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/BrokerMetricsAuthenticationTest.java
new file mode 100644
index 0000000..fcfca5e
--- /dev/null
+++ b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/BrokerMetricsAuthenticationTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.qpid.tests.http.metrics;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+
+import org.apache.qpid.server.management.plugin.HttpManagement;
+import org.apache.qpid.tests.http.HttpRequestConfig;
+import org.apache.qpid.tests.http.HttpTestBase;
+import org.apache.qpid.tests.http.HttpTestHelper;
+import org.apache.qpid.tests.utils.ConfigItem;
+
+@HttpRequestConfig(useVirtualHostAsHost = false)
+@ConfigItem(name = HttpManagement.HTTP_MANAGEMENT_ENABLE_CONTENT_AUTHENTICATION, value = "true")
+public class BrokerMetricsAuthenticationTest extends HttpTestBase
+{
+    @Test
+    public void testBrokerMetricsForAuthenticatedUser() throws Exception
+    {
+        getHelper().submitRequest("/metrics", "GET", HttpServletResponse.SC_OK);
+    }
+
+    @Test
+    public void testBrokerMetricsForUnauthenticatedUser() throws Exception
+    {
+        final HttpTestHelper helper = new HttpTestHelper(getBrokerAdmin(), null);
+        helper.setUserName(null);
+        helper.setPassword(null);
+        helper.submitRequest("/metrics", "GET", HttpServletResponse.SC_UNAUTHORIZED);
+    }
+
+}
diff --git a/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/BrokerMetricsTest.java b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/BrokerMetricsTest.java
new file mode 100644
index 0000000..7423588
--- /dev/null
+++ b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/BrokerMetricsTest.java
@@ -0,0 +1,80 @@
+package org.apache.qpid.tests.http.metrics;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.qpid.tests.http.metrics.TestMetricsHelper.QUEUE_NAME;
+import static org.apache.qpid.tests.http.metrics.TestMetricsHelper.assertMetricsInclusion;
+import static org.apache.qpid.tests.http.metrics.TestMetricsHelper.assertVirtualHostHierarchyMetrics;
+import static org.apache.qpid.tests.http.metrics.TestMetricsHelper.createQueueMetricPattern;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.util.Collection;
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+import org.apache.qpid.tests.http.HttpRequestConfig;
+import org.apache.qpid.tests.http.HttpTestBase;
+
+@HttpRequestConfig(useVirtualHostAsHost = false)
+public class BrokerMetricsTest extends HttpTestBase
+{
+    private static final String[] EXPECTED_BROKER_METRIC_NAMES =
+            new String[]{"qpid_broker_inbound_bytes_count", "qpid_broker_outbound_bytes_count"};
+
+    @Test
+    public void testBrokerMetrics() throws Exception
+    {
+        final String[] unexpectedMetricNames =
+                {"qpid_broker_live_threads_total", "qpid_broker_direct_memory_capacity_bytes_total"};
+
+        final byte[] metricsBytes = getHelper().getBytes("/metrics");
+        final String metricsString = new String(metricsBytes, UTF_8);
+        assertMetricsInclusion(metricsString, EXPECTED_BROKER_METRIC_NAMES, true);
+        assertMetricsInclusion(metricsString, unexpectedMetricNames, false);
+
+        final byte[] metricsBytesIncludingDisabled = getHelper().getBytes("/metrics?includeDisabled=true");
+        final String metricsStringIncludingDisabled = new String(metricsBytesIncludingDisabled, UTF_8);
+        assertMetricsInclusion(metricsStringIncludingDisabled, unexpectedMetricNames, true);
+        assertMetricsInclusion(metricsStringIncludingDisabled, EXPECTED_BROKER_METRIC_NAMES, true);
+    }
+
+    @Test
+    public void testQueueMetrics() throws Exception
+    {
+        getBrokerAdmin().createQueue(QUEUE_NAME);
+        final byte[] metricsBytes = getHelper().getBytes("/metrics");
+        final String metricsString = new String(metricsBytes, UTF_8);
+
+        final Pattern[] expectedMetricPattens = {createQueueMetricPattern("qpid_queue_consumers_total"),
+                createQueueMetricPattern("qpid_queue_depth_messages_total")};
+
+        assertMetricsInclusion(metricsString, expectedMetricPattens, true);
+    }
+
+    @Test
+    public void testQueueMetricsIncludeOnlyMessageDepth() throws Exception
+    {
+        getBrokerAdmin().createQueue(QUEUE_NAME);
+        final byte[] metricsBytes = getHelper().getBytes("/metrics?name[]=qpid_queue_depth_messages_total&name[]=qpid_queue_depth_bytes_total");
+        Collection<String> metricLines = TestMetricsHelper.getMetricLines(metricsBytes);
+        assertThat(metricLines.size(), is(equalTo(2)));
+
+        final String metricsString = new String(metricsBytes, UTF_8);
+        final Pattern[] expectedMetricPattens = {createQueueMetricPattern("qpid_queue_depth_bytes_total"),
+                createQueueMetricPattern("qpid_queue_depth_messages_total")};
+
+        assertMetricsInclusion(metricsString, expectedMetricPattens, true);
+    }
+
+    @Test
+    public void testMappingForVirtualHost() throws Exception
+    {
+        getBrokerAdmin().createQueue(QUEUE_NAME);
+        final byte[] metricsBytes =
+                getHelper().getBytes(String.format("/metrics/%s/%s", getVirtualHostNode(), getVirtualHost()));
+
+        assertVirtualHostHierarchyMetrics(metricsBytes);
+    }
+}
diff --git a/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/TestMetricsHelper.java b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/TestMetricsHelper.java
new file mode 100644
index 0000000..fa9b679
--- /dev/null
+++ b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/TestMetricsHelper.java
@@ -0,0 +1,93 @@
+/*
+ * 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.qpid.tests.http.metrics;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+
+class TestMetricsHelper
+{
+    static final String QUEUE_NAME = "foo";
+
+    static void assertMetricsInclusion(final String metricsString,
+                                       final String[] metricNames,
+                                       final boolean inclusionFlag)
+    {
+        for (String expected : metricNames)
+        {
+            assertThat(metricsString.contains(expected), equalTo(inclusionFlag));
+        }
+    }
+
+    static void assertMetricsInclusion(final String metricsString,
+                                       final Pattern[] expectedMetricPattens,
+                                       final boolean inclusionFlag)
+    {
+        for (Pattern expected : expectedMetricPattens)
+        {
+            assertThat(expected.matcher(metricsString).find(), equalTo(inclusionFlag));
+        }
+    }
+
+    static Pattern createQueueMetricPattern(final String metricName)
+    {
+        return Pattern.compile(String.format("%s\\s*\\{.*name\\s*=\\s*\"%s\"\\s*,.*\\}\\s*0\\.0",
+                                             metricName,
+                                             QUEUE_NAME));
+    }
+
+    static void assertVirtualHostHierarchyMetrics(final byte[] metricsBytes) throws IOException
+    {
+        final Predicate<String> unexpectedMetricPredicate = line -> !(line.startsWith("qpid_virtual_host")
+                                                                      || line.startsWith("qpid_queue")
+                                                                      || line.startsWith("qpid_exchange"));
+        getMetricLines(metricsBytes).stream().filter(unexpectedMetricPredicate)
+                                    .findFirst().ifPresent(found -> fail("Unexpected metric: " + found));
+    }
+
+    static Collection<String> getMetricLines(final byte[] metricsBytes) throws IOException
+    {
+        final List<String> results = new ArrayList<>();
+        try(BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(metricsBytes))))
+        {
+            String line;
+            while ((line = reader.readLine()) != null)
+            {
+                if (!(line.startsWith("#") || line.isEmpty()))
+                {
+                    results.add(line);
+                }
+            }
+        }
+        return results;
+    }
+}
diff --git a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/VirtualHostMetricsTest.java
similarity index 54%
copy from broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
copy to systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/VirtualHostMetricsTest.java
index cc22d62..bd06588 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectStatistic.java
+++ b/systests/qpid-systests-http-management/src/test/java/org/apache/qpid/tests/http/metrics/VirtualHostMetricsTest.java
@@ -1,5 +1,4 @@
 /*
- *
  * 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
@@ -18,15 +17,25 @@
  * under the License.
  *
  */
-package org.apache.qpid.server.model;
 
-public interface ConfiguredObjectStatistic<C extends ConfiguredObject, T extends Object> extends ConfiguredObjectAttributeOrStatistic<C,T>
-{
-    String getDescription();
+package org.apache.qpid.tests.http.metrics;
 
-    StatisticUnit getUnits();
+import static org.apache.qpid.tests.http.metrics.TestMetricsHelper.QUEUE_NAME;
+import static org.apache.qpid.tests.http.metrics.TestMetricsHelper.assertVirtualHostHierarchyMetrics;
 
-    StatisticType getStatisticType();
+import org.junit.Test;
 
-    String getLabel();
+import org.apache.qpid.tests.http.HttpRequestConfig;
+import org.apache.qpid.tests.http.HttpTestBase;
+
+@HttpRequestConfig
+public class VirtualHostMetricsTest extends HttpTestBase
+{
+    @Test
+    public void testVirtualHostMetrics() throws Exception
+    {
+        getBrokerAdmin().createQueue(QUEUE_NAME);
+        final byte[] metricsBytes = getHelper().getBytes("/metrics");
+        assertVirtualHostHierarchyMetrics(metricsBytes);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org