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)
  ManagementRegistryService managementRegistry = new DefaultManagementRegistryService(registryConfiguration); (2)

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

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

  Object o = managementRegistry.withCapability("StatisticCollectorCapability")
    .call("updateCollectedStatistics",
      new Parameter("StatisticsCapability"),
      new Parameter(Arrays.asList("Cache:HitCount", "Cache:MissCount"), Collection.class.getName()))
    .on(Context.create("cacheManagerName", "myCacheManager1"))
    .build()
    .execute()
    .getSingleResult();
  System.out.println(o);

  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();

  ResultSet<ContextualStatistics> counters = query.execute();

  ContextualStatistics statisticsContext = counters.getResult(context);

  Assert.assertThat(counters.size(), Matchers.is(1));
}
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

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"))
      .using(sharedManagementService) (2)
      .build(true);

  cacheManager2 = CacheManagerBuilder.newCacheManagerBuilder()
      .withCache("aCache", cacheConfiguration)
      .using(new DefaultManagementRegistryConfiguration().setCacheManagerAlias("myCacheManager-2"))
      .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);

    Long counterContext1 = statisticsContext1.<Long>getLatestSampleValue("Cache:MissCount").get();

    // 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
    val = counterContext1.longValue();
  } 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.

Rules for statistics calculation

This table describes whether or not a cache method can impact a given statistic. It is compatible with JSR-107 (section 12.4).

The statistics are:

Hit

An entry was asked for and found in the cache

Miss

An entry was asked for and not found in the cache

Put

An entry was added or updated

Removal

An entry was removed from the cache

Expiration

An entry has expired and thus removed from the cache

Eviction

An entry was evicted from the cache for lack of space

Method Hit Miss Put Removal Expiration Eviction Notes clear

No

No

No

No

No

No

A bulk remove with no impact on stats

containsKey

No

No

No

No

No

No

Generates no hit or miss since the value isn’t accessed

forEach

Yes

No

No

No

Yes

No

Java 8. Will hit each entry once

get

Yes

Yes

No

No

Yes

No

Hit when the entry is found, miss otherwise

getAll

Yes

Yes

No

No

Yes

No

Like get but calculated per entry

getAndPut

Yes

Yes

Yes

No

Yes

Yes

JSR107 specific. Hit when the entry is found, miss otherwise. Always put

getAndRemove

Yes

Yes

No

Yes

Yes

No

JSR107 specific. Hit and remove when the entry is found, miss otherwise

getAndReplace

Yes

Yes

Yes

No

Yes

Yes

JSR107 specific. Hit and put when the entry is found, miss otherwise

invoke

Yes

Yes

Yes

Yes

Yes

Yes

JSR107 specific. Depends on how the EntryProcessor modifies the MutableEntry

invokeAll

Yes

Yes

Yes

Yes

Yes

Yes

JSR107 specific. Like invoke but calculated per entry

iterator

Yes

No

No

Yes

Yes

No

Will hit each iterated entry and count a removal on Iterator.remove

loadAll

No

No

No

No

No

No

JSR107 specific. Background loading that has no impact on stats

put

No

No

Yes

No

No

Yes

Put an entry whether it already existed or not.

putAll

No

No

Yes

No

No

Yes

Like put but calculated per entry

putIfAbsent

Yes

Yes

Yes

No

Yes

Yes

Will hit if the entry exists. Will put and miss if it doesn’t

remove(K)

No

No

No

Yes

Yes

No

Count a removal if the entry exists

remove(K,V)

Yes

Yes

No

Yes

Yes

No

Hit if the key is found. Miss otherwise

removeAll

No

No

No

Yes

No

No

One removal per entry in the cache

removeAll(keys)

No

No

No

Yes

No

No

Like remove but calculated per entry

replace(K,V)

Yes

Yes

Yes

No

Yes

Yes

Hit and put if the entry exists. Miss otherwise

replace(K,O,N)

Yes

Yes

Yes

No

Yes

Yes

Hit if the entry exists

spliterator

Yes

No

No

The statistics are provided by cache and tiers. Cache evictions and expirations are taken from the lowest (authoritative) tier.