/* * Copyright 2014-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 javax.annotation.Nullable; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.io.CharStreams; import org.immutables.value.Value; import org.glowroot.common.live.ImmutableOverallQuery; import org.glowroot.common.live.ImmutableTransactionQuery; import org.glowroot.common.live.LiveAggregateRepository.OverallQuery; import org.glowroot.common.live.LiveAggregateRepository.ThroughputAggregate; import org.glowroot.common.live.LiveAggregateRepository.TransactionQuery; import org.glowroot.common.model.OverallErrorSummaryCollector.OverallErrorSummary; import org.glowroot.common.model.Result; import org.glowroot.common.model.TransactionErrorSummaryCollector.ErrorSummarySortOrder; import org.glowroot.common.model.TransactionErrorSummaryCollector.TransactionErrorSummary; import org.glowroot.common.repo.ImmutableErrorMessageFilter; import org.glowroot.common.repo.ImmutableTraceQuery; import org.glowroot.common.repo.TraceRepository; import org.glowroot.common.repo.TraceRepository.ErrorMessageCount; import org.glowroot.common.repo.TraceRepository.ErrorMessageFilter; import org.glowroot.common.repo.TraceRepository.ErrorMessagePoint; import org.glowroot.common.repo.TraceRepository.ErrorMessageResult; import org.glowroot.common.repo.TraceRepository.TraceQuery; import org.glowroot.common.repo.util.RollupLevelService; import org.glowroot.common.util.Clock; import org.glowroot.common.util.ObjectMappers; import org.glowroot.common.util.Styles; @JsonService class ErrorJsonService { private static final ObjectMapper mapper; static { mapper = ObjectMappers.create(); mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); } private final ErrorCommonService errorCommonService; private final TransactionCommonService transactionCommonService; private final TraceRepository traceRepository; private final RollupLevelService rollupLevelService; private final Clock clock; ErrorJsonService(ErrorCommonService errorCommonService, TransactionCommonService transactionCommonService, TraceRepository traceRepository, RollupLevelService rollupLevelService, Clock clock) { this.errorCommonService = errorCommonService; this.transactionCommonService = transactionCommonService; this.traceRepository = traceRepository; this.rollupLevelService = rollupLevelService; this.clock = clock; } @GET(path = "/backend/error/messages", permission = "agent:error:overview") String getData(@BindAgentRollupId String agentRollupId, @BindRequest ErrorMessageRequest request, @BindAutoRefresh boolean autoRefresh) throws Exception { TraceQuery query = ImmutableTraceQuery.builder() .transactionType(request.transactionType()) .transactionName(request.transactionName()) .from(request.from()) .to(request.to()) .build(); TransactionQuery transactionQuery = ImmutableTransactionQuery.builder() .transactionType(request.transactionType()) .transactionName(request.transactionName()) .from(request.from()) .to(request.to()) .rollupLevel(rollupLevelService.getRollupLevelForView(request.from(), request.to())) .build(); ErrorMessageFilter filter = ImmutableErrorMessageFilter.builder() .addAllIncludes(request.include()) .addAllExcludes(request.exclude()) .build(); long liveCaptureTime = clock.currentTimeMillis(); List<ThroughputAggregate> throughputAggregates = transactionCommonService .getThroughputAggregates(agentRollupId, transactionQuery, autoRefresh); DataSeries dataSeries = new DataSeries(null); Map<Long, Long[]> dataSeriesExtra = Maps.newHashMap(); Map<Long, Long> transactionCountMap = Maps.newHashMap(); for (ThroughputAggregate unfilteredErrorPoint : throughputAggregates) { transactionCountMap.put(unfilteredErrorPoint.captureTime(), unfilteredErrorPoint.transactionCount()); } List<ErrorMessageCount> records = Lists.newArrayList(); boolean moreAvailable = false; if (!throughputAggregates.isEmpty()) { long maxCaptureTime = throughputAggregates.get(throughputAggregates.size() - 1).captureTime(); long resolutionMillis = rollupLevelService.getDataPointIntervalMillis(query.from(), query.to()); ErrorMessageResult result = traceRepository.readErrorMessages(agentRollupId, ImmutableTraceQuery.builder().copyFrom(query).to(maxCaptureTime).build(), filter, resolutionMillis, request.errorMessageLimit()); List<ErrorPoint> errorPoints = Lists.newArrayList(); for (ErrorMessagePoint traceErrorPoint : result.points()) { long captureTime = traceErrorPoint.captureTime(); if (captureTime > maxCaptureTime) { // traceRepository.readErrorMessages() returns capture time on resolutionMillis, // while throughputAggregates may return last capture time at a finer rollup // level captureTime = maxCaptureTime; } Long transactionCount = transactionCountMap.get(captureTime); if (transactionCount != null) { errorPoints.add(ImmutableErrorPoint.of(captureTime, traceErrorPoint.errorCount(), transactionCount)); } } populateDataSeries(query, errorPoints, dataSeries, dataSeriesExtra, liveCaptureTime); records = result.counts().records(); moreAvailable = result.counts().moreAvailable(); } StringBuilder sb = new StringBuilder(); JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb)); jg.writeStartObject(); jg.writeObjectField("dataSeries", dataSeries); jg.writeObjectField("dataSeriesExtra", dataSeriesExtra); jg.writeObjectField("errorMessages", records); jg.writeBooleanField("moreErrorMessagesAvailable", moreAvailable); jg.writeEndObject(); jg.close(); return sb.toString(); } @GET(path = "/backend/error/summaries", permission = "agent:error:overview") String getSummaries(@BindAgentRollupId String agentRollupId, @BindRequest ErrorSummaryRequest request, @BindAutoRefresh boolean autoRefresh) throws Exception { OverallQuery query = ImmutableOverallQuery.builder() .transactionType(request.transactionType()) .from(request.from()) .to(request.to()) .rollupLevel(rollupLevelService.getRollupLevelForView(request.from(), request.to())) .build(); OverallErrorSummary overallSummary = errorCommonService.readOverallErrorSummary(agentRollupId, query, autoRefresh); Result<TransactionErrorSummary> queryResult = errorCommonService.readTransactionErrorSummaries(agentRollupId, query, request.sortOrder(), request.limit(), autoRefresh); StringBuilder sb = new StringBuilder(); JsonGenerator jg = mapper.getFactory().createGenerator(CharStreams.asWriter(sb)); jg.writeStartObject(); jg.writeObjectField("overall", overallSummary); jg.writeObjectField("transactions", queryResult.records()); jg.writeBooleanField("moreAvailable", queryResult.moreAvailable()); jg.writeEndObject(); jg.close(); return sb.toString(); } private void populateDataSeries(TraceQuery query, List<ErrorPoint> errorPoints, DataSeries dataSeries, Map<Long, Long[]> dataSeriesExtra, long liveCaptureTime) throws Exception { DataSeriesHelper dataSeriesHelper = new DataSeriesHelper(liveCaptureTime, rollupLevelService.getDataPointIntervalMillis(query.from(), query.to())); ErrorPoint lastErrorPoint = null; for (ErrorPoint errorPoint : errorPoints) { if (lastErrorPoint == null) { // first aggregate dataSeriesHelper.addInitialUpslopeIfNeeded(query.from(), errorPoint.captureTime(), dataSeries); } else { dataSeriesHelper.addGapIfNeeded(lastErrorPoint.captureTime(), errorPoint.captureTime(), dataSeries); } lastErrorPoint = errorPoint; long transactionCount = errorPoint.transactionCount(); dataSeries.add(errorPoint.captureTime(), 100 * errorPoint.errorCount() / (double) transactionCount); dataSeriesExtra.put(errorPoint.captureTime(), new Long[] {errorPoint.errorCount(), transactionCount}); } if (lastErrorPoint != null) { dataSeriesHelper.addFinalDownslopeIfNeeded(dataSeries, lastErrorPoint.captureTime()); } } @Value.Immutable @Styles.AllParameters interface ErrorPoint { long captureTime(); long errorCount(); long transactionCount(); } @Value.Immutable interface ErrorSummaryRequest { String transactionType(); long from(); long to(); ErrorSummarySortOrder sortOrder(); int limit(); } @Value.Immutable interface ErrorMessageRequest { String transactionType(); @Nullable String transactionName(); long from(); long to(); // intentionally not plural since maps from query string ImmutableList<String> include(); // intentionally not plural since maps from query string ImmutableList<String> exclude(); int errorMessageLimit(); } }