Intro

Managed objects like caches, cache managers and stores are registered into an org.ehcache.management.ManagementRegistryService instance.

A ManagementRegistry implementation has to understand the registered object and provide management and monitoring capabilities for them, including the capabilities' context.

Given a capability and a context, statistics can be collected or calls can be made.

The current ManagementRegistry implementation provides minimal support for Ehcache instances, providing a minimal set of statistics and actions via a couple of capabilities.

Making use of the ManagementRegistry

By default, a ManagementRegistry is automatically discovered and enabled, but can only be accessed by Ehcache internal services. If you wish to make use of it, you should create your own instance and pass it to the cache manager builder as a service:

CacheManager cacheManager = null;
try {
  DefaultManagementRegistryConfiguration registryConfiguration = new DefaultManagementRegistryConfiguration().setCacheManagerAlias("myCacheManager1"); (1)
  registryConfiguration.addConfiguration(EHCACHE_STATS_CONFIG);
  ManagementRegistryService managementRegistry = new DefaultManagementRegistryService(registryConfiguration); (2)

  CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
    ResourcePoolsBuilder.newResourcePoolsBuilder().heap(1, MemoryUnit.MB))
    .build();

  cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache("myCache", cacheConfiguration)
      .using(managementRegistry) (3)
      .build(true);


  Cache<Long, String> aCache = cacheManager.getCache("myCache", Long.class, String.class);
  aCache.put(1L, "one");
  aCache.put(0L, "zero");
  aCache.get(1L); (4)
  aCache.get(0L); (4)
  aCache.get(0L);
  aCache.get(0L);

  Context context = StatsUtil.createContext(managementRegistry); (5)

  StatisticQuery query = managementRegistry.withCapability("StatisticsCapability") (6)
      .queryStatistic("Cache:HitCount")
      .on(context)
      .build();

  long onHeapHitCount = 0;
  // it could be several seconds before the sampled stats could become available
  // let's try until we find the correct value : 4
  do {
    ResultSet<ContextualStatistics> counters = query.execute();

    ContextualStatistics statisticsContext = counters.getResult(context);

    Assert.assertThat(counters.size(), Matchers.is(1));

    CounterHistory onHeapStore_Hit_Count = statisticsContext.getStatistic(CounterHistory.class, "Cache:HitCount");

    // hit count is a sampled stat, for example its values could be [0,0,3,4].
    // In the present case, only the last value is important to us , the cache was eventually hit 4 times
    if (onHeapStore_Hit_Count.getValue().length > 0) {
      int mostRecentIndex = onHeapStore_Hit_Count.getValue().length - 1;
      onHeapHitCount = onHeapStore_Hit_Count.getValue()[mostRecentIndex].getValue();
    }

  } while (onHeapHitCount != 4L);
}
finally {
  if(cacheManager != null) cacheManager.close();
}
1 Optional: give a name to your cache manager by using a custom configuration
2 Create an instance of org.ehcache.management.registry.DefaultManagementRegistryService. This is only required because the service is used below.
3 Pass it as a service to the cache manager (if you only want to configure the ManagementRegistry, you can just pass the configuration instead)
4 Perform a few gets to increment the statistic’s counter
5 Create the target statistic’s context
6 Collect the get count statistic
7 Check that the statistic reports the expected count

Obviously, you may use the above technique to pass your own implementation of ManagementRegistry.

Capabilities and contexts

Capabilities are metadata of what the managed objects are capable of: a collection of statistics that can be queried and/or remote actions that can be called. Each capability requires a context to run in. For instance, cache-specific statistics require a cache manager name and a cache name to uniquely identify the cache on which you want to query stats or call an action.

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10))
    .build();

CacheManager cacheManager = null;
try {
  ManagementRegistryService managementRegistry = new DefaultManagementRegistryService();
  cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache("aCache", cacheConfiguration)
      .using(managementRegistry)
      .build(true);


  Collection<? extends Capability> capabilities = managementRegistry.getCapabilities(); (1)
  Assert.assertThat(capabilities.isEmpty(), Matchers.is(false));
  Capability capability = capabilities.iterator().next();
  String capabilityName = capability.getName(); (2)
  Collection<? extends Descriptor> capabilityDescriptions = capability.getDescriptors(); (3)
  Assert.assertThat(capabilityDescriptions.isEmpty(), Matchers.is(false));
  CapabilityContext capabilityContext = capability.getCapabilityContext();
  Collection<CapabilityContext.Attribute> attributes = capabilityContext.getAttributes(); (4)
  Assert.assertThat(attributes.size(), Matchers.is(2));
  Iterator<CapabilityContext.Attribute> iterator = attributes.iterator();
  CapabilityContext.Attribute attribute1 = iterator.next();
  Assert.assertThat(attribute1.getName(), Matchers.equalTo("cacheManagerName"));  (5)
  Assert.assertThat(attribute1.isRequired(), Matchers.is(true));
  CapabilityContext.Attribute attribute2 = iterator.next();
  Assert.assertThat(attribute2.getName(), Matchers.equalTo("cacheName")); (6)
  Assert.assertThat(attribute2.isRequired(), Matchers.is(true));

  ContextContainer contextContainer = managementRegistry.getContextContainer();  (7)
  Assert.assertThat(contextContainer.getName(), Matchers.equalTo("cacheManagerName"));  (8)
  Assert.assertThat(contextContainer.getValue(), Matchers.startsWith("cache-manager-"));
  Collection<ContextContainer> subContexts = contextContainer.getSubContexts();
  Assert.assertThat(subContexts.size(), Matchers.is(1));
  ContextContainer subContextContainer = subContexts.iterator().next();
  Assert.assertThat(subContextContainer.getName(), Matchers.equalTo("cacheName"));  (9)
  Assert.assertThat(subContextContainer.getValue(), Matchers.equalTo("aCache"));
}
finally {
  if(cacheManager != null) cacheManager.close();
}
1 Query the ManagementRegistry for the registered managed objects' capabilities.
2 Each capability has a unique name you will need to refer to it.
3 Each capability has a collection of `Descriptor`s that contains the metadata of each statistic or action.
4 Each capability requires a context which it needs to refer to.
5 The first attribute of this context is the cache manager name.
6 The second attribute of this context is the cache name. With both attributes, the capability can uniquely refer to a unique managed object.
7 Query the ManagementRegistry for all of the registered managed objects' contexts.
8 There is only one context here, and its name is the cache manager’s name.
9 The above context has a subcontext: the cache’s name.

The context containers give you all the attributes of all existing contexts. You can match the values returned by a context container to a capability’s context by matching their respective names.

Actions

There are two forms of capabilities: statistics and action ones. The statistics ones offer a set of predefined statistics that can be queried at will, while the action ones offer a set of actions that can be taken on a managed object. Examples of actions could be: clear caches, get their configuration or modify a configuration setting.

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10))
    .build();

CacheManager cacheManager = null;
try {
  ManagementRegistryService managementRegistry = new DefaultManagementRegistryService();
  cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache("aCache", cacheConfiguration)
      .using(managementRegistry)
      .build(true);

  Cache<Long, String> aCache = cacheManager.getCache("aCache", Long.class, String.class);
  aCache.put(0L, "zero"); (1)

  Context context = StatsUtil.createContext(managementRegistry); (2)

  managementRegistry.withCapability("ActionsCapability") (3)
      .call("clear")
      .on(context)
      .build()
      .execute();

  Assert.assertThat(aCache.get(0L), Matchers.is(Matchers.nullValue())); (4)
}
finally {
  if(cacheManager != null) cacheManager.close();
}
1 Put something in a cache.
2 Call the 'clear' action on the managed cache. Refer to the descriptors of the provider to get the exact list of action names and their required parameters.
3 Call the clear action on the cache.
4 Make sure that the cache is now empty.

Managing multiple cache managers

The default ManagementRegistry instance that is created when none are manually registered only manages a single cache manager by default, but sometimes you may want one ManagementRegistry to manage multiple cache managers.

ManagementRegistry instances are thread-safe, so one instance can be shared amongst multiple cache managers:

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10))
    .build();

CacheManager cacheManager1 = null;
CacheManager cacheManager2 = null;
try {
  SharedManagementService sharedManagementService = new DefaultSharedManagementService(); (1)
  cacheManager1 = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache("aCache", cacheConfiguration)
      .using(new DefaultManagementRegistryConfiguration().setCacheManagerAlias("myCacheManager-1").addConfiguration(EHCACHE_STATS_CONFIG))
      .using(sharedManagementService) (2)
      .build(true);

  cacheManager2 = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache("aCache", cacheConfiguration)
      .using(new DefaultManagementRegistryConfiguration().setCacheManagerAlias("myCacheManager-2").addConfiguration(EHCACHE_STATS_CONFIG))
      .using(sharedManagementService) (3)
      .build(true);

  Context context1 = Context.empty()
    .with("cacheManagerName", "myCacheManager-1")
    .with("cacheName", "aCache");

  Context context2 = Context.empty()
    .with("cacheManagerName", "myCacheManager-2")
    .with("cacheName", "aCache");

  Cache<Long, String> cache = cacheManager1.getCache("aCache", Long.class, String.class);
  cache.get(1L);//cache miss
  cache.get(2L);//cache miss

  StatisticQuery query = sharedManagementService.withCapability("StatisticsCapability")
    .queryStatistic("Cache:MissCount")
    .on(context1)
    .on(context2)
    .build();

  long val = 0;
  // it could be several seconds before the sampled stats could become available
  // let's try until we find the correct value : 2
  do {
    ResultSet<ContextualStatistics> counters = query.execute();

    ContextualStatistics statisticsContext1 = counters.getResult(context1);

    CounterHistory counterContext1 = statisticsContext1.getStatistic(CounterHistory.class, "Cache:MissCount");

    // miss count is a sampled stat, for example its values could be [0,1,2].
    // In the present case, only the last value is important to us , the cache was eventually missed 2 times
    if (counterContext1.getValue().length > 0) {
      int mostRecentSampleIndex = counterContext1.getValue().length - 1;
      val = counterContext1.getValue()[mostRecentSampleIndex].getValue();
    }
  } while(val != 2);
}
finally {
  if(cacheManager2 != null) cacheManager2.close();
  if(cacheManager1 != null) cacheManager1.close();
}
1 Create an instance of org.ehcache.management.SharedManagementService
2 Pass it as a service to the first cache manager
3 Pass it as a service to the second cache manager

This way, all managed objects get registered into a common ManagementRegistry instance.