geode-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kl...@apache.org
Subject [geode] branch develop updated: GEODE-6295: Add Micrometer-based metrics system (#3277)
Date Wed, 13 Mar 2019 21:16:51 GMT
This is an automated email from the ASF dual-hosted git repository.

klund pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 8ee587c  GEODE-6295: Add Micrometer-based metrics system (#3277)
8ee587c is described below

commit 8ee587c0d2e301775dae6a1eecba4f9a9a258ca6
Author: Kirk Lund <klund@apache.org>
AuthorDate: Wed Mar 13 14:16:34 2019 -0700

    GEODE-6295: Add Micrometer-based metrics system (#3277)
    
    - Add MetricsSession interface that represents the lifecycle of a meter
      registry, and allows connecting "downstream" registries.
    - Add MetricsPublishingService interface that defines a service that
      can be implemented to interact with MetricsSessions by connecting
      "downstream" registries to publish metrics to external monitoring
      systems.
    - Add CompositeMeterRegistryFactory whichs handles creating the
      composite meter registry for the cache and defining its common tags.
    - Add CacheLifecycleMetricsSession class that loads implementations of
      MetricsPublishingService and manages the cache meter registry based
      on the cache lifecycle.
    - InternalCacheBuilder uses CompositeMeterRegistryFactory to create the
      cache meter registry and starts a CacheLifecycleMetricsSession. The
      cache meter registry is passed to GemFireCacheImpl and exposed to
      internal Geode code by InternalCache.getMeterRegistry().
    
    Co-Authored-By: Dale Emery <demery@pivotal.io>
    Co-Authored-By: Michael Oleske <moleske@pivotal.io>
    Co-Authored-By: Mark Hanson <mhanson@pivotal.io>
    Co-Authored-By: Kirk Lund <klund@apache.org>
---
 .../src/test/resources/expected-pom.xml            |   5 +
 .../gradle/plugins/DependencyConstraints.groovy    |   2 +
 extensions/geode-modules-assembly/build.gradle     |   1 +
 .../release/session/bin/modify_war                 |   2 +
 .../apache/geode/session/tests/TomcatInstall.java  |   4 +-
 .../integrationTest/resources/assembly_content.txt |   8 +
 .../resources/dependency_classpath.txt             |   3 +
 .../integrationTest/resources/expected_jars.txt    |   3 +
 geode-assembly/src/main/dist/LICENSE               |   2 +
 geode-core/build.gradle                            |   2 +
 .../geode/internal/cache/GemFireCacheImpl.java     |  17 +-
 .../apache/geode/internal/cache/InternalCache.java |   4 +
 .../geode/internal/cache/InternalCacheBuilder.java |  33 +++-
 .../cache/InternalCacheForClientAccess.java        |   7 +
 .../internal/cache/xmlcache/CacheCreation.java     |   7 +
 .../metrics/CacheLifecycleMetricsSession.java      | 129 ++++++++++++
 .../internal/metrics/CollectingServiceLoader.java  |  29 +++
 .../metrics/CompositeMeterRegistryFactory.java     |  44 +++++
 .../geode/metrics/MetricsPublishingService.java    |  89 +++++++++
 .../org/apache/geode/metrics/MetricsSession.java   |  44 +++++
 .../org/apache/geode/metrics/package-info.java     |  37 ++++
 ...nalDistributedSystemStatisticsManagerTest.java} |   2 +-
 .../geode/internal/cache/GemFireCacheImplTest.java |  10 +
 ...ernalCacheBuilderAllowsMultipleSystemsTest.java |  56 +++++-
 .../internal/cache/InternalCacheBuilderTest.java   | 116 +++++++++--
 .../CacheLifecycleMetricsSessionBuilderTest.java   | 108 ++++++++++
 .../metrics/CacheLifecycleMetricsSessionTest.java  | 220 +++++++++++++++++++++
 .../metrics/CompositeMeterRegistryFactoryTest.java |  79 ++++++++
 geode-core/src/test/resources/expected-pom.xml     |   5 +
 29 files changed, 1038 insertions(+), 30 deletions(-)

diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml
index b6b6d40..a8dcc11 100644
--- a/boms/geode-all-bom/src/test/resources/expected-pom.xml
+++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml
@@ -173,6 +173,11 @@
         <version>4.0.6</version>
       </dependency>
       <dependency>
+        <groupId>io.micrometer</groupId>
+        <artifactId>micrometer-core</artifactId>
+        <version>1.1.3</version>
+      </dependency>
+      <dependency>
         <groupId>io.netty</groupId>
         <artifactId>netty-all</artifactId>
         <version>4.1.31.Final</version>
diff --git a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
index c7c15b4..004c593 100644
--- a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
+++ b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy
@@ -41,6 +41,7 @@ class DependencyConstraints implements Plugin<Project> {
     deps.put("javax.transaction-api.version", "1.3")
     deps.put("jgroups.version", "3.6.14.Final")
     deps.put("log4j.version", "2.11.1")
+    deps.put("micrometer.version", "1.1.3")
     deps.put("shiro.version", "1.4.0")
     deps.put("slf4j-api.version", "1.7.25")
 
@@ -111,6 +112,7 @@ class DependencyConstraints implements Plugin<Project> {
         api(group: 'commons-modeler', name: 'commons-modeler', version: '2.0.1')
         api(group: 'commons-validator', name: 'commons-validator', version: get('commons-validator.version'))
         api(group: 'io.github.classgraph', name: 'classgraph', version: '4.0.6')
+        api(group: 'io.micrometer', name: 'micrometer-core', version: get('micrometer.version'))
         api(group: 'io.netty', name: 'netty-all', version: '4.1.31.Final')
         api(group: 'it.unimi.dsi', name: 'fastutil', version: get('fastutil.version'))
         api(group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2')
diff --git a/extensions/geode-modules-assembly/build.gradle b/extensions/geode-modules-assembly/build.gradle
index efa66cf..13c9597 100644
--- a/extensions/geode-modules-assembly/build.gradle
+++ b/extensions/geode-modules-assembly/build.gradle
@@ -208,6 +208,7 @@ task distAppServer(type: Zip, dependsOn: [':extensions:geode-modules-session:jar
       filter(ReplaceTokens, tokens:['LOG4J_VERSION': DependencyConstraints.get('log4j.version')])
       filter(ReplaceTokens, tokens:['FASTUTIL_VERSION': DependencyConstraints.get('fastutil.version')])
       filter(ReplaceTokens, tokens:['ANTLR_VERSION': DependencyConstraints.get('antlr.version')])
+      filter(ReplaceTokens, tokens:['MICROMETER_VERSION': DependencyConstraints.get('micrometer.version')])
       filter(ReplaceTokens, tokens:['TX_VERSION': DependencyConstraints.get('javax.transaction-api.version')])
       filter(ReplaceTokens, tokens:['JGROUPS_VERSION': DependencyConstraints.get('jgroups.version')])
       filter(ReplaceTokens, tokens:['JETTY_VERSION': DependencyConstraints.get('jetty.version')])
diff --git a/extensions/geode-modules-assembly/release/session/bin/modify_war b/extensions/geode-modules-assembly/release/session/bin/modify_war
index bdafa4c..1a44e5d 100755
--- a/extensions/geode-modules-assembly/release/session/bin/modify_war
+++ b/extensions/geode-modules-assembly/release/session/bin/modify_war
@@ -97,6 +97,7 @@ WHERE <args>:
                         fastutil.jar
                         javax.transactions-api.jar
                         jgroups.jar
+                        micrometer-core.jar
                         slf4j-api.jar
                         slf4j-jdk14.jar (not for WebLogic)
                         geode-modules-slf4j-weblogic.jar (WebLogic only)
@@ -280,6 +281,7 @@ OTHER_JARS=(${GEODE}/lib/geode-core-${VERSION}.jar \
     ${GEODE}/lib/commons-lang3-@COMMONS_LANG_VERSION@.jar \
     ${GEODE}/lib/shiro-core-@SHIRO_VERSION@.jar \
     ${GEODE}/lib/commons-validator-@COMMONS_VALIDATOR_VERSION@.jar \
+    ${GEODE}/lib/micrometer-core-@MICROMETER_VERSION@.jar \
     ${LIB_DIR}/geode-modules-${VERSION}.jar \
     ${LIB_DIR}/geode-modules-session-internal-${VERSION}.jar \
     ${LIB_DIR}/slf4j-api-@SLF4J_VERSION@.jar \
diff --git a/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/session/tests/TomcatInstall.java b/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/session/tests/TomcatInstall.java
index 31958bc..dabf7df 100644
--- a/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/session/tests/TomcatInstall.java
+++ b/geode-assembly/geode-assembly-test/src/main/java/org/apache/geode/session/tests/TomcatInstall.java
@@ -92,8 +92,8 @@ public class TomcatInstall extends ContainerInstall {
   private static final String[] tomcatRequiredJars =
       {"antlr", "commons-io", "commons-lang", "commons-validator", "fastutil", "geode-common",
           "geode-core", "geode-management", "javax.transaction-api", "jgroups", "log4j-api",
-          "log4j-core", "log4j-jul", "shiro-core", "jetty-server", "jetty-util", "jetty-http",
-          "jetty-io"};
+          "log4j-core", "log4j-jul", "micrometer", "shiro-core", "jetty-server", "jetty-util",
+          "jetty-http", "jetty-io"};
 
   private final TomcatVersion version;
 
diff --git a/geode-assembly/src/integrationTest/resources/assembly_content.txt b/geode-assembly/src/integrationTest/resources/assembly_content.txt
index abca10f..09c988d 100644
--- a/geode-assembly/src/integrationTest/resources/assembly_content.txt
+++ b/geode-assembly/src/integrationTest/resources/assembly_content.txt
@@ -724,6 +724,11 @@ javadoc/org/apache/geode/memcached/GemFireMemcachedServer.html
 javadoc/org/apache/geode/memcached/package-frame.html
 javadoc/org/apache/geode/memcached/package-summary.html
 javadoc/org/apache/geode/memcached/package-tree.html
+javadoc/org/apache/geode/metrics/MetricsPublishingService.html
+javadoc/org/apache/geode/metrics/MetricsSession.html
+javadoc/org/apache/geode/metrics/package-frame.html
+javadoc/org/apache/geode/metrics/package-summary.html
+javadoc/org/apache/geode/metrics/package-tree.html
 javadoc/org/apache/geode/modules/gatewaydelta/AbstractGatewayDeltaEvent.html
 javadoc/org/apache/geode/modules/gatewaydelta/GatewayDelta.html
 javadoc/org/apache/geode/modules/gatewaydelta/GatewayDeltaCreateEvent.html
@@ -874,7 +879,9 @@ javadoc/package-list
 javadoc/script.js
 javadoc/serialized-form.html
 javadoc/stylesheet.css
+lib/HdrHistogram-2.1.9.jar
 lib/HikariCP-3.2.0.jar
+lib/LatencyUtils-2.0.3.jar
 lib/antlr-2.7.7.jar
 lib/classgraph-4.0.6.jar
 lib/commons-beanutils-1.9.3.jar
@@ -945,6 +952,7 @@ lib/lucene-analyzers-phonetic-6.6.2.jar
 lib/lucene-core-6.6.2.jar
 lib/lucene-queries-6.6.2.jar
 lib/lucene-queryparser-6.6.2.jar
+lib/micrometer-core-1.1.3.jar
 lib/mx4j-3.0.2.jar
 lib/mx4j-remote-3.0.2.jar
 lib/mx4j-tools-3.0.1.jar
diff --git a/geode-assembly/src/integrationTest/resources/dependency_classpath.txt b/geode-assembly/src/integrationTest/resources/dependency_classpath.txt
index e488c40..92f206e 100644
--- a/geode-assembly/src/integrationTest/resources/dependency_classpath.txt
+++ b/geode-assembly/src/integrationTest/resources/dependency_classpath.txt
@@ -1,4 +1,6 @@
+HdrHistogram-2.1.9.jar
 HikariCP-3.2.0.jar
+LatencyUtils-2.0.3.jar
 antlr-2.7.7.jar
 classgraph-4.0.6.jar
 commons-beanutils-1.9.3.jar
@@ -61,6 +63,7 @@ lucene-analyzers-phonetic-6.6.2.jar
 lucene-core-6.6.2.jar
 lucene-queries-6.6.2.jar
 lucene-queryparser-6.6.2.jar
+micrometer-core-1.1.3.jar
 netty-all-4.1.31.Final.jar
 protobuf-java-3.6.1.jar
 rmiio-2.1.2.jar
diff --git a/geode-assembly/src/integrationTest/resources/expected_jars.txt b/geode-assembly/src/integrationTest/resources/expected_jars.txt
index 1b3e988..8240782 100644
--- a/geode-assembly/src/integrationTest/resources/expected_jars.txt
+++ b/geode-assembly/src/integrationTest/resources/expected_jars.txt
@@ -1,4 +1,6 @@
+HdrHistogram
 HikariCP
+LatencyUtils
 animal-sniffer-annotations
 antlr
 aopalliance
@@ -68,6 +70,7 @@ lucene-core
 lucene-queries
 lucene-queryparser
 mapstruct
+micrometer-core
 mx4j
 mx4j-remote
 mx4j-tools
diff --git a/geode-assembly/src/main/dist/LICENSE b/geode-assembly/src/main/dist/LICENSE
index 348706e..94a70e6 100644
--- a/geode-assembly/src/main/dist/LICENSE
+++ b/geode-assembly/src/main/dist/LICENSE
@@ -725,4 +725,6 @@ domain:
   - AOP Alliance v1.0 (http://aopalliance.sourceforge.net)
   - CompactConcurrentHashSet2, derived from JSR-166 ConcurrentHashMap v1.43
     (http://gee.cs.oswego.edu/dl/concurrency-interest).
+  - HdrHistogram (https://github.com/HdrHistogram/HdrHistogram)
+  - LatencyUtils (https://github.com/LatencyUtils/LatencyUtils)
   - tooltip.js v1.2.6 (https://github.com/jquerytools/jquerytools)
diff --git a/geode-core/build.gradle b/geode-core/build.gradle
index c91ba88..5b5da3e 100755
--- a/geode-core/build.gradle
+++ b/geode-core/build.gradle
@@ -194,6 +194,8 @@ dependencies {
     exclude module: 'xml-apis'
     ext.optional = true
   }
+  compile('io.micrometer:micrometer-core')
+
   compile('io.netty:netty-all') {
     ext.optional = true
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java b/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
index 741d86e..c13b5f7 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/GemFireCacheImpl.java
@@ -77,6 +77,7 @@ import javax.transaction.TransactionManager;
 
 import com.sun.jna.Native;
 import com.sun.jna.Platform;
+import io.micrometer.core.instrument.MeterRegistry;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.Logger;
 
@@ -281,7 +282,8 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
    * The {@code CacheLifecycleListener} s that have been registered in this VM
    */
   @MakeNotStatic
-  private static final Set<CacheLifecycleListener> cacheLifecycleListeners = new HashSet<>();
+  private static final Set<CacheLifecycleListener> cacheLifecycleListeners =
+      new CopyOnWriteArraySet<>();
 
   /**
    * Define gemfire.Cache.ASYNC_EVENT_LISTENERS=true to invoke event listeners in the background
@@ -613,6 +615,8 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
 
   private Optional<HttpService> httpService = Optional.ofNullable(null);
 
+  private final MeterRegistry meterRegistry;
+
   static {
     // this works around jdk bug 6427854, reported in ticket #44434
     String propertyName = "sun.nio.ch.bugLevel";
@@ -763,6 +767,7 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
    * @deprecated Rather than fishing for a cache with this static method, use a cache that is passed
    *             in to your method.
    */
+  @Deprecated
   public static GemFireCacheImpl getForPdx(String reason) {
 
     InternalDistributedSystem system = getAnyInstance();
@@ -784,12 +789,13 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
    * Currently only unit tests set the typeRegistry parameter to a non-null value
    */
   GemFireCacheImpl(boolean isClient, PoolFactory poolFactory,
-      InternalDistributedSystem internalDistributedSystem,
-      CacheConfig cacheConfig, boolean useAsyncEventListeners, TypeRegistry typeRegistry) {
+      InternalDistributedSystem internalDistributedSystem, CacheConfig cacheConfig,
+      boolean useAsyncEventListeners, TypeRegistry typeRegistry, MeterRegistry meterRegistry) {
     this.isClient = isClient;
     this.poolFactory = poolFactory;
     this.cacheConfig = cacheConfig; // do early for bug 43213
     this.pdxRegistry = typeRegistry;
+    this.meterRegistry = meterRegistry;
 
     // Synchronized to prevent a new cache from being created
     // before an old one has finished closing
@@ -953,6 +959,11 @@ public class GemFireCacheImpl implements InternalCache, InternalClientCache, Has
   }
 
   @Override
+  public MeterRegistry getMeterRegistry() {
+    return meterRegistry;
+  }
+
+  @Override
   public Optional<HttpService> getHttpService() {
     return httpService;
   }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCache.java b/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCache.java
index a3d38ba..ddd0a92 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCache.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCache.java
@@ -27,6 +27,8 @@ import java.util.concurrent.Executor;
 
 import javax.transaction.TransactionManager;
 
+import io.micrometer.core.instrument.MeterRegistry;
+
 import org.apache.geode.cache.Cache;
 import org.apache.geode.cache.CacheClosedException;
 import org.apache.geode.cache.Declarable;
@@ -377,4 +379,6 @@ public interface InternalCache extends Cache, Extensible<Cache>, CacheTime {
   void initialize();
 
   void throwCacheExistsException();
+
+  MeterRegistry getMeterRegistry();
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheBuilder.java b/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheBuilder.java
index 342cef9..8c8702e 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheBuilder.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheBuilder.java
@@ -20,8 +20,11 @@ import static org.apache.geode.distributed.internal.InternalDistributedSystem.AL
 
 import java.util.Optional;
 import java.util.Properties;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
 import org.apache.logging.log4j.Logger;
 
 import org.apache.geode.annotations.VisibleForTesting;
@@ -39,6 +42,8 @@ import org.apache.geode.distributed.DistributedSystem;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
 import org.apache.geode.distributed.internal.SecurityConfig;
 import org.apache.geode.internal.logging.LogService;
+import org.apache.geode.internal.metrics.CacheLifecycleMetricsSession;
+import org.apache.geode.internal.metrics.CompositeMeterRegistryFactory;
 import org.apache.geode.pdx.PdxSerializer;
 import org.apache.geode.pdx.internal.TypeRegistry;
 import org.apache.geode.security.AuthenticationFailedException;
@@ -58,6 +63,8 @@ public class InternalCacheBuilder {
 
   private final Properties configProperties;
   private final CacheConfig cacheConfig;
+  private final CompositeMeterRegistryFactory compositeMeterRegistryFactory;
+  private final Consumer<CompositeMeterRegistry> metricsSessionInitializer;
   private final Supplier<InternalDistributedSystem> singletonSystemSupplier;
   private final Supplier<InternalCache> singletonCacheSupplier;
   private final InternalDistributedSystemConstructor internalDistributedSystemConstructor;
@@ -102,20 +109,29 @@ public class InternalCacheBuilder {
   }
 
   private InternalCacheBuilder(Properties configProperties, CacheConfig cacheConfig) {
-    this(configProperties, cacheConfig, InternalDistributedSystem::getConnectedInstance,
+    this(configProperties,
+        cacheConfig,
+        new CompositeMeterRegistryFactory() {},
+        CacheLifecycleMetricsSession.builder()::build,
+        InternalDistributedSystem::getConnectedInstance,
         InternalDistributedSystem::connectInternal,
-        GemFireCacheImpl::getInstance, GemFireCacheImpl::new);
+        GemFireCacheImpl::getInstance,
+        GemFireCacheImpl::new);
   }
 
   @VisibleForTesting
   InternalCacheBuilder(Properties configProperties,
       CacheConfig cacheConfig,
+      CompositeMeterRegistryFactory compositeMeterRegistryFactory,
+      Consumer<CompositeMeterRegistry> metricsSessionInitializer,
       Supplier<InternalDistributedSystem> singletonSystemSupplier,
       InternalDistributedSystemConstructor internalDistributedSystemConstructor,
       Supplier<InternalCache> singletonCacheSupplier,
       InternalCacheConstructor internalCacheConstructor) {
     this.configProperties = configProperties;
     this.cacheConfig = cacheConfig;
+    this.compositeMeterRegistryFactory = compositeMeterRegistryFactory;
+    this.metricsSessionInitializer = metricsSessionInitializer;
     this.singletonSystemSupplier = singletonSystemSupplier;
     this.internalDistributedSystemConstructor = internalDistributedSystemConstructor;
     this.internalCacheConstructor = internalCacheConstructor;
@@ -173,9 +189,18 @@ public class InternalCacheBuilder {
               existingCache(internalDistributedSystem::getCache, singletonCacheSupplier);
           if (cache == null) {
 
+            int systemId = internalDistributedSystem.getConfig().getDistributedSystemId();
+            String memberName = internalDistributedSystem.getName();
+            String hostName = internalDistributedSystem.getDistributedMember().getHost();
+
+            CompositeMeterRegistry compositeMeterRegistry = compositeMeterRegistryFactory
+                .create(systemId, memberName, hostName);
+
+            metricsSessionInitializer.accept(compositeMeterRegistry);
+
             cache =
                 internalCacheConstructor.construct(isClient, poolFactory, internalDistributedSystem,
-                    cacheConfig, useAsyncEventListeners, typeRegistry);
+                    cacheConfig, useAsyncEventListeners, typeRegistry, compositeMeterRegistry);
 
             internalDistributedSystem.setCache(cache);
             cache.initialize();
@@ -384,7 +409,7 @@ public class InternalCacheBuilder {
   interface InternalCacheConstructor {
     InternalCache construct(boolean isClient, PoolFactory poolFactory,
         InternalDistributedSystem internalDistributedSystem, CacheConfig cacheConfig,
-        boolean useAsyncEventListeners, TypeRegistry typeRegistry);
+        boolean useAsyncEventListeners, TypeRegistry typeRegistry, MeterRegistry meterRegistry);
   }
 
   @VisibleForTesting
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheForClientAccess.java b/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheForClientAccess.java
index bde6506..39d9b73 100644
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheForClientAccess.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/InternalCacheForClientAccess.java
@@ -32,6 +32,8 @@ import java.util.concurrent.TimeUnit;
 import javax.naming.Context;
 import javax.transaction.TransactionManager;
 
+import io.micrometer.core.instrument.MeterRegistry;
+
 import org.apache.geode.CancelCriterion;
 import org.apache.geode.LogWriter;
 import org.apache.geode.cache.Cache;
@@ -1234,4 +1236,9 @@ public class InternalCacheForClientAccess implements InternalCache {
   public void throwCacheExistsException() {
     delegate.throwCacheExistsException();
   }
+
+  @Override
+  public MeterRegistry getMeterRegistry() {
+    return delegate.getMeterRegistry();
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/xmlcache/CacheCreation.java b/geode-core/src/main/java/org/apache/geode/internal/cache/xmlcache/CacheCreation.java
index 7f47d09..2548f23 100755
--- a/geode-core/src/main/java/org/apache/geode/internal/cache/xmlcache/CacheCreation.java
+++ b/geode-core/src/main/java/org/apache/geode/internal/cache/xmlcache/CacheCreation.java
@@ -40,6 +40,8 @@ import java.util.concurrent.TimeUnit;
 import javax.naming.Context;
 import javax.transaction.TransactionManager;
 
+import io.micrometer.core.instrument.MeterRegistry;
+
 import org.apache.geode.CancelCriterion;
 import org.apache.geode.GemFireIOException;
 import org.apache.geode.LogWriter;
@@ -2441,4 +2443,9 @@ public class CacheCreation implements InternalCache {
   public Optional<HttpService> getHttpService() {
     throw new UnsupportedOperationException("Should not be invoked");
   }
+
+  @Override
+  public MeterRegistry getMeterRegistry() {
+    throw new UnsupportedOperationException("Should not be invoked");
+  }
 }
diff --git a/geode-core/src/main/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSession.java b/geode-core/src/main/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSession.java
new file mode 100644
index 0000000..a621185
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSession.java
@@ -0,0 +1,129 @@
+/*
+ * 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.geode.internal.metrics;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+
+import org.apache.geode.annotations.VisibleForTesting;
+import org.apache.geode.internal.cache.CacheLifecycleListener;
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.metrics.MetricsPublishingService;
+import org.apache.geode.metrics.MetricsSession;
+
+public class CacheLifecycleMetricsSession implements MetricsSession, CacheLifecycleListener {
+  private final CacheLifecycle cacheLifecycle;
+  private final CompositeMeterRegistry registry;
+  private final Collection<MetricsPublishingService> metricsPublishingServices;
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @VisibleForTesting
+  CacheLifecycleMetricsSession(CacheLifecycle cacheLifecycle, CompositeMeterRegistry registry,
+      Collection<MetricsPublishingService> metricsPublishingServices) {
+    this.cacheLifecycle = cacheLifecycle;
+    this.registry = registry;
+    this.metricsPublishingServices = metricsPublishingServices;
+  }
+
+  @Override
+  public void addSubregistry(MeterRegistry subregistry) {
+    registry.add(subregistry);
+  }
+
+  @Override
+  public void removeSubregistry(MeterRegistry subregistry) {
+    registry.remove(subregistry);
+  }
+
+  @Override
+  public void cacheCreated(InternalCache cache) {
+    for (MetricsPublishingService metricsPublishingService : metricsPublishingServices) {
+      metricsPublishingService.start(this);
+    }
+  }
+
+  @Override
+  public void cacheClosed(InternalCache cache) {
+    cacheLifecycle.removeListener(this);
+
+    for (MetricsPublishingService metricsPublishingService : metricsPublishingServices) {
+      metricsPublishingService.stop();
+    }
+
+    for (MeterRegistry downstream : new HashSet<>(registry.getRegistries())) {
+      removeSubregistry(downstream);
+    }
+  }
+
+  @VisibleForTesting
+  CompositeMeterRegistry meterRegistry() {
+    return registry;
+  }
+
+  @VisibleForTesting
+  Collection<MetricsPublishingService> metricsPublishingServices() {
+    return metricsPublishingServices;
+  }
+
+  public static class Builder {
+
+    private CollectingServiceLoader serviceLoader = new CollectingServiceLoader() {};
+    private CacheLifecycle cacheLifecycle = new CacheLifecycle() {};
+
+    private Builder() {
+      // private to prevent instantiation
+    }
+
+    @VisibleForTesting
+    Builder setCacheLifecycle(CacheLifecycle cacheLifecycle) {
+      this.cacheLifecycle = cacheLifecycle;
+      return this;
+    }
+
+    @VisibleForTesting
+    Builder setServiceLoader(CollectingServiceLoader serviceLoader) {
+      this.serviceLoader = serviceLoader;
+      return this;
+    }
+
+    public CacheLifecycleMetricsSession build(CompositeMeterRegistry registry) {
+      Collection<MetricsPublishingService> services =
+          serviceLoader.loadServices(MetricsPublishingService.class);
+      CacheLifecycleMetricsSession cacheLifecycleMetricsSession =
+          new CacheLifecycleMetricsSession(cacheLifecycle, registry, services);
+      cacheLifecycle.addListener(cacheLifecycleMetricsSession);
+      return cacheLifecycleMetricsSession;
+    }
+  }
+
+  @VisibleForTesting
+  interface CacheLifecycle {
+
+    default void addListener(CacheLifecycleListener listener) {
+      GemFireCacheImpl.addCacheLifecycleListener(listener);
+    }
+
+    default void removeListener(CacheLifecycleListener listener) {
+      GemFireCacheImpl.removeCacheLifecycleListener(listener);
+    }
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/metrics/CollectingServiceLoader.java b/geode-core/src/main/java/org/apache/geode/internal/metrics/CollectingServiceLoader.java
new file mode 100644
index 0000000..c5cbd69
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/metrics/CollectingServiceLoader.java
@@ -0,0 +1,29 @@
+/*
+ * 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.geode.internal.metrics;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.ServiceLoader;
+
+interface CollectingServiceLoader {
+
+  default <S> Collection<S> loadServices(Class<S> service) {
+    List<S> services = new ArrayList<>();
+    ServiceLoader.load(service).iterator().forEachRemaining(services::add);
+    return services;
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/internal/metrics/CompositeMeterRegistryFactory.java b/geode-core/src/main/java/org/apache/geode/internal/metrics/CompositeMeterRegistryFactory.java
new file mode 100644
index 0000000..a0e378d
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/internal/metrics/CompositeMeterRegistryFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.geode.internal.metrics;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+
+import org.apache.geode.annotations.VisibleForTesting;
+
+/**
+ * Creates {@code CompositeMeterRegistry} and configures commonTags.
+ */
+public interface CompositeMeterRegistryFactory {
+
+  @VisibleForTesting
+  String CLUSTER_ID_TAG = "ClusterId";
+  @VisibleForTesting
+  String MEMBER_NAME_TAG = "MemberName";
+  @VisibleForTesting
+  String HOST_NAME_TAG = "HostName";
+
+  default CompositeMeterRegistry create(int systemId, String memberName, String hostName) {
+    CompositeMeterRegistry registry = new CompositeMeterRegistry();
+
+    MeterRegistry.Config registryConfig = registry.config();
+    registryConfig.commonTags(CLUSTER_ID_TAG, String.valueOf(systemId));
+    registryConfig.commonTags(MEMBER_NAME_TAG, memberName == null ? "" : memberName);
+    registryConfig.commonTags(HOST_NAME_TAG, hostName == null ? "" : hostName);
+
+    return registry;
+  }
+}
diff --git a/geode-core/src/main/java/org/apache/geode/metrics/MetricsPublishingService.java b/geode-core/src/main/java/org/apache/geode/metrics/MetricsPublishingService.java
new file mode 100644
index 0000000..0baf765
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/metrics/MetricsPublishingService.java
@@ -0,0 +1,89 @@
+/*
+ * 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.geode.metrics;
+
+import java.util.ServiceLoader;
+
+import org.apache.geode.annotations.Experimental;
+
+/**
+ * Publishes metrics generated by Micrometer meters in the cache meter registry.
+ *
+ * <p>
+ * Geode discovers {@code MetricsPublishingService}s during cache creation, using the standard Java
+ * {@link ServiceLoader} mechanism:
+ *
+ * <p>
+ *
+ * <pre>
+ * package com.application;
+ *
+ * public class MyMetricsPublishingService implements MetricsPublishingService {
+ *   private volatile MeterRegistry registry;
+ *   private volatile MetricsSession session;
+ *
+ *  {@literal @}Override
+ *   public void start(MetricsSession session) {
+ *     this.session = session;
+ *     registry = ... // configure your meter registry and start publishing
+ *
+ *     // add your registry as a sub-registry to the cache's composite registry
+ *     session.addSubregistry(registry);
+ *   }
+ *
+ *  {@literal @}Override
+ *   public void stop() {
+ *     ...
+ *     // clean up any resources used by your meter registry
+ *     ...
+ *
+ *     session.removeSubregistry(registry);
+ *   }
+ * }
+ * </pre>
+ *
+ * <p>
+ * To make your service available for loading, add the following provider-configuration file in the
+ * resource directory of your application Jar:
+ *
+ * <p>
+ * {@code META-INF/services/org.apache.geode.metrics.MetricsPublishingService}
+ *
+ * <p>
+ * Add a line inside the file indicating the fully qualified class name of your implementation:
+ *
+ * <p>
+ * {@code com.application.MyMetricsPublishingService}
+ *
+ * @see <a href="https://micrometer.io/docs">Micrometer Documentation</a>
+ * @see <a href="https://micrometer.io/docs/concepts">Micrometer Concepts</a>
+ */
+@Experimental("Micrometer metrics is a new addition to Geode and the API may change")
+public interface MetricsPublishingService {
+
+  /**
+   * Invoked when a metrics session is started during cache initialization. The implementation can
+   * use the metrics session to add sub-registries to the cache's composite registry for publishing
+   * metrics to external monitoring systems.
+   *
+   * @param session the metrics session that is used to connect sub-registries
+   */
+  void start(MetricsSession session);
+
+  /**
+   * Invoked when a metrics session is stopped during cache close.
+   */
+  void stop();
+}
diff --git a/geode-core/src/main/java/org/apache/geode/metrics/MetricsSession.java b/geode-core/src/main/java/org/apache/geode/metrics/MetricsSession.java
new file mode 100644
index 0000000..dcad5f4
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/metrics/MetricsSession.java
@@ -0,0 +1,44 @@
+/*
+ * 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.geode.metrics;
+
+import io.micrometer.core.instrument.MeterRegistry;
+
+import org.apache.geode.annotations.Experimental;
+
+/**
+ * Provides the ability to add and remove a meter registry for publishing cache metrics to external
+ * monitoring systems.
+ */
+@Experimental("Micrometer metrics is a new addition to Geode and the API may change")
+public interface MetricsSession {
+  /**
+   * Adds the given registry to the cache's composite registry.
+   *
+   * @param subregistry the registry to add
+   */
+  void addSubregistry(MeterRegistry subregistry);
+
+  /**
+   * Removes the given registry from the cache's composite registry.
+   *
+   * <p>
+   * <strong>Caution:</strong> This method deletes from the sub-registry each meter that corresponds
+   * to a meter in the cache's composite registry.
+   *
+   * @param subregistry the registry to remove
+   */
+  void removeSubregistry(MeterRegistry subregistry);
+}
diff --git a/geode-core/src/main/java/org/apache/geode/metrics/package-info.java b/geode-core/src/main/java/org/apache/geode/metrics/package-info.java
new file mode 100644
index 0000000..63bfcc0
--- /dev/null
+++ b/geode-core/src/main/java/org/apache/geode/metrics/package-info.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+/**
+ * Geode uses Micrometer for its metrics. Each Geode cache maintains a registry of all meters that
+ * instrument Geode code. Users can add sub-registries for various purposes. A key purpose for a
+ * sub-registry is to publish a cache's metrics to an external monitoring system.
+ *
+ * <p>
+ * The {@link org.apache.geode.metrics.MetricsSession MetricsSession} provides the ability to add
+ * and remove a meter registry for publishing cache metrics to external monitoring systems.
+ *
+ * <p>
+ * The {@link org.apache.geode.metrics.MetricsPublishingService MetricsPublishingService} defines
+ * a service that users can implement to be notified when a metrics session starts or stops. When
+ * a session starts, the service can add a meter registry to the cache's composite registry. When
+ * the session stops, the service can clean up any resources and remove its meter registry from the
+ * cache's composite registry.
+ *
+ * @see <a href="https://micrometer.io/docs">Micrometer Documentation</a>
+ * @see <a href="https://micrometer.io/docs/concepts">Micrometer Concepts</a>
+ */
+@Experimental("Micrometer metrics is a new addition to Geode and the API may change")
+package org.apache.geode.metrics;
+
+import org.apache.geode.annotations.Experimental;
diff --git a/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalDistributedSystemTest.java b/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalDistributedSystemStatisticsManagerTest.java
similarity index 99%
rename from geode-core/src/test/java/org/apache/geode/distributed/internal/InternalDistributedSystemTest.java
rename to geode-core/src/test/java/org/apache/geode/distributed/internal/InternalDistributedSystemStatisticsManagerTest.java
index 3c8ad85..71f1848 100644
--- a/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalDistributedSystemTest.java
+++ b/geode-core/src/test/java/org/apache/geode/distributed/internal/InternalDistributedSystemStatisticsManagerTest.java
@@ -42,7 +42,7 @@ import org.apache.geode.internal.statistics.StatisticsManagerFactory;
 /**
  * Unit tests for {@link InternalDistributedSystem}.
  */
-public class InternalDistributedSystemTest {
+public class InternalDistributedSystemStatisticsManagerTest {
 
   private static final String STATISTIC_NAME = "statistic-name";
   private static final String STATISTIC_DESCRIPTION = "statistic-description";
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/GemFireCacheImplTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/GemFireCacheImplTest.java
index b278f08..5f97af0 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/cache/GemFireCacheImplTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/GemFireCacheImplTest.java
@@ -30,6 +30,7 @@ import java.util.Properties;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ThreadPoolExecutor;
 
+import io.micrometer.core.instrument.MeterRegistry;
 import org.junit.After;
 import org.junit.Test;
 
@@ -54,6 +55,8 @@ public class GemFireCacheImplTest {
 
   @After
   public void tearDown() {
+    InternalDistributedSystem.ALLOW_MULTIPLE_SYSTEMS = false;
+
     if (gemFireCacheImpl != null) {
       gemFireCacheImpl.close();
     }
@@ -338,6 +341,13 @@ public class GemFireCacheImplTest {
     InternalDistributedSystem.ALLOW_MULTIPLE_SYSTEMS = oldValue;
   }
 
+  @Test
+  public void getMeterRegistryReturnsTheMeterRegistry() {
+    gemFireCacheImpl = createGemFireCacheImpl();
+
+    assertThat(gemFireCacheImpl.getMeterRegistry()).isInstanceOf(MeterRegistry.class);
+  }
+
   private static GemFireCacheImpl createGemFireCacheImpl() {
     return (GemFireCacheImpl) new InternalCacheBuilder().create(Fakes.distributedSystem());
   }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderAllowsMultipleSystemsTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderAllowsMultipleSystemsTest.java
index baba452..ed0ac66 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderAllowsMultipleSystemsTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderAllowsMultipleSystemsTest.java
@@ -26,20 +26,26 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
 
 import java.util.Properties;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
 import org.assertj.core.api.ThrowableAssert;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.Mock;
 
 import org.apache.geode.cache.CacheExistsException;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
+import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
 import org.apache.geode.internal.cache.InternalCacheBuilder.InternalCacheConstructor;
 import org.apache.geode.internal.cache.InternalCacheBuilder.InternalDistributedSystemConstructor;
+import org.apache.geode.internal.metrics.CompositeMeterRegistryFactory;
 
 /**
  * Unit tests for {@link InternalCacheBuilder} when
@@ -49,6 +55,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
   private static final int ANY_SYSTEM_ID = 12;
   private static final String ANY_MEMBER_NAME = "a-member-name";
+  private static final String ANY_HOST_NAME = "a-host-name";
 
   private static final Supplier<InternalDistributedSystem> THROWING_SYSTEM_SUPPLIER =
       () -> {
@@ -64,12 +71,20 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
         throw new AssertionError("throwing system constructor");
       };
   private static final InternalCacheConstructor THROWING_CACHE_CONSTRUCTOR =
-      (a, b, c, d, e, f) -> {
+      (a, b, c, d, e, f, g) -> {
         throw new AssertionError("throwing cache constructor");
       };
 
+  @Mock
+  private CompositeMeterRegistryFactory compositeMeterRegistryFactory;
+
+  @Mock
+  private Consumer<CompositeMeterRegistry> metricsSessionInitializer;
+
   @Before
   public void setUp() {
+    initMocks(this);
+
     InternalDistributedSystem.ALLOW_MULTIPLE_SYSTEMS = true;
   }
 
@@ -82,6 +97,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
   public void create_throwsNullPointerException_ifConfigPropertiesIsNull() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         null, new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, constructorOf(constructedSystem()),
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache()));
 
@@ -95,6 +111,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
   public void create_throwsNullPointerException_andCacheConfigIsNull() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), null,
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, constructorOf(constructedSystem()),
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache()));
 
@@ -113,6 +130,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         configProperties, new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, systemConstructor,
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -127,6 +145,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, constructorOf(constructedSystem()),
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -142,6 +161,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, constructorOf(constructedSystem),
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -158,13 +178,14 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, constructorOf(constructedSystem),
         THROWING_CACHE_SUPPLIER, cacheConstructor);
 
     internalCacheBuilder.create();
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(constructedSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
 
@@ -172,6 +193,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
   public void createWithSystem_throwsNullPointerException_ifSystemIsNull() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -186,6 +208,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -202,6 +225,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -219,6 +243,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, cacheConstructor);
 
@@ -226,7 +251,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
         .create(givenSystem);
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(givenSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
@@ -236,6 +261,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -252,6 +278,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, constructorOf(constructedCache));
 
@@ -269,6 +296,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, cacheConstructor);
 
@@ -276,7 +304,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
         .create(givenSystem);
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(givenSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
@@ -285,6 +313,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -301,6 +330,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -319,6 +349,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), throwingCacheConfig(thrownByCacheConfig),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -335,6 +366,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), throwingCacheConfig(new IllegalStateException("incompatible")),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -352,6 +384,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -369,6 +402,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -380,25 +414,29 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
   }
 
   private InternalDistributedSystem constructedSystem() {
-    return systemWith("constructedSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME);
+    return systemWith("constructedSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME, ANY_HOST_NAME);
   }
 
   private InternalDistributedSystem givenSystem() {
-    return systemWith("givenSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME);
+    return systemWith("givenSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME, ANY_HOST_NAME);
   }
 
-  private InternalDistributedSystem systemWith(String mockName, int systemId, String memberName) {
+  private InternalDistributedSystem systemWith(String mockName, int systemId, String memberName,
+      String hostName) {
     InternalDistributedSystem system = mock(InternalDistributedSystem.class, mockName);
     DistributionConfig distributionConfig = mock(DistributionConfig.class);
+    InternalDistributedMember distributedMember = mock(InternalDistributedMember.class);
     when(distributionConfig.getDistributedSystemId()).thenReturn(systemId);
+    when(distributedMember.getHost()).thenReturn(hostName);
     when(system.getConfig()).thenReturn(distributionConfig);
+    when(system.getDistributedMember()).thenReturn(distributedMember);
     when(system.getName()).thenReturn(memberName);
     return system;
   }
 
   private InternalDistributedSystem givenSystemWithCache(CacheState state) {
     InternalDistributedSystem system =
-        systemWith("givenSystemWithCache", ANY_SYSTEM_ID, ANY_MEMBER_NAME);
+        systemWith("givenSystemWithCache", ANY_SYSTEM_ID, ANY_MEMBER_NAME, ANY_HOST_NAME);
     systemCache(system, state);
     return system;
   }
@@ -433,7 +471,7 @@ public class InternalCacheBuilderAllowsMultipleSystemsTest {
   private static InternalCacheConstructor constructorOf(InternalCache constructedCache) {
     InternalCacheConstructor constructor =
         mock(InternalCacheConstructor.class, "internal cache constructor");
-    when(constructor.construct(anyBoolean(), any(), any(), any(), anyBoolean(), any()))
+    when(constructor.construct(anyBoolean(), any(), any(), any(), anyBoolean(), any(), any()))
         .thenReturn(constructedCache);
     return constructor;
   }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderTest.java
index 2814376..e30a1a7 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/cache/InternalCacheBuilderTest.java
@@ -19,6 +19,8 @@ import static org.apache.geode.internal.cache.InternalCacheBuilderTest.CacheStat
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.catchThrowable;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -30,8 +32,10 @@ import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import java.util.Properties;
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
 import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
 import org.junit.Before;
 import org.junit.Test;
@@ -40,8 +44,10 @@ import org.mockito.Mock;
 import org.apache.geode.cache.CacheExistsException;
 import org.apache.geode.distributed.internal.DistributionConfig;
 import org.apache.geode.distributed.internal.InternalDistributedSystem;
+import org.apache.geode.distributed.internal.membership.InternalDistributedMember;
 import org.apache.geode.internal.cache.InternalCacheBuilder.InternalCacheConstructor;
 import org.apache.geode.internal.cache.InternalCacheBuilder.InternalDistributedSystemConstructor;
+import org.apache.geode.internal.metrics.CompositeMeterRegistryFactory;
 
 /**
  * Unit tests for {@link InternalCacheBuilder}.
@@ -50,6 +56,7 @@ public class InternalCacheBuilderTest {
 
   private static final int ANY_SYSTEM_ID = 12;
   private static final String ANY_MEMBER_NAME = "a-member-name";
+  private static final String ANY_HOST_NAME = "a-host-name";
 
   private static final Supplier<InternalDistributedSystem> THROWING_SYSTEM_SUPPLIER =
       () -> {
@@ -65,7 +72,7 @@ public class InternalCacheBuilderTest {
         throw new AssertionError("throwing system constructor");
       };
   private static final InternalCacheConstructor THROWING_CACHE_CONSTRUCTOR =
-      (a, b, c, d, e, f) -> {
+      (a, b, c, d, e, f, g) -> {
         throw new AssertionError("throwing cache constructor");
       };
 
@@ -75,6 +82,12 @@ public class InternalCacheBuilderTest {
   @Mock
   private Supplier<InternalCache> nullSingletonCacheSupplier;
 
+  @Mock
+  private CompositeMeterRegistryFactory compositeMeterRegistryFactory;
+
+  @Mock
+  private Consumer<CompositeMeterRegistry> metricsSessionInitializer;
+
   @Before
   public void setUp() {
     initMocks(this);
@@ -89,6 +102,7 @@ public class InternalCacheBuilderTest {
   public void create_throwsNullPointerException_ifConfigPropertiesIsNull() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         null, new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         nullSingletonSystemSupplier, constructorOf(constructedSystem()),
         nullSingletonCacheSupplier, constructorOf(constructedCache()));
 
@@ -102,6 +116,7 @@ public class InternalCacheBuilderTest {
   public void create_throwsNullPointerException_andCacheConfigIsNull() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), null,
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         nullSingletonSystemSupplier, constructorOf(constructedSystem()),
         nullSingletonCacheSupplier, constructorOf(constructedCache()));
 
@@ -120,6 +135,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         configProperties, new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         nullSingletonSystemSupplier, systemConstructor,
         nullSingletonCacheSupplier, constructorOf(constructedCache));
 
@@ -130,11 +146,61 @@ public class InternalCacheBuilderTest {
   }
 
   @Test
+  public void create_constructsCompositeMeterRegistry_ifNoCacheExists() {
+    int theSystemId = 21;
+    String theMemberName = "theMemberName";
+    String theHostName = "theHostName";
+    InternalDistributedSystem theConstructedSystem =
+        systemWith("theConstructedSystem", theSystemId, theMemberName, theHostName);
+    InternalCache constructedCache = constructedCache();
+
+    CompositeMeterRegistryFactory theCompositeMeterRegistryFactory =
+        mock(CompositeMeterRegistryFactory.class);
+
+    InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
+        new Properties(), new CacheConfig(),
+        theCompositeMeterRegistryFactory, metricsSessionInitializer,
+        nullSingletonSystemSupplier, constructorOf(theConstructedSystem),
+        nullSingletonCacheSupplier, constructorOf(constructedCache));
+
+    internalCacheBuilder
+        .create();
+
+    verify(theCompositeMeterRegistryFactory)
+        .create(eq(theSystemId), eq(theMemberName), eq(theHostName));
+  }
+
+  @Test
+  public void create_setsConstructedCompositeMeterRegistry_onTheConstructedCache_ifNoCacheExists() {
+    InternalCache constructedCache = constructedCache();
+    InternalCacheConstructor cacheConstructor = constructorOf(constructedCache);
+
+    CompositeMeterRegistry theCompositeMeterRegistry = new CompositeMeterRegistry();
+    CompositeMeterRegistryFactory theCompositeMeterRegistryFactory =
+        mock(CompositeMeterRegistryFactory.class);
+    when(theCompositeMeterRegistryFactory.create(anyInt(), any(), any()))
+        .thenReturn(theCompositeMeterRegistry);
+
+    InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
+        new Properties(), new CacheConfig(),
+        theCompositeMeterRegistryFactory, metricsSessionInitializer,
+        nullSingletonSystemSupplier, constructorOf(constructedSystem()),
+        nullSingletonCacheSupplier, cacheConstructor);
+
+    internalCacheBuilder
+        .create();
+
+    verify(cacheConstructor).construct(anyBoolean(), any(), any(), any(),
+        anyBoolean(), any(), same(theCompositeMeterRegistry));
+  }
+
+  @Test
   public void create_returnsConstructedCache_ifNoSystemExists() {
     InternalCache constructedCache = constructedCache();
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         nullSingletonSystemSupplier, constructorOf(constructedSystem()),
         nullSingletonCacheSupplier, constructorOf(constructedCache));
 
@@ -151,6 +217,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         nullSingletonSystemSupplier, constructorOf(constructedSystem),
         nullSingletonCacheSupplier, constructorOf(constructedCache));
 
@@ -169,6 +236,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         nullSingletonSystemSupplier, constructorOf(constructedSystem),
         nullSingletonCacheSupplier, cacheConstructor);
 
@@ -176,7 +244,7 @@ public class InternalCacheBuilderTest {
         .create();
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(constructedSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
@@ -185,6 +253,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem()), THROWING_SYSTEM_CONSTRUCTOR,
         nullSingletonCacheSupplier, constructorOf(constructedCache));
 
@@ -203,6 +272,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem), THROWING_SYSTEM_CONSTRUCTOR,
         nullSingletonCacheSupplier, cacheConstructor);
 
@@ -221,6 +291,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem), THROWING_SYSTEM_CONSTRUCTOR,
         nullSingletonCacheSupplier, cacheConstructor);
 
@@ -228,7 +299,7 @@ public class InternalCacheBuilderTest {
         .create();
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(singletonSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
@@ -237,6 +308,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem()), THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(CLOSED)), constructorOf(constructedCache));
 
@@ -253,6 +325,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem), THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(CLOSED)), constructorOf(constructedCache));
 
@@ -270,6 +343,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem), THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(CLOSED)), cacheConstructor);
 
@@ -277,13 +351,14 @@ public class InternalCacheBuilderTest {
         .create();
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(singletonSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
   public void create_throwsCacheExistsException_ifSingletonSystemExists_andSingletonCacheIsOpen_butExistingIsNotOk() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem()), THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(OPEN)), THROWING_CACHE_CONSTRUCTOR);
 
@@ -300,6 +375,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), throwingCacheConfig(thrownByCacheConfig),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem()), THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(OPEN)), THROWING_CACHE_CONSTRUCTOR);
 
@@ -316,6 +392,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         supplierOf(singletonSystem()), THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache), THROWING_CACHE_CONSTRUCTOR);
 
@@ -329,6 +406,7 @@ public class InternalCacheBuilderTest {
   public void createWithSystem_throwsNullPointerException_ifSystemIsNull() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         THROWING_CACHE_SUPPLIER, THROWING_CACHE_CONSTRUCTOR);
 
@@ -344,6 +422,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         nullSingletonCacheSupplier, constructorOf(constructedCache));
 
@@ -360,6 +439,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         nullSingletonCacheSupplier, constructorOf(constructedCache));
 
@@ -377,6 +457,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         nullSingletonCacheSupplier, cacheConstructor);
 
@@ -384,7 +465,7 @@ public class InternalCacheBuilderTest {
         .create(givenSystem);
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(givenSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
@@ -393,6 +474,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(CLOSED)), constructorOf(constructedCache));
 
@@ -409,6 +491,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(CLOSED)), constructorOf(constructedCache));
 
@@ -426,6 +509,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(CLOSED)), cacheConstructor);
 
@@ -433,13 +517,14 @@ public class InternalCacheBuilderTest {
         .create(givenSystem);
 
     verify(cacheConstructor).construct(anyBoolean(), any(), same(givenSystem), any(),
-        anyBoolean(), any());
+        anyBoolean(), any(), any());
   }
 
   @Test
   public void createWithSystem_throwsCacheExistsException_ifSingletonCacheIsOpen_butExistingIsNotOk() {
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(OPEN)), THROWING_CACHE_CONSTRUCTOR);
 
@@ -456,6 +541,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(OPEN)), THROWING_CACHE_CONSTRUCTOR);
 
@@ -472,6 +558,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), throwingCacheConfig(thrownByCacheConfig),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(OPEN)), THROWING_CACHE_CONSTRUCTOR);
 
@@ -488,6 +575,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), throwingCacheConfig(new IllegalStateException("incompatible")),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache(OPEN)), THROWING_CACHE_CONSTRUCTOR);
 
@@ -504,6 +592,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache), THROWING_CACHE_CONSTRUCTOR);
 
@@ -521,6 +610,7 @@ public class InternalCacheBuilderTest {
 
     InternalCacheBuilder internalCacheBuilder = new InternalCacheBuilder(
         new Properties(), new CacheConfig(),
+        compositeMeterRegistryFactory, metricsSessionInitializer,
         THROWING_SYSTEM_SUPPLIER, THROWING_SYSTEM_CONSTRUCTOR,
         supplierOf(singletonCache), THROWING_CACHE_CONSTRUCTOR);
 
@@ -532,22 +622,26 @@ public class InternalCacheBuilderTest {
   }
 
   private InternalDistributedSystem constructedSystem() {
-    return systemWith("constructedSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME);
+    return systemWith("constructedSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME, ANY_HOST_NAME);
   }
 
   private InternalDistributedSystem givenSystem() {
-    return systemWith("givenSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME);
+    return systemWith("givenSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME, ANY_HOST_NAME);
   }
 
   private InternalDistributedSystem singletonSystem() {
-    return systemWith("singletonSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME);
+    return systemWith("singletonSystem", ANY_SYSTEM_ID, ANY_MEMBER_NAME, ANY_HOST_NAME);
   }
 
-  private InternalDistributedSystem systemWith(String mockName, int systemId, String memberName) {
+  private InternalDistributedSystem systemWith(String mockName, int systemId, String memberName,
+      String hostName) {
     InternalDistributedSystem system = mock(InternalDistributedSystem.class, mockName);
     DistributionConfig distributionConfig = mock(DistributionConfig.class);
+    InternalDistributedMember distributedMember = mock(InternalDistributedMember.class);
     when(distributionConfig.getDistributedSystemId()).thenReturn(systemId);
+    when(distributedMember.getHost()).thenReturn(hostName);
     when(system.getConfig()).thenReturn(distributionConfig);
+    when(system.getDistributedMember()).thenReturn(distributedMember);
     when(system.getName()).thenReturn(memberName);
     return system;
   }
@@ -579,7 +673,7 @@ public class InternalCacheBuilderTest {
   private static InternalCacheConstructor constructorOf(InternalCache constructedCache) {
     InternalCacheConstructor constructor =
         mock(InternalCacheConstructor.class, "internal cache constructor");
-    when(constructor.construct(anyBoolean(), any(), any(), any(), anyBoolean(), any()))
+    when(constructor.construct(anyBoolean(), any(), any(), any(), anyBoolean(), any(), any()))
         .thenReturn(constructedCache);
     return constructor;
   }
diff --git a/geode-core/src/test/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSessionBuilderTest.java b/geode-core/src/test/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSessionBuilderTest.java
new file mode 100644
index 0000000..d8f5dbb
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSessionBuilderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.geode.internal.metrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.geode.internal.metrics.CacheLifecycleMetricsSession.Builder;
+import org.apache.geode.internal.metrics.CacheLifecycleMetricsSession.CacheLifecycle;
+import org.apache.geode.metrics.MetricsPublishingService;
+
+public class CacheLifecycleMetricsSessionBuilderTest {
+
+  private CompositeMeterRegistry registry;
+  private Builder builder;
+
+  @Before
+  public void setUp() {
+    CacheLifecycle cacheLifecycle = mock(CacheLifecycle.class);
+    registry = new CompositeMeterRegistry();
+    builder = CacheLifecycleMetricsSession
+        .builder()
+        .setCacheLifecycle(cacheLifecycle);
+  }
+
+  @Test
+  public void buildsCacheMetricsSession() {
+    assertThat(builder.build(registry)).isInstanceOf(CacheLifecycleMetricsSession.class);
+  }
+
+  @Test
+  public void buildsCacheMetricsSession_withGivenMeterRegistry() {
+    CompositeMeterRegistry givenRegistry = new CompositeMeterRegistry();
+
+    CacheLifecycleMetricsSession session = builder
+        .build(givenRegistry);
+
+    assertThat(session.meterRegistry()).isSameAs(givenRegistry);
+  }
+
+  @Test
+  public void addsSessionAsCacheLifecycleListener() {
+    CacheLifecycle theCacheLifecycle = mock(CacheLifecycle.class);
+
+    CacheLifecycleMetricsSession session = builder
+        .setCacheLifecycle(theCacheLifecycle)
+        .build(registry);
+
+    verify(theCacheLifecycle).addListener(same(session));
+  }
+
+  @Test
+  public void loadsMetricsPublishingServices() {
+    CollectingServiceLoader theMetricsPublishingServicesLoader =
+        mock(CollectingServiceLoader.class);
+
+    builder
+        .setServiceLoader(theMetricsPublishingServicesLoader)
+        .build(registry);
+
+    verify(theMetricsPublishingServicesLoader).loadServices(MetricsPublishingService.class);
+  }
+
+  @Test
+  public void buildsCacheMetricsSession_withMetricsPublishingServices() {
+    CollectingServiceLoader theMetricsPublishingServicesLoader =
+        mock(CollectingServiceLoader.class);
+    Collection<MetricsPublishingService> theMetricsPublishingServices = Arrays.asList(
+        metricsPublishingService("metricsPublishingService1"),
+        metricsPublishingService("metricsPublishingService2"),
+        metricsPublishingService("metricsPublishingService3"));
+    when(theMetricsPublishingServicesLoader.loadServices(MetricsPublishingService.class))
+        .thenReturn(theMetricsPublishingServices);
+
+    CacheLifecycleMetricsSession session = builder
+        .setServiceLoader(theMetricsPublishingServicesLoader)
+        .build(registry);
+
+    assertThat(session.metricsPublishingServices())
+        .hasSameElementsAs(theMetricsPublishingServices);
+  }
+
+  private MetricsPublishingService metricsPublishingService(String name) {
+    return mock(MetricsPublishingService.class, name);
+  }
+}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSessionTest.java b/geode-core/src/test/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSessionTest.java
new file mode 100644
index 0000000..4a9af35
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/metrics/CacheLifecycleMetricsSessionTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.geode.internal.metrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import org.junit.After;
+import org.junit.Test;
+
+import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.internal.cache.InternalCache;
+import org.apache.geode.internal.metrics.CacheLifecycleMetricsSession.CacheLifecycle;
+import org.apache.geode.metrics.MetricsPublishingService;
+
+public class CacheLifecycleMetricsSessionTest {
+
+  private final CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry();
+
+  private CacheLifecycleMetricsSession metricsSession;
+
+  @After
+  public void tearDown() {
+    if (metricsSession != null) {
+      GemFireCacheImpl.removeCacheLifecycleListener(metricsSession);
+    }
+  }
+
+  @Test
+  public void startsWithNoDownstreamRegistries() {
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        Collections.emptyList());
+
+    Set<MeterRegistry> downstreamRegistries = compositeRegistry.getRegistries();
+
+    assertThat(downstreamRegistries)
+        .isEmpty();
+  }
+
+  @Test
+  public void remembersConnectedDownstreamRegistries() {
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        Collections.emptyList());
+
+    MeterRegistry downstreamRegistry = new SimpleMeterRegistry();
+
+    metricsSession.addSubregistry(downstreamRegistry);
+
+    assertThat(compositeRegistry.getRegistries())
+        .contains(downstreamRegistry);
+  }
+
+  @Test
+  public void forgetsDisconnectedDownstreamRegistries() {
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        Collections.emptyList());
+
+    MeterRegistry downstreamRegistry = new SimpleMeterRegistry();
+    metricsSession.addSubregistry(downstreamRegistry);
+
+    metricsSession.removeSubregistry(downstreamRegistry);
+
+    assertThat(compositeRegistry.getRegistries())
+        .doesNotContain(downstreamRegistry);
+  }
+
+  @Test
+  public void connectsExistingMetersToNewDownstreamRegistries() {
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        Collections.emptyList());
+
+    String counterName = "the.counter";
+    Counter primaryCounter = compositeRegistry.counter(counterName);
+
+    double amountIncrementedBeforeConnectingDownstreamRegistry = 3.0;
+    primaryCounter.increment(amountIncrementedBeforeConnectingDownstreamRegistry);
+
+    MeterRegistry downstreamRegistry = new SimpleMeterRegistry();
+    metricsSession.addSubregistry(downstreamRegistry);
+
+    Counter downstreamCounter = downstreamRegistry.find(counterName).counter();
+    assertThat(downstreamCounter)
+        .as("downstream counter after connecting, before incrementing")
+        .isNotNull();
+
+    // Note that the newly-created downstream counter starts at zero, ignoring
+    // any increments that happened before the downstream registry was added.
+    assertThat(downstreamCounter.count())
+        .as("downstream counter value after connecting, before incrementing")
+        .isNotEqualTo(amountIncrementedBeforeConnectingDownstreamRegistry)
+        .isEqualTo(0);
+
+    double amountIncrementedAfterConnectingDownstreamRegistry = 42.0;
+    primaryCounter.increment(amountIncrementedAfterConnectingDownstreamRegistry);
+
+    assertThat(downstreamCounter.count())
+        .as("downstream counter value after incrementing")
+        .isEqualTo(amountIncrementedAfterConnectingDownstreamRegistry);
+  }
+
+  @Test
+  public void connectsNewMetersToExistingDownstreamRegistries() {
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        Collections.emptyList());
+
+    MeterRegistry downstreamRegistry = new SimpleMeterRegistry();
+    metricsSession.addSubregistry(downstreamRegistry);
+
+    String counterName = "the.counter";
+    Counter newCounter = compositeRegistry.counter(counterName);
+
+    Counter downstreamCounter = downstreamRegistry.find(counterName).counter();
+    assertThat(downstreamCounter)
+        .as("downstream counter before incrementing")
+        .isNotNull();
+
+    assertThat(downstreamCounter.count())
+        .as("downstream counter value before incrementing")
+        .isEqualTo(newCounter.count())
+        .isEqualTo(0);
+
+    double amountIncrementedAfterConnectingDownstreamRegistry = 93.0;
+    newCounter.increment(amountIncrementedAfterConnectingDownstreamRegistry);
+
+    assertThat(downstreamCounter.count())
+        .as("downstream counter value after incrementing")
+        .isEqualTo(newCounter.count());
+  }
+
+  @Test
+  public void cacheCreatedStartsEachMetricsPublishingService() {
+    List<MetricsPublishingService> metricsPublishingServices = Arrays.asList(
+        metricsPublishingService("metricsPublishingService1"),
+        metricsPublishingService("metricsPublishingService2"),
+        metricsPublishingService("metricsPublishingService3"));
+
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        metricsPublishingServices);
+
+    metricsSession.cacheCreated(mock(InternalCache.class));
+
+    for (MetricsPublishingService metricsPublishingService : metricsPublishingServices) {
+      verify(metricsPublishingService).start(same(metricsSession));
+    }
+  }
+
+  @Test
+  public void cacheClosedStopsEachMetricsPublishingService() {
+    List<MetricsPublishingService> metricsPublishingServices = Arrays.asList(
+        metricsPublishingService("metricsPublishingService1"),
+        metricsPublishingService("metricsPublishingService2"),
+        metricsPublishingService("metricsPublishingService3"));
+
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        metricsPublishingServices);
+
+    metricsSession.cacheClosed(mock(InternalCache.class));
+
+    for (MetricsPublishingService metricsPublishingService : metricsPublishingServices) {
+      verify(metricsPublishingService).stop();
+    }
+  }
+
+  @Test
+  public void cacheClosedDisconnectsAllDownstreamRegistries() {
+    metricsSession = new CacheLifecycleMetricsSession(mock(CacheLifecycle.class), compositeRegistry,
+        Collections.emptyList());
+
+    MeterRegistry downstreamMeterRegistry1 = new SimpleMeterRegistry();
+    MeterRegistry downstreamMeterRegistry2 = new SimpleMeterRegistry();
+    MeterRegistry downstreamMeterRegistry3 = new SimpleMeterRegistry();
+
+    metricsSession.addSubregistry(downstreamMeterRegistry1);
+    metricsSession.addSubregistry(downstreamMeterRegistry2);
+    metricsSession.addSubregistry(downstreamMeterRegistry3);
+
+    metricsSession.cacheClosed(mock(InternalCache.class));
+
+    assertThat(compositeRegistry.getRegistries()).isEmpty();
+  }
+
+  @Test
+  public void cacheClosedRemovesSessionAsCacheLifecycleListener() {
+    CacheLifecycle theCacheLifecycle = mock(CacheLifecycle.class);
+    metricsSession =
+        new CacheLifecycleMetricsSession(theCacheLifecycle, compositeRegistry,
+            Collections.emptyList());
+
+    metricsSession.cacheClosed(mock(InternalCache.class));
+
+    verify(theCacheLifecycle).removeListener(same(metricsSession));
+  }
+
+  private MetricsPublishingService metricsPublishingService(String name) {
+    return mock(MetricsPublishingService.class, name);
+  }
+}
diff --git a/geode-core/src/test/java/org/apache/geode/internal/metrics/CompositeMeterRegistryFactoryTest.java b/geode-core/src/test/java/org/apache/geode/internal/metrics/CompositeMeterRegistryFactoryTest.java
new file mode 100644
index 0000000..0a31db9
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/metrics/CompositeMeterRegistryFactoryTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.geode.internal.metrics;
+
+import static org.apache.geode.internal.metrics.CompositeMeterRegistryFactory.CLUSTER_ID_TAG;
+import static org.apache.geode.internal.metrics.CompositeMeterRegistryFactory.HOST_NAME_TAG;
+import static org.apache.geode.internal.metrics.CompositeMeterRegistryFactory.MEMBER_NAME_TAG;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.micrometer.core.instrument.Meter;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
+import org.junit.Test;
+
+public class CompositeMeterRegistryFactoryTest {
+
+  private static final int CLUSTER_ID = 42;
+  private static final String MEMBER_NAME = "member-name";
+  private static final String HOST_NAME = "host-name";
+
+  @Test
+  public void createsCompositeMeterRegistry() {
+    CompositeMeterRegistryFactory factory = new CompositeMeterRegistryFactory() {};
+
+    assertThat(factory.create(CLUSTER_ID, MEMBER_NAME, HOST_NAME))
+        .isInstanceOf(CompositeMeterRegistry.class);
+  }
+
+  @Test
+  public void addsMemberNameCommonTag() {
+    CompositeMeterRegistryFactory factory = new CompositeMeterRegistryFactory() {};
+    String theMemberName = "the-member-name";
+
+    CompositeMeterRegistry registry = factory.create(CLUSTER_ID, theMemberName, HOST_NAME);
+
+    Meter meter = registry.counter("my.meter");
+
+    assertThat(meter.getId().getTags())
+        .contains(Tag.of(MEMBER_NAME_TAG, theMemberName));
+  }
+
+  @Test
+  public void addsClusterIdCommonTag() {
+    CompositeMeterRegistryFactory factory = new CompositeMeterRegistryFactory() {};
+    int theSystemId = 21;
+
+    CompositeMeterRegistry registry = factory.create(theSystemId, MEMBER_NAME, HOST_NAME);
+
+    Meter meter = registry.counter("my.meter");
+
+    assertThat(meter.getId().getTags())
+        .contains(Tag.of(CLUSTER_ID_TAG, String.valueOf(theSystemId)));
+  }
+
+  @Test
+  public void addsHostNameCommonTag() {
+    CompositeMeterRegistryFactory factory = new CompositeMeterRegistryFactory() {};
+    String theHostName = "the-host-name";
+
+    CompositeMeterRegistry registry = factory.create(CLUSTER_ID, MEMBER_NAME, theHostName);
+
+    Meter meter = registry.counter("my.meter");
+
+    assertThat(meter.getId().getTags())
+        .contains(Tag.of(HOST_NAME_TAG, theHostName));
+  }
+}
diff --git a/geode-core/src/test/resources/expected-pom.xml b/geode-core/src/test/resources/expected-pom.xml
index 3bbcc55..5df710a 100644
--- a/geode-core/src/test/resources/expected-pom.xml
+++ b/geode-core/src/test/resources/expected-pom.xml
@@ -142,6 +142,11 @@
       <optional>true</optional>
     </dependency>
     <dependency>
+      <groupId>io.micrometer</groupId>
+      <artifactId>micrometer-core</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
       <groupId>io.netty</groupId>
       <artifactId>netty-all</artifactId>
       <scope>compile</scope>


Mime
View raw message