/*
* Copyright 2015-2017 the original author or authors.
*
* Licensed 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.glowroot.agent.init;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.List;
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.google.common.base.Ticker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.glowroot.agent.collector.Collector;
import org.glowroot.agent.config.AdvancedConfig;
import org.glowroot.agent.config.ConfigService;
import org.glowroot.agent.config.GaugeConfig;
import org.glowroot.agent.config.ImmutableAdvancedConfig;
import org.glowroot.agent.config.ImmutableGaugeConfig;
import org.glowroot.agent.config.ImmutableMBeanAttribute;
import org.glowroot.agent.util.LazyPlatformMBeanServer;
import org.glowroot.common.util.Clock;
import org.glowroot.wire.api.model.CollectorServiceOuterClass.GaugeValue;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
public class GaugeCollectorTest {
private GaugeCollector gaugeCollector;
private LazyPlatformMBeanServer lazyPlatformMBeanServer;
private Clock clock;
private Ticker ticker;
private Logger logger;
@Before
public void beforeEachTest() throws Exception {
ConfigService configService = mock(ConfigService.class);
AdvancedConfig advancedConfig =
ImmutableAdvancedConfig.builder().mbeanGaugeNotFoundDelaySeconds(60).build();
when(configService.getAdvancedConfig()).thenReturn(advancedConfig);
Collector collector = mock(Collector.class);
lazyPlatformMBeanServer = mock(LazyPlatformMBeanServer.class);
clock = mock(Clock.class);
ticker = mock(Ticker.class);
logger = mock(Logger.class);
setLogger(GaugeCollector.class, logger);
gaugeCollector = new GaugeCollector(configService, collector, lazyPlatformMBeanServer,
clock, ticker);
}
@After
public void afterEachTest() {
verifyNoMoreInteractions(logger);
}
@Test
public void shouldCaptureNonCounterGauge() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("test:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenReturn(555);
// when
List<GaugeValue> gaugeValues = gaugeCollector.collectGaugeValues(gaugeConfig);
// then
assertThat(gaugeValues).hasSize(1);
assertThat(gaugeValues.get(0).getValue()).isEqualTo(555);
assertThat(gaugeValues.get(0).getWeight()).isEqualTo(1);
}
@Test
public void shouldNotCaptureCounterGauge() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("test:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", true))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenReturn(555);
// need to execute run() once in order to initialize internal priorRawCounterValues map
gaugeCollector.run();
// when
List<GaugeValue> gaugeValues = gaugeCollector.collectGaugeValues(gaugeConfig);
// then
assertThat(gaugeValues).isEmpty();
}
@Test
public void shouldCaptureCounterGauge() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("test:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", true))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenReturn(555, 565);
when(ticker.read()).thenReturn(SECONDS.toNanos(1), SECONDS.toNanos(3));
// need to execute run() once in order to initialize internal priorRawCounterValues map
gaugeCollector.run();
// when
gaugeCollector.collectGaugeValues(gaugeConfig);
List<GaugeValue> gaugeValues = gaugeCollector.collectGaugeValues(gaugeConfig);
// then
assertThat(gaugeValues).hasSize(1);
assertThat(gaugeValues.get(0).getValue()).isEqualTo(5); // 5 "units" per second
assertThat(gaugeValues.get(0).getWeight()).isEqualTo(SECONDS.toNanos(2));
}
@Test
public void shouldHandleInvalidMBeanObjectName() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("invalid mbean object name")
.build();
// when
List<GaugeValue> gaugeValues = gaugeCollector.collectGaugeValues(gaugeConfig);
// then
assertThat(gaugeValues).isEmpty();
verify(logger).debug(anyString(), any(Exception.class));
verify(logger).warn(eq("error accessing mbean: {}"), eq("invalid mbean object name"),
any(MalformedObjectNameException.class));
}
@Test
public void shouldHandleMBeanInstanceNotFoundBeforeLoggingDelay() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(clock.currentTimeMillis()).thenReturn(59999L);
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenThrow(InstanceNotFoundException.class);
// when
List<GaugeValue> gaugeValues = gaugeCollector.collectGaugeValues(gaugeConfig);
// then
assertThat(gaugeValues).isEmpty();
verify(logger).debug(nullable(String.class), any(Exception.class));
}
@Test
public void shouldHandleMBeanInstanceNotFoundAfterLoggingDelay() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(clock.currentTimeMillis()).thenReturn(60000L);
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenThrow(InstanceNotFoundException.class);
// when
List<GaugeValue> gaugeValues = gaugeCollector.collectGaugeValues(gaugeConfig);
// then
assertThat(gaugeValues).isEmpty();
verify(logger).debug(nullable(String.class), any(Exception.class));
verify(logger).warn("mbean not {}: {}", "found", "xyz:aaa=bbb");
}
@Test
public void shouldHandleMBeanInstanceNotFoundBeforeAndAfterLoggingDelay() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(clock.currentTimeMillis()).thenReturn(0L).thenReturn(30000L).thenReturn(60000L);
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenThrow(InstanceNotFoundException.class);
// when
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
// then
verify(logger, times(5)).debug(nullable(String.class), any(Exception.class));
verify(logger)
.warn("mbean not {}: {} (waited {} seconds after jvm startup before logging this"
+ " warning to allow time for mbean registration - this wait time can be"
+ " changed under Configuration > Advanced)", "found", "xyz:aaa=bbb", 60);
}
@Test
public void shouldHandleMBeanAttributeNotFound() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenThrow(AttributeNotFoundException.class);
// when
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
// then
verify(logger, times(10)).debug(nullable(String.class), any(Exception.class));
verify(logger).warn("mbean attribute {} not found in {}", "ccc", "xyz:aaa=bbb");
verify(logger).warn("mbean attribute {} not found in {}", "ddd", "xyz:aaa=bbb");
}
@Test
public void shouldHandleMBeanAttributeOtherException() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenThrow(new RuntimeException("A msg"));
// when
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
// then
verify(logger, times(10)).debug(anyString(), any(Exception.class));
verify(logger).warn(eq("error accessing mbean attribute: {} {}"), eq("xyz:aaa=bbb"),
eq("ccc"), any(RuntimeException.class));
verify(logger).warn(eq("error accessing mbean attribute: {} {}"), eq("xyz:aaa=bbb"),
eq("ddd"), any(RuntimeException.class));
}
@Test
public void shouldHandleMBeanAttributeNotANumber() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenReturn("not a number");
// when
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
// then
verify(logger).warn("error accessing mbean attribute {} {}: {}", "xyz:aaa=bbb", "ccc",
"MBean attribute value is not a valid number: \"not a number\"");
verify(logger).warn("error accessing mbean attribute {} {}: {}", "xyz:aaa=bbb", "ddd",
"MBean attribute value is not a valid number: \"not a number\"");
}
@Test
public void shouldHandleMBeanAttributeNotANumberOrString() throws Exception {
// given
GaugeConfig gaugeConfig = ImmutableGaugeConfig.builder()
.mbeanObjectName("xyz:aaa=bbb")
.addMbeanAttributes(ImmutableMBeanAttribute.of("ccc", false))
.addMbeanAttributes(ImmutableMBeanAttribute.of("ddd", false))
.build();
when(lazyPlatformMBeanServer.getAttribute(any(ObjectName.class), anyString()))
.thenReturn(new Object());
// when
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
gaugeCollector.collectGaugeValues(gaugeConfig);
// then
verify(logger).warn("error accessing mbean attribute {} {}: {}", "xyz:aaa=bbb", "ccc",
"MBean attribute value is not a number or string");
verify(logger).warn("error accessing mbean attribute {} {}: {}", "xyz:aaa=bbb", "ddd",
"MBean attribute value is not a number or string");
}
private static void setLogger(Class<?> clazz, Logger logger) throws Exception {
Field loggerField = clazz.getDeclaredField("logger");
loggerField.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(loggerField, loggerField.getModifiers() & ~Modifier.FINAL);
loggerField.set(null, logger);
}
}