/*
* Copyright 2013-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.ui;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.io.CharStreams;
import org.immutables.value.Value;
import org.glowroot.common.repo.ConfigRepository;
import org.glowroot.common.repo.ImmutableSyntheticResult;
import org.glowroot.common.repo.SyntheticResultRepository;
import org.glowroot.common.repo.SyntheticResultRepository.SyntheticResult;
import org.glowroot.common.repo.Utils;
import org.glowroot.common.repo.util.RollupLevelService;
import org.glowroot.common.util.ObjectMappers;
import org.glowroot.common.util.Styles;
import org.glowroot.wire.api.model.AgentConfigOuterClass.AgentConfig.SyntheticMonitorConfig;
import static com.google.common.base.Preconditions.checkNotNull;
@JsonService
class SyntheticResultJsonService {
private static final double NANOSECONDS_PER_MILLISECOND = 1000000.0;
private static final ObjectMapper mapper = ObjectMappers.create();
private final SyntheticResultRepository syntheticResultRepository;
private final RollupLevelService rollupLevelService;
private final ConfigRepository configRepository;
SyntheticResultJsonService(SyntheticResultRepository syntheticResultRepository,
RollupLevelService rollupLevelService, ConfigRepository configRepository) {
this.syntheticResultRepository = syntheticResultRepository;
this.rollupLevelService = rollupLevelService;
this.configRepository = configRepository;
}
@GET(path = "/backend/synthetic-monitor/results", permission = "agent:syntheticMonitor")
String getSyntheticResults(@BindAgentRollupId String agentRollupId,
@BindRequest SyntheticResultRequest request) throws Exception {
int rollupLevel = rollupLevelService.getRollupLevelForView(request.from(), request.to());
long intervalMillis = configRepository.getRollupConfigs().get(rollupLevel).intervalMillis();
double gapMillis = intervalMillis * 1.5;
long revisedFrom = request.from() - intervalMillis;
long revisedTo = request.to() + intervalMillis;
Map<String, List<SyntheticResult>> map = Maps.newLinkedHashMap();
for (String syntheticMonitorId : request.syntheticMonitorId()) {
map.put(syntheticMonitorId, getSyntheticResults(agentRollupId, revisedFrom, revisedTo,
syntheticMonitorId, rollupLevel));
}
if (rollupLevel != 0) {
syncManualRollupCaptureTimes(map, rollupLevel);
}
List<DataSeries> dataSeriesList = Lists.newArrayList();
for (Entry<String, List<SyntheticResult>> entry : map.entrySet()) {
String syntheticMonitorId = entry.getKey();
SyntheticMonitorConfig config =
configRepository.getSyntheticMonitorConfig(agentRollupId, syntheticMonitorId);
if (config == null) {
throw new IllegalStateException(
"Synthetic monitor not found: " + syntheticMonitorId);
}
dataSeriesList.add(
convertToDataSeriesWithGaps(config.getDisplay(), entry.getValue(), gapMillis));
}
StringBuilder sb = new StringBuilder();
JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb));
jg.writeStartObject();
jg.writeObjectField("dataSeries", dataSeriesList);
jg.writeEndObject();
jg.close();
return sb.toString();
}
@GET(path = "/backend/synthetic-monitor/all-monitors", permission = "agent:syntheticMonitor")
String getAllConfigs(@BindAgentRollupId String agentRollupId) throws Exception {
List<SyntheticMonitor> syntheticMonitors = Lists.newArrayList();
List<SyntheticMonitorConfig> configs =
configRepository.getSyntheticMonitorConfigs(agentRollupId);
for (SyntheticMonitorConfig config : configs) {
syntheticMonitors
.add(ImmutableSyntheticMonitor.of(config.getId(), config.getDisplay()));
}
ImmutableList<SyntheticMonitor> sortedSyntheticMonitors =
new SyntheticMonitorOrdering().immutableSortedCopy(syntheticMonitors);
return mapper.writeValueAsString(sortedSyntheticMonitors);
}
private List<SyntheticResult> getSyntheticResults(String agentRollupId, long from, long to,
String syntheticMonitorId, int rollupLevel) throws Exception {
List<SyntheticResult> syntheticResults = syntheticResultRepository
.readSyntheticResults(agentRollupId, syntheticMonitorId, from, to, rollupLevel);
if (rollupLevel == 0) {
return syntheticResults;
}
long nonRolledUpFrom = from;
if (!syntheticResults.isEmpty()) {
long lastRolledUpTime = syntheticResults.get(syntheticResults.size() - 1).captureTime();
nonRolledUpFrom = Math.max(nonRolledUpFrom, lastRolledUpTime + 1);
}
List<SyntheticResult> orderedNonRolledUpSyntheticResults = Lists.newArrayList();
orderedNonRolledUpSyntheticResults
.addAll(syntheticResultRepository.readSyntheticResults(agentRollupId,
syntheticMonitorId, nonRolledUpFrom, to, 0));
syntheticResults = Lists.newArrayList(syntheticResults);
long fixedIntervalMillis =
configRepository.getRollupConfigs().get(rollupLevel).intervalMillis();
syntheticResults.addAll(rollUpSyntheticResults(orderedNonRolledUpSyntheticResults,
new RollupCaptureTimeFn(fixedIntervalMillis)));
return syntheticResults;
}
private <K> void syncManualRollupCaptureTimes(Map<K, List<SyntheticResult>> map,
int rollupLevel) {
long fixedIntervalMillis =
configRepository.getRollupConfigs().get(rollupLevel - 1).intervalMillis();
Map<K, Long> manualRollupCaptureTimes = Maps.newHashMap();
long maxCaptureTime = Long.MIN_VALUE;
for (Entry<K, List<SyntheticResult>> entry : map.entrySet()) {
List<SyntheticResult> syntheticResults = entry.getValue();
if (syntheticResults.isEmpty()) {
continue;
}
SyntheticResult lastSyntheticResult = syntheticResults.get(syntheticResults.size() - 1);
long lastCaptureTime = lastSyntheticResult.captureTime();
maxCaptureTime = Math.max(maxCaptureTime, lastCaptureTime);
if (lastCaptureTime % fixedIntervalMillis != 0) {
manualRollupCaptureTimes.put(entry.getKey(), lastCaptureTime);
}
}
if (maxCaptureTime == Long.MIN_VALUE) {
// nothing to sync
return;
}
long maxRollupCaptureTime = Utils.getRollupCaptureTime(maxCaptureTime, fixedIntervalMillis);
long maxDiffToSync = Math.min(fixedIntervalMillis / 5, 60000);
for (Entry<K, Long> entry : manualRollupCaptureTimes.entrySet()) {
Long captureTime = entry.getValue();
if (Utils.getRollupCaptureTime(captureTime,
fixedIntervalMillis) != maxRollupCaptureTime) {
continue;
}
if (maxCaptureTime - captureTime > maxDiffToSync) {
// only sync up times that are close to each other
continue;
}
K key = entry.getKey();
List<SyntheticResult> syntheticResults = checkNotNull(map.get(key));
// make copy in case ImmutableList
syntheticResults = Lists.newArrayList(syntheticResults);
SyntheticResult lastSyntheticResult = syntheticResults.get(syntheticResults.size() - 1);
syntheticResults.set(syntheticResults.size() - 1, ImmutableSyntheticResult.builder()
.copyFrom(lastSyntheticResult)
.captureTime(maxCaptureTime)
.build());
map.put(key, syntheticResults);
}
}
static List<SyntheticResult> rollUpSyntheticResults(
List<SyntheticResult> orderedNonRolledUpSyntheticResults,
Function<Long, Long> rollupCaptureTimeFn) {
List<SyntheticResult> rolledUpSyntheticResults = Lists.newArrayList();
double totalDurationNanos = 0;
long executionCount = 0;
long errorCount = 0;
long currRollupCaptureTime = Long.MIN_VALUE;
for (SyntheticResult nonRolledUpSyntheticResult : orderedNonRolledUpSyntheticResults) {
long captureTime = nonRolledUpSyntheticResult.captureTime();
long rollupCaptureTime = rollupCaptureTimeFn.apply(captureTime);
if (rollupCaptureTime != currRollupCaptureTime && executionCount > 0) {
rolledUpSyntheticResults.add(ImmutableSyntheticResult.builder()
.captureTime(currRollupCaptureTime)
.totalDurationNanos(totalDurationNanos)
.executionCount(executionCount)
.errorCount(errorCount)
.build());
totalDurationNanos = 0;
executionCount = 0;
errorCount = 0;
}
currRollupCaptureTime = rollupCaptureTime;
totalDurationNanos += nonRolledUpSyntheticResult.totalDurationNanos();
executionCount += nonRolledUpSyntheticResult.executionCount();
errorCount += nonRolledUpSyntheticResult.errorCount();
}
if (executionCount > 0) {
// roll up final one
long lastCaptureTime = orderedNonRolledUpSyntheticResults
.get(orderedNonRolledUpSyntheticResults.size() - 1).captureTime();
rolledUpSyntheticResults.add(ImmutableSyntheticResult.builder()
.captureTime(lastCaptureTime)
.totalDurationNanos(totalDurationNanos)
.executionCount(executionCount)
.errorCount(errorCount)
.build());
}
return rolledUpSyntheticResults;
}
private static DataSeries convertToDataSeriesWithGaps(String dataSeriesName,
List<SyntheticResult> syntheticResults, double gapMillis) {
DataSeries dataSeries = new DataSeries(dataSeriesName);
SyntheticResult lastSyntheticResult = null;
for (SyntheticResult syntheticResult : syntheticResults) {
if (lastSyntheticResult != null
&& syntheticResult.captureTime()
- lastSyntheticResult.captureTime() > gapMillis) {
dataSeries.addNull();
}
dataSeries.add(syntheticResult.captureTime(),
(syntheticResult.totalDurationNanos() / syntheticResult.executionCount())
/ NANOSECONDS_PER_MILLISECOND);
lastSyntheticResult = syntheticResult;
}
return dataSeries;
}
@Value.Immutable
interface SyntheticResultRequest {
long from();
long to();
// singular because this is used in query string
ImmutableList<String> syntheticMonitorId();
}
static class SyntheticMonitorOrdering extends Ordering<SyntheticMonitor> {
@Override
public int compare(SyntheticMonitor left, SyntheticMonitor right) {
return left.display().compareToIgnoreCase(right.display());
}
}
private static class RollupCaptureTimeFn implements Function<Long, Long> {
private final long fixedIntervalMillis;
private RollupCaptureTimeFn(long fixedIntervalMillis) {
this.fixedIntervalMillis = fixedIntervalMillis;
}
@Override
public Long apply(Long captureTime) {
return Utils.getRollupCaptureTime(captureTime, fixedIntervalMillis);
}
}
@Value.Immutable
@Styles.AllParameters
interface SyntheticMonitor {
String id();
String display();
}
}