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);
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)
assertThat(capabilities.isEmpty(), Matchers.is(false));
Capability capability = capabilities.iterator().next();
String capabilityName = capability.getName(); (2)
Collection<? extends Descriptor> capabilityDescriptions = capability.getDescriptors(); (3)
assertThat(capabilityDescriptions.isEmpty(), Matchers.is(false));
CapabilityContext capabilityContext = capability.getCapabilityContext();
Collection<CapabilityContext.Attribute> attributes = capabilityContext.getAttributes(); (4)
assertThat(attributes.size(), Matchers.is(2));
Iterator<CapabilityContext.Attribute> iterator = attributes.iterator();
CapabilityContext.Attribute attribute1 = iterator.next();
assertThat(attribute1.getName(), Matchers.equalTo("cacheManagerName")); (5)
assertThat(attribute1.isRequired(), Matchers.is(true));
CapabilityContext.Attribute attribute2 = iterator.next();
assertThat(attribute2.getName(), Matchers.equalTo("cacheName")); (6)
assertThat(attribute2.isRequired(), Matchers.is(true));
ContextContainer contextContainer = managementRegistry.getContextContainer(); (7)
assertThat(contextContainer.getName(), Matchers.equalTo("cacheManagerName")); (8)
assertThat(contextContainer.getValue(), Matchers.startsWith("cache-manager-"));
Collection<ContextContainer> subContexts = contextContainer.getSubContexts();
assertThat(subContexts.size(), Matchers.is(1));
ContextContainer subContextContainer = subContexts.iterator().next();
assertThat(subContextContainer.getName(), Matchers.equalTo("cacheName")); (9)
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();
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 |
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 |
invokeAll |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes |
JSR107 specific. Like |
iterator |
Yes |
No |
No |
Yes |
Yes |
No |
Will hit each iterated entry and count a removal on |
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 |
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 |
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.