Java tutorial
/* * Copyright 2013-2019 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.Set; 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.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; import com.google.common.io.CharStreams; import org.immutables.value.Value; import org.glowroot.common.util.CaptureTimes; import org.glowroot.common.util.ObjectMappers; import org.glowroot.common2.repo.ConfigRepository; import org.glowroot.common2.repo.ConfigRepository.RollupConfig; import org.glowroot.common2.repo.GaugeValueRepository; import org.glowroot.common2.repo.GaugeValueRepository.Gauge; import org.glowroot.common2.repo.ImmutableGauge; import org.glowroot.common2.repo.util.RollupLevelService; import org.glowroot.wire.api.model.CollectorServiceOuterClass.GaugeValueMessage.GaugeValue; import static com.google.common.base.Preconditions.checkNotNull; @JsonService class GaugeValueJsonService { private static final ObjectMapper mapper = ObjectMappers.create(); private final GaugeValueRepository gaugeValueRepository; private final RollupLevelService rollupLevelService; private final ConfigRepository configRepository; GaugeValueJsonService(GaugeValueRepository gaugeValueRepository, RollupLevelService rollupLevelService, ConfigRepository configRepository) { this.gaugeValueRepository = gaugeValueRepository; this.rollupLevelService = rollupLevelService; this.configRepository = configRepository; } @GET(path = "/backend/jvm/gauges", permission = "agent:jvm:gauges") String getGaugeValues(@BindAgentRollupId String agentRollupId, @BindRequest GaugeValueRequest request) throws Exception { int rollupLevel = rollupLevelService.getGaugeRollupLevelForView(request.from(), request.to(), agentRollupId.endsWith("::")); long dataPointIntervalMillis; if (rollupLevel == 0) { dataPointIntervalMillis = configRepository.getGaugeCollectionIntervalMillis(); } else { dataPointIntervalMillis = configRepository.getRollupConfigs().get(rollupLevel - 1).intervalMillis(); } Map<String, List<GaugeValue>> origGaugeValues = getGaugeValues(agentRollupId, request, rollupLevel, dataPointIntervalMillis); Map<String, List<GaugeValue>> gaugeValues = origGaugeValues; if (isEmpty(gaugeValues) && noHarmFallingBackToLargestAggregate(agentRollupId, rollupLevel, request)) { // fall back to largest aggregates in case expiration settings have recently changed rollupLevel = getLargestRollupLevel(); dataPointIntervalMillis = configRepository.getRollupConfigs().get(rollupLevel - 1).intervalMillis(); gaugeValues = getGaugeValues(agentRollupId, request, rollupLevel, dataPointIntervalMillis); long lastCaptureTime = 0; for (List<GaugeValue> list : gaugeValues.values()) { if (!list.isEmpty()) { lastCaptureTime = Math.max(lastCaptureTime, Iterables.getLast(list).getCaptureTime()); } } if (lastCaptureTime != 0 && ignoreFallBackData(request, lastCaptureTime)) { // this is probably data from before the requested time period // (go back to empty gauge values) gaugeValues = origGaugeValues; } } if (rollupLevel != 0) { syncManualRollupCaptureTimes(gaugeValues, rollupLevel); } double gapMillis = dataPointIntervalMillis * 1.5; List<DataSeries> dataSeriesList = Lists.newArrayList(); for (Map.Entry<String, List<GaugeValue>> entry : gaugeValues.entrySet()) { dataSeriesList.add(convertToDataSeriesWithGaps(entry.getKey(), entry.getValue(), gapMillis)); } List<Gauge> gauges = gaugeValueRepository.getGauges(agentRollupId, request.from(), request.to()); List<Gauge> sortedGauges = new GaugeOrdering().immutableSortedCopy(gauges); sortedGauges = addCounterSuffixesIfAndWhereNeeded(sortedGauges); StringBuilder sb = new StringBuilder(); JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb)); try { jg.writeStartObject(); jg.writeObjectField("dataSeries", dataSeriesList); jg.writeNumberField("dataPointIntervalMillis", dataPointIntervalMillis); jg.writeObjectField("allGauges", sortedGauges); jg.writeEndObject(); } finally { jg.close(); } return sb.toString(); } private Map<String, List<GaugeValue>> getGaugeValues(String agentRollupId, GaugeValueRequest request, int rollupLevel, long dataPointIntervalMillis) throws Exception { long revisedFrom = request.from() - dataPointIntervalMillis; long revisedTo = request.to() + dataPointIntervalMillis; Map<String, List<GaugeValue>> map = Maps.newLinkedHashMap(); for (String gaugeName : request.gaugeName()) { List<GaugeValue> gaugeValues = getGaugeValues(agentRollupId, revisedFrom, revisedTo, gaugeName, rollupLevel); map.put(gaugeName, gaugeValues); } return map; } private List<GaugeValue> getGaugeValues(String agentRollupId, long from, long to, String gaugeName, int rollupLevel) throws Exception { List<GaugeValue> gaugeValues = gaugeValueRepository.readGaugeValues(agentRollupId, gaugeName, from, to, rollupLevel); if (rollupLevel == 0) { return gaugeValues; } long nonRolledUpFrom = from; if (!gaugeValues.isEmpty()) { nonRolledUpFrom = Iterables.getLast(gaugeValues).getCaptureTime() + 1; } List<GaugeValue> orderedNonRolledUpGaugeValues = Lists.newArrayList(); int lowestLevel = agentRollupId.endsWith("::") ? 1 : 0; orderedNonRolledUpGaugeValues.addAll( gaugeValueRepository.readGaugeValues(agentRollupId, gaugeName, nonRolledUpFrom, to, lowestLevel)); gaugeValues = Lists.newArrayList(gaugeValues); long fixedIntervalMillis = configRepository.getRollupConfigs().get(rollupLevel - 1).intervalMillis(); gaugeValues.addAll(rollUpGaugeValues(orderedNonRolledUpGaugeValues, gaugeName, new RollupCaptureTimeFn(fixedIntervalMillis))); return gaugeValues; } private <K> void syncManualRollupCaptureTimes(Map<K, List<GaugeValue>> map, int rollupLevel) { long fixedIntervalMillis = configRepository.getRollupConfigs().get(rollupLevel - 1).intervalMillis(); Map<K, Long> manualRollupCaptureTimes = Maps.newHashMap(); long maxCaptureTime = Long.MIN_VALUE; for (Map.Entry<K, List<GaugeValue>> entry : map.entrySet()) { List<GaugeValue> gaugeValues = entry.getValue(); if (gaugeValues.isEmpty()) { continue; } long lastCaptureTime = Iterables.getLast(gaugeValues).getCaptureTime(); 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 = CaptureTimes.getRollup(maxCaptureTime, fixedIntervalMillis); long maxDiffToSync = Math.min(fixedIntervalMillis / 5, 60000); for (Map.Entry<K, Long> entry : manualRollupCaptureTimes.entrySet()) { Long captureTime = entry.getValue(); if (CaptureTimes.getRollup(captureTime, fixedIntervalMillis) != maxRollupCaptureTime) { continue; } if (maxCaptureTime - captureTime > maxDiffToSync) { // only sync up times that are close to each other continue; } K key = entry.getKey(); List<GaugeValue> gaugeValues = checkNotNull(map.get(key)); // make copy in case ImmutableList gaugeValues = Lists.newArrayList(gaugeValues); GaugeValue lastGaugeValue = Iterables.getLast(gaugeValues); gaugeValues.set(gaugeValues.size() - 1, lastGaugeValue.toBuilder().setCaptureTime(maxCaptureTime).build()); map.put(key, gaugeValues); } } private boolean noHarmFallingBackToLargestAggregate(String agentRollupId, int rollupLevel, GaugeValueRequest request) throws Exception { if (rollupLevel == getLargestRollupLevel()) { return false; } for (String gaugeName : request.gaugeName()) { long oldestCaptureTime = gaugeValueRepository.getOldestCaptureTime(agentRollupId, gaugeName, rollupLevel); if (oldestCaptureTime < request.to()) { return false; } } return true; } private boolean ignoreFallBackData(GaugeValueRequest request, long lastCaptureTime) { return lastCaptureTime < request.from() + getLargestRollupIntervalMillis(); } private int getLargestRollupLevel() { return configRepository.getRollupConfigs().size(); } private long getLargestRollupIntervalMillis() { List<RollupConfig> rollupConfigs = configRepository.getRollupConfigs(); return rollupConfigs.get(rollupConfigs.size() - 1).intervalMillis(); } static List<GaugeValue> rollUpGaugeValues(List<GaugeValue> orderedNonRolledUpGaugeValues, String gaugeName, Function<Long, Long> rollupCaptureTimeFn) { List<GaugeValue> rolledUpGaugeValues = Lists.newArrayList(); double currTotal = 0; long currWeight = 0; long currRollupCaptureTime = Long.MIN_VALUE; for (GaugeValue nonRolledUpGaugeValue : orderedNonRolledUpGaugeValues) { long captureTime = nonRolledUpGaugeValue.getCaptureTime(); long rollupCaptureTime = rollupCaptureTimeFn.apply(captureTime); if (rollupCaptureTime != currRollupCaptureTime && currWeight > 0) { rolledUpGaugeValues .add(GaugeValue.newBuilder().setGaugeName(gaugeName).setCaptureTime(currRollupCaptureTime) .setValue(currTotal / currWeight).setWeight(currWeight).build()); currTotal = 0; currWeight = 0; } currRollupCaptureTime = rollupCaptureTime; currTotal += nonRolledUpGaugeValue.getValue() * nonRolledUpGaugeValue.getWeight(); currWeight += nonRolledUpGaugeValue.getWeight(); } if (currWeight > 0) { // roll up final one long lastCaptureTime = Iterables.getLast(orderedNonRolledUpGaugeValues).getCaptureTime(); rolledUpGaugeValues.add(GaugeValue.newBuilder().setGaugeName(gaugeName).setCaptureTime(lastCaptureTime) .setValue(currTotal / currWeight).setWeight(currWeight).build()); } return rolledUpGaugeValues; } private static boolean isEmpty(Map<String, List<GaugeValue>> map) { for (List<GaugeValue> values : map.values()) { if (!values.isEmpty()) { return false; } } return true; } private static DataSeries convertToDataSeriesWithGaps(String dataSeriesName, List<GaugeValue> gaugeValues, double gapMillis) { DataSeries dataSeries = new DataSeries(dataSeriesName); GaugeValue lastGaugeValue = null; for (GaugeValue gaugeValue : gaugeValues) { if (lastGaugeValue != null && gaugeValue.getCaptureTime() - lastGaugeValue.getCaptureTime() > gapMillis) { dataSeries.addNull(); } dataSeries.add(gaugeValue.getCaptureTime(), gaugeValue.getValue()); lastGaugeValue = gaugeValue; } return dataSeries; } private static List<Gauge> addCounterSuffixesIfAndWhereNeeded(List<Gauge> gauges) { Set<String> nonCounterGaugeNames = Sets.newHashSet(); for (Gauge gauge : gauges) { if (!gauge.counter()) { nonCounterGaugeNames.add(gauge.name()); } } List<Gauge> updatedGauges = Lists.newArrayList(); for (Gauge gauge : gauges) { if (gauge.counter() && nonCounterGaugeNames .contains(gauge.name().substring(0, gauge.name().length() - "[counter]".length()))) { List<String> displayParts = Lists.newArrayList(gauge.displayParts()); displayParts.set(displayParts.size() - 1, displayParts.get(displayParts.size() - 1) + " (Counter)"); updatedGauges.add(ImmutableGauge.builder().copyFrom(gauge).display(gauge.display() + " (Counter)") .displayParts(displayParts).build()); } else { updatedGauges.add(gauge); } } return updatedGauges; } @Value.Immutable interface GaugeValueRequest { long from(); long to(); // singular because this is used in query string ImmutableList<String> gaugeName(); } @Value.Immutable interface AllGaugeResponse { List<Gauge> allGauges(); List<String> defaultGaugeNames(); } static class GaugeOrdering extends Ordering<Gauge> { @Override public int compare(Gauge left, Gauge 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 CaptureTimes.getRollup(captureTime, fixedIntervalMillis); } } }