/*
* 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.common.repo.util;
import java.text.DecimalFormat;
import javax.crypto.SecretKey;
import javax.mail.Message;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
import org.glowroot.common.config.ImmutableSmtpConfig;
import org.glowroot.common.config.SmtpConfig;
import org.glowroot.common.live.ImmutablePercentileAggregate;
import org.glowroot.common.live.ImmutableTransactionQuery;
import org.glowroot.common.live.LiveAggregateRepository.PercentileAggregate;
import org.glowroot.common.model.LazyHistogram;
import org.glowroot.common.model.LazyHistogram.ScratchBuffer;
import org.glowroot.common.repo.AgentRepository;
import org.glowroot.common.repo.AgentRepository.AgentRollup;
import org.glowroot.common.repo.AggregateRepository;
import org.glowroot.common.repo.ConfigRepository;
import org.glowroot.common.repo.GaugeValueRepository;
import org.glowroot.common.repo.ImmutableAgentRollup;
import org.glowroot.common.repo.TriggeredAlertRepository;
import org.glowroot.common.repo.Utils;
import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AlertConfig;
import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.AlertConfig.AlertKind;
import org.glowroot.wire.api.model.CollectorServiceOuterClass.GaugeValue;
import org.glowroot.wire.api.model.Proto.OptionalDouble;
import org.glowroot.wire.api.model.Proto.OptionalInt32;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class AlertingServiceTest {
private static final String AGENT_ID = "";
private static final AlertConfig TRANSACTION_ALERT_CONFIG = AlertConfig.newBuilder()
.setKind(AlertKind.TRANSACTION)
.setTransactionType("tt")
.setTransactionPercentile(OptionalDouble.newBuilder().setValue(95.0))
.setThresholdMillis(OptionalInt32.newBuilder().setValue(1))
.setTimePeriodSeconds(60)
.setMinTransactionCount(OptionalInt32.newBuilder().setValue(0))
.setGaugeName("")
.addEmailAddress("to@example.org")
.build();
private static final AlertConfig GAUGE_ALERT_CONFIG = AlertConfig.newBuilder()
.setKind(AlertKind.GAUGE)
.setGaugeName("java.lang:type=GarbageCollector,name=ConcurrentMarkSweep"
+ ":CollectionTime[counter]")
.setGaugeThreshold(OptionalDouble.newBuilder().setValue(500.0))
.setTimePeriodSeconds(60)
.setMinTransactionCount(OptionalInt32.newBuilder().setValue(0))
.setTransactionType("")
.addEmailAddress("to@example.org")
.build();
private static final LazySecretKey LAZY_SECRET_KEY;
private static final SmtpConfig SMTP_CONFIG;
static {
try {
SecretKey secretKey = Encryption.generateNewKey();
LAZY_SECRET_KEY = mock(LazySecretKey.class);
when(LAZY_SECRET_KEY.getOrCreate()).thenReturn(secretKey);
when(LAZY_SECRET_KEY.getExisting()).thenReturn(secretKey);
SMTP_CONFIG = ImmutableSmtpConfig.builder()
.host("localhost")
.ssl(true)
.username("u")
.password(Encryption.encrypt("test", LAZY_SECRET_KEY))
.putAdditionalProperties("a", "x")
.putAdditionalProperties("b", "y")
.build();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
private ConfigRepository configRepository;
private AgentRepository agentRepository;
private TriggeredAlertRepository triggeredAlertRepository;
private AggregateRepository aggregateRepository;
private GaugeValueRepository gaugeValueRepository;
private RollupLevelService rollupLevelService;
private MockMailService mailService;
@Before
public void beforeEachTest() throws Exception {
configRepository = mock(ConfigRepository.class);
agentRepository = mock(AgentRepository.class);
when(agentRepository.readAgentRollups()).thenReturn(ImmutableList.<AgentRollup>of(
ImmutableAgentRollup.builder()
.id("")
.display("")
.agent(true)
.build()));
triggeredAlertRepository = mock(TriggeredAlertRepository.class);
aggregateRepository = mock(AggregateRepository.class);
gaugeValueRepository = mock(GaugeValueRepository.class);
rollupLevelService = mock(RollupLevelService.class);
mailService = new MockMailService();
when(configRepository.getLazySecretKey()).thenReturn(LAZY_SECRET_KEY);
when(configRepository.getSmtpConfig()).thenReturn(SMTP_CONFIG);
}
@Test
public void shouldSendMailForTransactionAlert() throws Exception {
// given
setupForTransaction(1000000);
AlertingService alertingService = new AlertingService(configRepository,
triggeredAlertRepository, aggregateRepository, gaugeValueRepository,
rollupLevelService, mailService);
// when
alertingService.checkTransactionAlert("", "", TRANSACTION_ALERT_CONFIG, 120000);
// then
assertThat(mailService.getMessage()).isNotNull();
assertThat(((String) mailService.getMessage().getContent()).trim())
.isEqualTo("95th percentile over the last 1 minute exceeded alert threshold of"
+ " 1 millisecond.");
}
@Test
public void shouldNotSendMailForTransactionAlert() throws Exception {
// given
setupForTransaction(999000);
AlertingService alertingService = new AlertingService(configRepository,
triggeredAlertRepository, aggregateRepository, gaugeValueRepository,
rollupLevelService, mailService);
// when
alertingService.checkTransactionAlert("", "", TRANSACTION_ALERT_CONFIG, 120000);
// then
assertThat(mailService.getMessage()).isNull();
}
@Test
public void shouldSendMailForGaugeAlert() throws Exception {
// given
setupForGauge(500.1);
AlertingService alertingService = new AlertingService(configRepository,
triggeredAlertRepository, aggregateRepository, gaugeValueRepository,
rollupLevelService, mailService);
// when
alertingService.checkGaugeAlert("", "", GAUGE_ALERT_CONFIG, 120000);
// then
assertThat(mailService.getMessage()).isNotNull();
assertThat(((String) mailService.getMessage().getContent()).trim())
.isEqualTo("Average over the last 1 minute exceeded alert threshold of"
+ " 500 milliseconds per second.");
}
@Test
public void shouldNotSendMailForGaugeAlert() throws Exception {
// given
setupForGauge(499);
AlertingService alertingService = new AlertingService(configRepository,
triggeredAlertRepository, aggregateRepository, gaugeValueRepository,
rollupLevelService, mailService);
// when
alertingService.checkGaugeAlert("", "", GAUGE_ALERT_CONFIG, 120000);
// then
assertThat(mailService.getMessage()).isNull();
}
@Test
public void shouldReturnCorrectPercentileName() {
shouldReturnCorrectPercentileName(0, "th");
shouldReturnCorrectPercentileName(1, "st");
shouldReturnCorrectPercentileName(2, "nd");
shouldReturnCorrectPercentileName(3, "rd");
shouldReturnCorrectPercentileName(4, "th");
shouldReturnCorrectPercentileName(9, "th");
shouldReturnCorrectPercentileName(10, "th");
shouldReturnCorrectPercentileName(11, "th");
shouldReturnCorrectPercentileName(12, "th");
shouldReturnCorrectPercentileName(13, "th");
shouldReturnCorrectPercentileName(14, "th");
shouldReturnCorrectPercentileName(20, "th");
shouldReturnCorrectPercentileName(21, "st");
shouldReturnCorrectPercentileName(22, "nd");
shouldReturnCorrectPercentileName(23, "rd");
shouldReturnCorrectPercentileName(24, "th");
shouldReturnCorrectPercentileName(50, "th");
shouldReturnCorrectPercentileName(50.1, "st");
shouldReturnCorrectPercentileName(50.2, "nd");
shouldReturnCorrectPercentileName(50.3, "rd");
shouldReturnCorrectPercentileName(50.4, "th");
shouldReturnCorrectPercentileName(50.11, "th");
shouldReturnCorrectPercentileName(50.12, "th");
shouldReturnCorrectPercentileName(50.13, "th");
shouldReturnCorrectPercentileName(50.14, "th");
shouldReturnCorrectPercentileName(50.2, "nd");
shouldReturnCorrectPercentileName(50.21, "st");
shouldReturnCorrectPercentileName(50.22, "nd");
shouldReturnCorrectPercentileName(50.23, "rd");
shouldReturnCorrectPercentileName(50.24, "th");
}
private void setupForTransaction(long... histogramValues) throws Exception {
LazyHistogram lazyHistogram = new LazyHistogram();
for (long histogramValue : histogramValues) {
lazyHistogram.add(histogramValue);
}
PercentileAggregate aggregate = ImmutablePercentileAggregate.builder()
.captureTime(120000)
.totalDurationNanos(1000000)
.transactionCount(1)
.durationNanosHistogram(lazyHistogram.toProto(new ScratchBuffer()))
.build();
ImmutableTransactionQuery query = ImmutableTransactionQuery.builder()
.transactionType("tt")
.from(60001)
.to(120000)
.rollupLevel(0)
.build();
when(aggregateRepository.readPercentileAggregates(AGENT_ID, query))
.thenReturn(ImmutableList.of(aggregate));
}
private void setupForGauge(double value) throws Exception {
GaugeValue gaugeValue = GaugeValue.newBuilder()
.setGaugeName("abc")
.setCaptureTime(120000)
.setValue(value)
.setWeight(1)
.build();
when(gaugeValueRepository.readGaugeValues(AGENT_ID,
"java.lang:type=GarbageCollector,name=ConcurrentMarkSweep:CollectionTime[counter]",
60001, 120000, 0)).thenReturn(ImmutableList.of(gaugeValue));
}
private static void shouldReturnCorrectPercentileName(double percentile, String suffix) {
assertThat(Utils.getPercentileWithSuffix(percentile))
.isEqualTo(new DecimalFormat().format(percentile) + suffix);
}
static class MockMailService extends MailService {
private Message msg;
@Override
public void send(Message msg) {
this.msg = msg;
}
public Message getMessage() {
return msg;
}
}
}