/**
* 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.ambari.server.controller.metrics.timeline.cache;
import static org.apache.ambari.server.controller.metrics.timeline.cache.TimelineMetricCacheProvider.TIMELINE_METRIC_CACHE_INSTANCE_NAME;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.controller.internal.TemporalInfoImpl;
import org.apache.ambari.server.controller.metrics.timeline.MetricsRequestHelper;
import org.apache.ambari.server.controller.spi.TemporalInfo;
import org.apache.hadoop.metrics2.sink.timeline.TimelineMetric;
import org.apache.hadoop.metrics2.sink.timeline.TimelineMetrics;
import org.apache.http.client.utils.URIBuilder;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Test;
import junit.framework.Assert;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.config.SizeOfPolicyConfiguration;
import net.sf.ehcache.constructs.blocking.UpdatingCacheEntryFactory;
import net.sf.ehcache.constructs.blocking.UpdatingSelfPopulatingCache;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
public class TimelineMetricCacheTest {
@After
public void removeCacheInstance() {
// Avoids Object Exists Exception on unit tests by adding a new cache for
// every provider.
CacheManager manager = CacheManager.getInstance();
manager.removeAllCaches();
}
// General cache behavior demonstration
@Test
public void testSelfPopulatingCacheUpdates() throws Exception {
UpdatingCacheEntryFactory cacheEntryFactory = createMock(UpdatingCacheEntryFactory.class);
StringBuilder value = new StringBuilder("b");
expect(cacheEntryFactory.createEntry("a")).andReturn(value);
cacheEntryFactory.updateEntryValue("a", value);
expectLastCall().andAnswer(new IAnswer<Object>() {
@Override
public Object answer() throws Throwable {
String key = (String) getCurrentArguments()[0];
StringBuilder value = (StringBuilder) getCurrentArguments()[1];
System.out.println("key = " + key + ", value = " + value);
value.append("c");
return null;
}
});
replay(cacheEntryFactory);
// Need to set this due what seems like a bug in Ehcache 2.10.0, setting
// it on the second cache instance results in a assertion error.
// Since this is not out production use case, setting it here as well.
net.sf.ehcache.config.Configuration managerConfig = new net.sf.ehcache.config.Configuration();
managerConfig.setMaxBytesLocalHeap("10%");
CacheManager manager = CacheManager.create(managerConfig);
Cache cache = new Cache("test", 0, false, false, 10000, 10000);
UpdatingSelfPopulatingCache testCache = new UpdatingSelfPopulatingCache(cache, cacheEntryFactory);
manager.addCache(testCache);
Assert.assertEquals("b", testCache.get("a").getObjectValue().toString());
Assert.assertEquals("bc", testCache.get("a").getObjectValue().toString());
verify(cacheEntryFactory);
}
private CacheConfiguration createTestCacheConfiguration(Configuration configuration) {
CacheConfiguration cacheConfiguration = new CacheConfiguration()
.name(TIMELINE_METRIC_CACHE_INSTANCE_NAME)
.timeToLiveSeconds(configuration.getMetricCacheTTLSeconds()) // 1 hour
.timeToIdleSeconds(configuration.getMetricCacheIdleSeconds()) // 5 minutes
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU)
.sizeOfPolicy(new SizeOfPolicyConfiguration() // Set sizeOf policy to continue on max depth reached - avoid OOM
.maxDepth(10000)
.maxDepthExceededBehavior(SizeOfPolicyConfiguration.MaxDepthExceededBehavior.CONTINUE))
.eternal(false)
.persistence(new PersistenceConfiguration()
.strategy(PersistenceConfiguration.Strategy.NONE.name()));
cacheConfiguration.setMaxBytesLocalHeap(20*1024*1024l);
return cacheConfiguration;
}
@Test
public void testTimelineMetricCacheProviderGets() throws Exception {
Configuration configuration = createNiceMock(Configuration.class);
expect(configuration.getMetricCacheTTLSeconds()).andReturn(3600);
expect(configuration.getMetricCacheIdleSeconds()).andReturn(100);
expect(configuration.getMetricsCacheManagerHeapPercent()).andReturn("10%").anyTimes();
replay(configuration);
final long now = System.currentTimeMillis();
TimelineMetrics metrics = new TimelineMetrics();
TimelineMetric timelineMetric = new TimelineMetric();
timelineMetric.setMetricName("cpu_user");
timelineMetric.setAppId("app1");
TreeMap<Long, Double> metricValues = new TreeMap<>();
metricValues.put(now + 100, 1.0);
metricValues.put(now + 200, 2.0);
metricValues.put(now + 300, 3.0);
timelineMetric.setMetricValues(metricValues);
metrics.getMetrics().add(timelineMetric);
TimelineMetricCacheEntryFactory cacheEntryFactory = createMock(TimelineMetricCacheEntryFactory.class);
TimelineAppMetricCacheKey queryKey = new TimelineAppMetricCacheKey(
Collections.singleton("cpu_user"),
"app1",
new TemporalInfoImpl(now, now + 1000, 1)
);
TimelineMetricsCacheValue value = new TimelineMetricsCacheValue(now, now + 1000, metrics, null);
TimelineAppMetricCacheKey testKey = new TimelineAppMetricCacheKey(
Collections.singleton("cpu_user"),
"app1",
new TemporalInfoImpl(now, now + 2000, 1)
);
expect(cacheEntryFactory.createEntry(anyObject())).andReturn(value);
cacheEntryFactory.updateEntryValue(testKey, value);
expectLastCall().once();
replay(cacheEntryFactory);
TimelineMetricCacheProvider cacheProvider = createMockBuilder(TimelineMetricCacheProvider.class)
.addMockedMethod("createCacheConfiguration")
.withConstructor(configuration, cacheEntryFactory)
.createNiceMock();
expect(cacheProvider.createCacheConfiguration()).andReturn(createTestCacheConfiguration(configuration)).anyTimes();
replay(cacheProvider);
TimelineMetricCache cache = cacheProvider.getTimelineMetricsCache();
// call to get
metrics = cache.getAppTimelineMetricsFromCache(queryKey);
List<TimelineMetric> metricsList = metrics.getMetrics();
Assert.assertEquals(1, metricsList.size());
TimelineMetric metric = metricsList.iterator().next();
Assert.assertEquals("cpu_user", metric.getMetricName());
Assert.assertEquals("app1", metric.getAppId());
Assert.assertSame(metricValues, metric.getMetricValues());
// call to update with new key
metrics = cache.getAppTimelineMetricsFromCache(testKey);
metricsList = metrics.getMetrics();
Assert.assertEquals(1, metricsList.size());
Assert.assertEquals("cpu_user", metric.getMetricName());
Assert.assertEquals("app1", metric.getAppId());
Assert.assertSame(metricValues, metric.getMetricValues());
verify(configuration, cacheEntryFactory);
}
@Test
@SuppressWarnings("all")
public void testCacheUpdateBoundsOnVariousRequestScenarios() throws Exception {
Configuration configuration = createNiceMock(Configuration.class);
expect(configuration.getMetricsRequestConnectTimeoutMillis()).andReturn(10000);
expect(configuration.getMetricsRequestReadTimeoutMillis()).andReturn(10000);
expect(configuration.getMetricsRequestIntervalReadTimeoutMillis()).andReturn(10000);
// Disable buffer fudge factor
expect(configuration.getMetricRequestBufferTimeCatchupInterval()).andReturn(0l);
replay(configuration);
TimelineMetricCacheEntryFactory factory =
createMockBuilder(TimelineMetricCacheEntryFactory.class)
.withConstructor(configuration).createMock();
replay(factory);
long now = System.currentTimeMillis();
final long existingSeriesStartTime = now - (3600 * 1000); // now - 1 hour
final long existingSeriesEndTime = now;
// Regular timeseries overlap
long requestedStartTime = existingSeriesStartTime + 60000; // + 1 min
long requestedEndTime = existingSeriesEndTime + 60000; // + 1 min
long newStartTime = factory.getRefreshRequestStartTime(existingSeriesStartTime,
existingSeriesEndTime, requestedStartTime);
long newEndTime = factory.getRefreshRequestEndTime(existingSeriesStartTime,
existingSeriesEndTime, requestedEndTime);
Assert.assertEquals(existingSeriesEndTime, newStartTime);
Assert.assertEquals(requestedEndTime, newEndTime);
// Disconnected timeseries graph
requestedStartTime = existingSeriesEndTime + 60000; // end + 1 min
requestedEndTime = existingSeriesEndTime + 60000 + 3600000; // + 1 min + 1 hour
newStartTime = factory.getRefreshRequestStartTime(existingSeriesStartTime,
existingSeriesEndTime, requestedStartTime);
newEndTime = factory.getRefreshRequestEndTime(existingSeriesStartTime,
existingSeriesEndTime, requestedEndTime);
Assert.assertEquals(requestedStartTime, newStartTime);
Assert.assertEquals(requestedEndTime, newEndTime);
// Complete overlap
requestedStartTime = existingSeriesStartTime - 60000; // - 1 min
requestedEndTime = existingSeriesEndTime + 60000; // + 1 min
newStartTime = factory.getRefreshRequestStartTime(existingSeriesStartTime,
existingSeriesEndTime, requestedStartTime);
newEndTime = factory.getRefreshRequestEndTime(existingSeriesStartTime,
existingSeriesEndTime, requestedEndTime);
Assert.assertEquals(requestedStartTime, newStartTime);
Assert.assertEquals(requestedEndTime, newEndTime);
// Timeseries in the past
requestedStartTime = existingSeriesStartTime - 3600000 - 60000; // - 1 hour - 1 min
requestedEndTime = existingSeriesStartTime - 60000; // start - 1 min
newStartTime = factory.getRefreshRequestStartTime(existingSeriesStartTime,
existingSeriesEndTime, requestedStartTime);
newEndTime = factory.getRefreshRequestEndTime(existingSeriesStartTime,
existingSeriesEndTime, requestedEndTime);
Assert.assertEquals(requestedStartTime, newStartTime);
Assert.assertEquals(requestedEndTime, newEndTime);
// Timeseries overlap - no new request needed
requestedStartTime = existingSeriesStartTime + 60000; // + 1 min
requestedEndTime = existingSeriesEndTime - 60000; // - 1 min
newStartTime = factory.getRefreshRequestStartTime(existingSeriesStartTime,
existingSeriesEndTime, requestedStartTime);
newEndTime = factory.getRefreshRequestEndTime(existingSeriesStartTime,
existingSeriesEndTime, requestedEndTime);
Assert.assertEquals(newStartTime, existingSeriesEndTime);
Assert.assertEquals(newEndTime, existingSeriesStartTime);
verify(configuration, factory);
}
@Test
public void testTimelineMetricCacheTimeseriesUpdates() throws Exception {
Configuration configuration = createNiceMock(Configuration.class);
expect(configuration.getMetricsRequestConnectTimeoutMillis()).andReturn(10000);
expect(configuration.getMetricsRequestReadTimeoutMillis()).andReturn(10000);
expect(configuration.getMetricsRequestIntervalReadTimeoutMillis()).andReturn(10000);
// Disable buffer fudge factor
expect(configuration.getMetricRequestBufferTimeCatchupInterval()).andReturn(0l);
replay(configuration);
TimelineMetricCacheEntryFactory factory =
createMockBuilder(TimelineMetricCacheEntryFactory.class)
.withConstructor(configuration).createMock();
replay(factory);
long now = System.currentTimeMillis();
// Existing values
final TimelineMetric timelineMetric1 = new TimelineMetric();
timelineMetric1.setMetricName("cpu_user");
timelineMetric1.setAppId("app1");
TreeMap<Long, Double> metricValues = new TreeMap<>();
metricValues.put(now - 100, 1.0);
metricValues.put(now - 200, 2.0);
metricValues.put(now - 300, 3.0);
timelineMetric1.setMetricValues(metricValues);
final TimelineMetric timelineMetric2 = new TimelineMetric();
timelineMetric2.setMetricName("cpu_nice");
timelineMetric2.setAppId("app1");
metricValues = new TreeMap<>();
metricValues.put(now + 400, 1.0);
metricValues.put(now + 500, 2.0);
metricValues.put(now + 600, 3.0);
timelineMetric2.setMetricValues(metricValues);
TimelineMetrics existingMetrics = new TimelineMetrics();
existingMetrics.getMetrics().add(timelineMetric1);
existingMetrics.getMetrics().add(timelineMetric2);
TimelineMetricsCacheValue existingMetricValue = new TimelineMetricsCacheValue(
now - 1000, now + 1000, existingMetrics, null);
// New values
TimelineMetrics newMetrics = new TimelineMetrics();
TimelineMetric timelineMetric3 = new TimelineMetric();
timelineMetric3.setMetricName("cpu_user");
timelineMetric3.setAppId("app1");
metricValues = new TreeMap<>();
metricValues.put(now + 1400, 1.0);
metricValues.put(now + 1500, 2.0);
metricValues.put(now + 1600, 3.0);
timelineMetric3.setMetricValues(metricValues);
newMetrics.getMetrics().add(timelineMetric3);
factory.updateTimelineMetricsInCache(newMetrics, existingMetricValue,
now, now + 2000, false);
Assert.assertEquals(2, existingMetricValue.getTimelineMetrics().getMetrics().size());
TimelineMetric newMetric1 = null;
TimelineMetric newMetric2 = null;
for (TimelineMetric metric : existingMetricValue.getTimelineMetrics().getMetrics()) {
if (metric.getMetricName().equals("cpu_user")) {
newMetric1 = metric;
}
if (metric.getMetricName().equals("cpu_nice")) {
newMetric2 = metric;
}
}
Assert.assertNotNull(newMetric1);
Assert.assertNotNull(newMetric2);
Assert.assertEquals(3, newMetric1.getMetricValues().size());
Assert.assertEquals(3, newMetric2.getMetricValues().size());
Map<Long, Double> newMetricsMap = newMetric1.getMetricValues();
Iterator<Long> metricKeyIterator = newMetricsMap.keySet().iterator();
Assert.assertEquals(now + 1400, metricKeyIterator.next().longValue());
Assert.assertEquals(now + 1500, metricKeyIterator.next().longValue());
Assert.assertEquals(now + 1600, metricKeyIterator.next().longValue());
verify(configuration, factory);
}
@Test
public void testEqualsOnKeys() {
long now = System.currentTimeMillis();
TemporalInfo temporalInfo = new TemporalInfoImpl(now - 1000, now, 1);
TimelineAppMetricCacheKey key1 = new TimelineAppMetricCacheKey(
new HashSet<String>() {{ add("cpu_num._avg"); add("proc_run._avg"); }},
"HOST",
temporalInfo
);
TimelineAppMetricCacheKey key2 = new TimelineAppMetricCacheKey(
new HashSet<String>() {{ add("cpu_num._avg"); }},
"HOST",
temporalInfo
);
Assert.assertFalse(key1.equals(key2));
Assert.assertFalse(key2.equals(key1));
key2.getMetricNames().add("proc_run._avg");
Assert.assertTrue(key1.equals(key2));
}
@Test
public void testTimelineMetricCachePrecisionUpdates() throws Exception {
Configuration configuration = createNiceMock(Configuration.class);
expect(configuration.getMetricCacheTTLSeconds()).andReturn(3600);
expect(configuration.getMetricCacheIdleSeconds()).andReturn(100);
expect(configuration.getMetricsCacheManagerHeapPercent()).andReturn("10%").anyTimes();
expect(configuration.getMetricRequestBufferTimeCatchupInterval()).andReturn(1000l).anyTimes();
replay(configuration);
final long now = System.currentTimeMillis();
long second = 1000;
long min = 60 * second;
long hour = 60 * min;
long day = 24 * hour;
long year = 365 * day;
//Original Values
Map<String, TimelineMetric> valueMap = new HashMap<>();
TimelineMetric timelineMetric = new TimelineMetric();
timelineMetric.setMetricName("cpu_user");
timelineMetric.setAppId("app1");
TreeMap<Long, Double> metricValues = new TreeMap<>();
for (long i = 1 * year - 1 * day; i >= 0; i -= 1 * day) {
metricValues.put(now-i, 1.0);
}
timelineMetric.setMetricValues(metricValues);
valueMap.put("cpu_user", timelineMetric);
List<TimelineMetric> timelineMetricList = new ArrayList<>();
timelineMetricList.add(timelineMetric);
TimelineMetrics metrics = new TimelineMetrics();
metrics.setMetrics(timelineMetricList);
TimelineAppMetricCacheKey key = new TimelineAppMetricCacheKey(
Collections.singleton("cpu_user"),
"app1",
new TemporalInfoImpl(now-1*year, now, 1)
);
key.setSpec("");
//Updated values
Map<String, TimelineMetric> newValueMap = new HashMap<>();
TimelineMetric newTimelineMetric = new TimelineMetric();
newTimelineMetric.setMetricName("cpu_user");
newTimelineMetric.setAppId("app1");
TreeMap<Long, Double> newMetricValues = new TreeMap<>();
for(long i=1*hour;i<=2*day;i+=hour) {
newMetricValues.put(now-1*day+i, 2.0);
}
newTimelineMetric.setMetricValues(newMetricValues);
newValueMap.put("cpu_user", newTimelineMetric);
List<TimelineMetric> newTimelineMetricList = new ArrayList<>();
newTimelineMetricList.add(newTimelineMetric);
TimelineMetrics newMetrics = new TimelineMetrics();
newMetrics.setMetrics(newTimelineMetricList);
TimelineAppMetricCacheKey newKey = new TimelineAppMetricCacheKey(
Collections.singleton("cpu_user"),
"app1",
new TemporalInfoImpl(now - 1 * day, now + 2 * day, 1)
);
newKey.setSpec("");
MetricsRequestHelper metricsRequestHelperForGets = createMock(MetricsRequestHelper.class);
expect(metricsRequestHelperForGets.fetchTimelineMetrics(EasyMock.isA(URIBuilder.class), anyLong(), anyLong()))
.andReturn(metrics).andReturn(newMetrics);
replay(metricsRequestHelperForGets);
TimelineMetricCacheEntryFactory cacheEntryFactory =
createMockBuilder(TimelineMetricCacheEntryFactory.class)
.withConstructor(configuration).createMock();
Field requestHelperField = TimelineMetricCacheEntryFactory.class.getDeclaredField("requestHelperForGets");
requestHelperField.setAccessible(true);
requestHelperField.set(cacheEntryFactory, metricsRequestHelperForGets);
requestHelperField = TimelineMetricCacheEntryFactory.class.getDeclaredField("requestHelperForUpdates");
requestHelperField.setAccessible(true);
requestHelperField.set(cacheEntryFactory, metricsRequestHelperForGets);
replay(cacheEntryFactory);
TimelineMetricCacheProvider cacheProvider = createMockBuilder(TimelineMetricCacheProvider.class)
.addMockedMethod("createCacheConfiguration")
.withConstructor(configuration, cacheEntryFactory)
.createNiceMock();
expect(cacheProvider.createCacheConfiguration()).andReturn(createTestCacheConfiguration(configuration)).anyTimes();
replay(cacheProvider);
TimelineMetricCache cache = cacheProvider.getTimelineMetricsCache();
// call to get
metrics = cache.getAppTimelineMetricsFromCache(key);
List<TimelineMetric> metricsList = metrics.getMetrics();
Assert.assertEquals(1, metricsList.size());
TimelineMetric metric = metricsList.iterator().next();
Assert.assertEquals("cpu_user", metric.getMetricName());
Assert.assertEquals("app1", metric.getAppId());
Assert.assertEquals(metricValues, metric.getMetricValues());
// call to update with new key
metrics = cache.getAppTimelineMetricsFromCache(newKey);
metricsList = metrics.getMetrics();
Assert.assertEquals(1, metricsList.size());
Assert.assertEquals("cpu_user", metric.getMetricName());
Assert.assertEquals("app1", metric.getAppId());
Assert.assertEquals(newMetricValues, metric.getMetricValues());
verify(configuration, metricsRequestHelperForGets, cacheEntryFactory);
}
}