/* * 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 gobblin.metrics; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.lang.ref.WeakReference; import java.util.UUID; import org.testng.Assert; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.base.Predicate; import com.typesafe.config.ConfigFactory; import lombok.AllArgsConstructor; import gobblin.metrics.callback.NotificationStore; import gobblin.metrics.notification.MetricContextCleanupNotification; import gobblin.metrics.notification.NewMetricContextNotification; import gobblin.metrics.notification.Notification; import gobblin.metrics.test.ContextStoreReporter; /** * Tests for {@link gobblin.metrics.RootMetricContext} */ public class RootMetricContextTest { private static final Cache<String, int[]> CACHE = CacheBuilder.newBuilder().softValues().build(); @BeforeMethod public void setUp() throws Exception { System.gc(); RootMetricContext.get().clearNotificationTargets(); } @AfterMethod public void tearDown() throws Exception { CACHE.invalidateAll(); System.gc(); RootMetricContext.get().clearNotificationTargets(); } @Test public void testGet() throws Exception { Assert.assertNotNull(RootMetricContext.get()); Assert.assertEquals(RootMetricContext.get(), RootMetricContext.get()); Assert.assertEquals(RootMetricContext.get().getName(), RootMetricContext.ROOT_METRIC_CONTEXT); } @Test public void testReporterCanBeAddedToStartedContext() throws Exception { RootMetricContext.get().startReporting(); ContextStoreReporter reporter = new ContextStoreReporter("testReporter", ConfigFactory.empty()); Assert.assertTrue(reporter.isStarted()); RootMetricContext.get().stopReporting(); } @Test public void testMetricContextLifecycle() throws Exception { String name = UUID.randomUUID().toString(); NotificationStore store = new NotificationStore(new ContextNamePredicate(name)); RootMetricContext.get().addNotificationTarget(store); // Create a new metric context MetricContext metricContext = MetricContext.builder(name).build(); WeakReference<MetricContext> contextWeakReference = new WeakReference<MetricContext>(metricContext); InnerMetricContext innerMetricContext = metricContext.getInnerMetricContext(); WeakReference<InnerMetricContext> innerMetricContextWeakReference = new WeakReference<InnerMetricContext>(innerMetricContext); innerMetricContext = null; // Check that existence of a reporter does not prevent GC ContextStoreReporter reporter = new ContextStoreReporter("testReporter", ConfigFactory.empty()); // Check that metric context is a child of root metric context Assert.assertTrue(RootMetricContext.get().getChildContextsAsMap().containsKey(name)); Assert.assertEquals(RootMetricContext.get().getChildContextsAsMap().get(name), metricContext); // Check that notification on new metric context was generated Assert.assertEquals(store.getNotificationList().size(), 1); Assert.assertEquals(store.getNotificationList().get(0).getClass(), NewMetricContextNotification.class); Assert.assertEquals(((NewMetricContextNotification) store.getNotificationList().get(0)).getMetricContext(), metricContext); store.getNotificationList().clear(); // Create a counter ContextAwareCounter counter1 = metricContext.contextAwareCounter("textCounter1"); // If losing reference of counter, should not be GCed while context is present WeakReference<ContextAwareCounter> counterWeakReference1 = new WeakReference<ContextAwareCounter>(counter1); counter1 = null; ensureNotGarbageCollected(counterWeakReference1); // Create some more metrics ContextAwareCounter counter2 = metricContext.contextAwareCounter("testCounter"); WeakReference<ContextAwareCounter> counterWeakReference2 = new WeakReference<ContextAwareCounter>(counter2); ContextAwareMeter meter = metricContext.contextAwareMeter("testMeter"); WeakReference<ContextAwareMeter> meterWeakReference = new WeakReference<ContextAwareMeter>(meter); meter.mark(); ContextAwareHistogram histogram = metricContext.contextAwareHistogram("testHistogram"); WeakReference<ContextAwareHistogram> histogramWeakReference = new WeakReference<ContextAwareHistogram>(histogram); ContextAwareTimer timer = metricContext.contextAwareTimer("testTimer"); WeakReference<ContextAwareTimer> timerWeakReference = new WeakReference<ContextAwareTimer>(timer); // If losing reference to context, should not be GCed while reference to metric is present metricContext = null; ensureNotGarbageCollected(contextWeakReference); ensureNotGarbageCollected(counterWeakReference2); ensureNotGarbageCollected(meterWeakReference); ensureNotGarbageCollected(timerWeakReference); ensureNotGarbageCollected(histogramWeakReference); // After losing reference to context and all metrics, context and all metrics should be GCed store.getNotificationList().clear(); reporter.getReportedContexts().clear(); counter2 = null; meter = null; histogram = null; timer = null; ensureGarbageCollected(contextWeakReference); ensureGarbageCollected(counterWeakReference1); ensureGarbageCollected(counterWeakReference2); ensureGarbageCollected(meterWeakReference); ensureGarbageCollected(timerWeakReference); ensureGarbageCollected(histogramWeakReference); // Inner metric context should not be GCed ensureNotGarbageCollected(innerMetricContextWeakReference); // Notification on removal of metric context should be available int maxWait = 10; while(store.getNotificationList().isEmpty() && maxWait > 0) { Thread.sleep(1000); maxWait--; } Assert.assertEquals(store.getNotificationList().size(), 1); Assert.assertEquals(store.getNotificationList().get(0).getClass(), MetricContextCleanupNotification.class); Assert.assertEquals(((MetricContextCleanupNotification) store.getNotificationList().get(0)).getMetricContext(), innerMetricContextWeakReference.get()); // Reporter should have attempted to report metric context Assert.assertEquals(reporter.getReportedContexts().size(), 1); Assert.assertEquals(reporter.getReportedContexts().get(0), innerMetricContextWeakReference.get()); // Metrics in deleted metric context should still be readable Assert.assertEquals(innerMetricContextWeakReference.get().getCounters().size(), 2); Assert.assertEquals(innerMetricContextWeakReference.get().getMeters().size(), 1); Assert.assertEquals(innerMetricContextWeakReference.get().getTimers().size(), 2); Assert.assertEquals(innerMetricContextWeakReference.get().getHistograms().size(), 1); Assert.assertEquals(innerMetricContextWeakReference.get().getMeters().get("testMeter").getCount(), 1); // After clearing notification, inner metric context should be GCed store.getNotificationList().clear(); reporter.getReportedContexts().clear(); ensureGarbageCollected(innerMetricContextWeakReference); RootMetricContext.get().removeReporter(reporter); } private void triggerGarbageCollection(WeakReference<?> weakReference) { System.gc(); // System.gc() might not clean up the object being checked, so allocate some memory and call System.gc() again for (int i = 0; weakReference.get() != null && i <= 10000; i++) { CACHE.put(Integer.toString(i), new int[10000]); if (i % 1000 == 0) { System.gc(); } } } private void ensureGarbageCollected(WeakReference<?> weakReference) { triggerGarbageCollection(weakReference); Assert.assertNull(weakReference.get()); } private void ensureNotGarbageCollected(WeakReference<?> weakReference) { triggerGarbageCollection(weakReference); Assert.assertNotNull(weakReference.get()); } @AllArgsConstructor private class ContextNamePredicate implements Predicate<Notification> { private final String name; @Override public boolean apply(Notification input) { if (input instanceof NewMetricContextNotification && ((NewMetricContextNotification) input).getInnerMetricContext().getName().equals(this.name)) { return true; } if (input instanceof MetricContextCleanupNotification && ((MetricContextCleanupNotification) input).getMetricContext().getName().equals(this.name)) { return true; } return false; } } }