| /* |
| * 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.cassandra.metrics; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| |
| import org.junit.Test; |
| |
| import org.apache.cassandra.cache.CacheSize; |
| import org.apache.cassandra.cache.ICache; |
| import org.apache.cassandra.cache.InstrumentingCache; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| public class CacheMetricsTest |
| { |
| private static final long capacity = 65536; |
| |
| @Test |
| public void testCacheMetrics() |
| { |
| ICache<String,Object> mockedCache = new MapMockedCache(); |
| InstrumentingCache<String,Object> cache = new InstrumentingCache<>("cache", mockedCache); |
| CacheMetrics metrics = cache.getMetrics(); |
| |
| assertCacheMetrics(metrics, expect(mockedCache)); |
| |
| cache.put("k1", "v1"); |
| assertCacheMetrics(metrics, expect(mockedCache)); |
| |
| getFromCache(cache, "k1", 10); |
| assertCacheMetrics(metrics, expect(mockedCache).hits(10).misses(0)); |
| |
| getFromCache(cache, "k2", 10); |
| assertCacheMetrics(metrics, expect(mockedCache).hits(10).misses(10)); |
| |
| cache.put("k2", "v2"); |
| getFromCache(cache, "k2", 70); |
| getFromCache(cache, "k3", 10); |
| assertCacheMetrics(metrics, expect(mockedCache).hits(80).misses(20)); |
| |
| cache.clear(); |
| metrics.reset(); |
| assertCacheMetrics(metrics, expect(mockedCache)); |
| } |
| |
| private void getFromCache(InstrumentingCache<String,Object> cache, String key, int times) |
| { |
| for (int ix = 0; ix < times; ix++) |
| { |
| cache.get(key); |
| } |
| } |
| |
| private void assertCacheMetrics(CacheMetrics actual, CacheMetricsExpectation expectation) |
| { |
| // assuming meters/guagues (hits, misses, requests, and hitRate) will have correct one/five/fifteenMinute |
| // calculations - applying some general assertions for hitRate calculations that essentially just smoke test |
| // existence (i.e. NaN at initialization) since they are established by way of an inner class on CacheMetrics |
| // itself. |
| assertEquals(expectation.cacheSize.capacity(), actual.capacity.getValue().longValue()); |
| assertEquals(expectation.cacheSize.weightedSize(), actual.size.getValue().longValue()); |
| assertEquals(expectation.cacheSize.size(), actual.entries.getValue().intValue()); |
| assertEquals(expectation.hits, actual.hits.getCount()); |
| assertEquals(expectation.misses, actual.misses.getCount()); |
| assertEquals(expectation.requests(), actual.requests.getCount()); |
| assertEquals(expectation.hitRate(), actual.hitRate.getValue(), 0.001d); |
| assertEquals(Double.NaN, actual.oneMinuteHitRate.getValue(), 0.001d); |
| assertEquals(Double.NaN, actual.fiveMinuteHitRate.getValue(), 0.001d); |
| assertEquals(Double.NaN, actual.fifteenMinuteHitRate.getValue(), 0.001d); |
| } |
| |
| static CacheMetricsExpectation expect(CacheSize cacheSize) |
| { |
| return new CacheMetricsExpectation(cacheSize); |
| } |
| |
| private static class MapMockedCache implements ICache<String,Object> |
| { |
| private final Map<String,Object> map = new HashMap<>(); |
| |
| public void put(String key, Object value) |
| { |
| map.put(key, value); |
| } |
| |
| public boolean putIfAbsent(String key, Object value) |
| { |
| return map.putIfAbsent(key, value) == null; |
| } |
| |
| public boolean replace(String key, Object old, Object value) |
| { |
| return map.replace(key, old, value); |
| } |
| |
| public Object get(String key) |
| { |
| return map.get(key); |
| } |
| |
| public void remove(String key) |
| { |
| map.remove(key); |
| } |
| |
| public void clear() |
| { |
| map.clear(); |
| } |
| |
| public Iterator<String> keyIterator() |
| { |
| return map.keySet().iterator(); |
| } |
| |
| public Iterator<String> hotKeyIterator(int n) |
| { |
| return map.keySet().iterator(); |
| } |
| |
| public boolean containsKey(String key) |
| { |
| return map.containsKey(key); |
| } |
| |
| public long capacity() |
| { |
| // capacity in bytes but just using a fixed number here since since the validation is just to ensure |
| // that this number is equivalent to the metric that publishes it. |
| return capacity; |
| } |
| |
| public void setCapacity(long capacity) |
| { |
| throw new UnsupportedOperationException("Not needed for testing"); |
| } |
| |
| public int size() |
| { |
| return map.size(); |
| } |
| |
| public long weightedSize() |
| { |
| // should be cache size in bytes, but no need to calculate that for tests since the validation |
| // is just to ensure that this number is equivalent to the metric that publishes it. |
| return map.size() * 8; |
| } |
| } |
| |
| private static class CacheMetricsExpectation |
| { |
| final CacheSize cacheSize; |
| private long hits = 0; |
| private long misses = 0; |
| |
| CacheMetricsExpectation(CacheSize cacheSize) |
| { |
| this.cacheSize = cacheSize; |
| } |
| |
| public CacheMetricsExpectation hits(long hits) |
| { |
| this.hits = hits; |
| return this; |
| } |
| |
| public CacheMetricsExpectation misses(long misses) |
| { |
| this.misses = misses; |
| return this; |
| } |
| |
| public long requests() |
| { |
| return hits + misses; |
| } |
| |
| public double hitRate() |
| { |
| return requests() == 0 ? Double.NaN : ((double) hits / (double) requests()); |
| } |
| } |
| } |