All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glowroot.ui.TracePointJsonService Maven / Gradle / Ivy

There is a newer version: 0.9.28
Show newest version
/*
 * Copyright 2011-2015 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.io.IOException;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Nullable;

import org.glowroot.agent.shaded.fasterxml.jackson.core.JsonFactory;
import org.glowroot.agent.shaded.fasterxml.jackson.core.JsonGenerator;
import org.glowroot.agent.shaded.google.common.base.Ticker;
import org.glowroot.agent.shaded.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.google.common.collect.Lists;
import org.glowroot.agent.shaded.google.common.io.CharStreams;
import org.immutables.value.Value;

import org.glowroot.common.live.ImmutableTracePointQuery;
import org.glowroot.common.live.LiveTraceRepository;
import org.glowroot.common.live.LiveTraceRepository.TracePoint;
import org.glowroot.common.live.LiveTraceRepository.TracePointQuery;
import org.glowroot.common.live.StringComparator;
import org.glowroot.common.util.Clock;
import org.glowroot.storage.repo.ConfigRepository;
import org.glowroot.storage.repo.Result;
import org.glowroot.storage.repo.TraceRepository;

import static java.util.concurrent.TimeUnit.HOURS;

@JsonService
class TracePointJsonService {

    private static final double NANOSECONDS_PER_MILLISECOND = 1000000.0;

    private static final JsonFactory jsonFactory = new JsonFactory();

    private final TraceRepository traceRepository;
    private final LiveTraceRepository liveTraceRepository;
    private final ConfigRepository configRepository;
    private final Ticker ticker;
    private final Clock clock;

    TracePointJsonService(TraceRepository traceRepository, LiveTraceRepository liveTraceRepository,
            ConfigRepository configRepository, Ticker ticker, Clock clock) {
        this.traceRepository = traceRepository;
        this.liveTraceRepository = liveTraceRepository;
        this.configRepository = configRepository;
        this.ticker = ticker;
        this.clock = clock;
    }

    @GET("/backend/trace/points")
    String getPoints(String queryString) throws Exception {
        TracePointRequest request = QueryStrings.decode(queryString, TracePointRequest.class);

        double durationMillisLow = request.responseTimeMillisLow();
        long durationNanosLow = Math.round(durationMillisLow * NANOSECONDS_PER_MILLISECOND);
        Long durationNanosHigh = null;
        Double durationMillisHigh = request.responseTimeMillisHigh();
        if (durationMillisHigh != null) {
            durationNanosHigh = Math.round(durationMillisHigh * NANOSECONDS_PER_MILLISECOND);
        }

        TracePointQuery query = ImmutableTracePointQuery.builder()
                .serverRollup(request.serverRollup())
                .from(request.from())
                .to(request.to())
                .durationNanosLow(durationNanosLow)
                .durationNanosHigh(durationNanosHigh)
                .transactionType(request.transactionType())
                .transactionNameComparator(request.transactionNameComparator())
                .transactionName(request.transactionName())
                .headlineComparator(request.headlineComparator())
                .headline(request.headline())
                .errorComparator(request.errorComparator())
                .error(request.error())
                .userComparator(request.userComparator())
                .user(request.user())
                .attributeName(request.attributeName())
                .attributeValueComparator(request.attributeValueComparator())
                .attributeValue(request.attributeValue())
                .slowOnly(request.slowOnly())
                .errorOnly(request.errorOnly())
                .limit(request.limit())
                .build();
        return new Handler(query).handle();
    }

    private class Handler {

        private final TracePointQuery query;

        public Handler(TracePointQuery query) {
            this.query = query;
        }

        private String handle() throws Exception {
            boolean captureActiveTracePoints = shouldCaptureActiveTracePoints();
            List activeTracePoints = Lists.newArrayList();
            long captureTime = 0;
            long captureTick = 0;
            if (captureActiveTracePoints) {
                captureTime = clock.currentTimeMillis();
                captureTick = ticker.read();
                // capture active traces first to make sure that none are missed in the transition
                // between active and pending/stored (possible duplicates are removed below)
                activeTracePoints.addAll(liveTraceRepository.getMatchingActiveTracePoints(
                        query.serverRollup(), captureTime, captureTick, query));
            }
            Result queryResult =
                    getStoredAndPendingPoints(captureTime, captureActiveTracePoints);
            List points = Lists.newArrayList(queryResult.records());
            removeDuplicatesBetweenActiveAndNormalTracePoints(activeTracePoints, points);
            boolean expired = points.isEmpty() && query.to() < clock.currentTimeMillis()
                    - HOURS.toMillis(configRepository.getStorageConfig().traceExpirationHours());
            List traceAttributeNames =
                    traceRepository.readTraceAttributeNames(query.serverRollup());
            return writeResponse(points, activeTracePoints, queryResult.moreAvailable(), expired,
                    traceAttributeNames);
        }

        private boolean shouldCaptureActiveTracePoints() {
            long currentTimeMillis = clock.currentTimeMillis();
            return (query.to() == 0 || query.to() > currentTimeMillis)
                    && query.from() < currentTimeMillis;
        }

        private Result getStoredAndPendingPoints(long captureTime,
                boolean captureActiveTraces) throws Exception {
            List matchingPendingPoints;
            // it only seems worth looking at pending traces if request asks for active traces
            if (captureActiveTraces) {
                // important to grab pending traces before stored points to ensure none are
                // missed in the transition between pending and stored
                matchingPendingPoints = liveTraceRepository
                        .getMatchingPendingPoints(query.serverRollup(), captureTime, query);
            } else {
                matchingPendingPoints = ImmutableList.of();
            }
            Result queryResult = traceRepository.readPoints(query);
            // create single merged and limited list of points
            List orderedPoints = Lists.newArrayList(queryResult.records());
            for (TracePoint pendingPoint : matchingPendingPoints) {
                insertIntoOrderedPoints(pendingPoint, orderedPoints);
            }
            return new Result(orderedPoints, queryResult.moreAvailable());
        }

        private void insertIntoOrderedPoints(TracePoint pendingPoint,
                List orderedPoints) {
            int duplicateIndex = -1;
            int insertionIndex = -1;
            // check if duplicate and capture insertion index at the same time
            for (int i = 0; i < orderedPoints.size(); i++) {
                TracePoint point = orderedPoints.get(i);
                if (pendingPoint.traceId().equals(point.traceId())) {
                    duplicateIndex = i;
                    break;
                }
                if (pendingPoint.durationNanos() > point.durationNanos()) {
                    insertionIndex = i;
                    break;
                }
            }
            if (duplicateIndex != -1) {
                TracePoint point = orderedPoints.get(duplicateIndex);
                if (pendingPoint.durationNanos() > point.durationNanos()) {
                    // prefer the pending trace, it must be a partial trace that has just completed
                    orderedPoints.set(duplicateIndex, pendingPoint);
                }
                return;
            }
            if (insertionIndex == -1) {
                orderedPoints.add(pendingPoint);
            } else {
                orderedPoints.add(insertionIndex, pendingPoint);
            }
        }

        private void removeDuplicatesBetweenActiveAndNormalTracePoints(
                List activeTracePoints, List points) {
            for (Iterator i = activeTracePoints.iterator(); i.hasNext();) {
                TracePoint activeTracePoint = i.next();
                for (Iterator j = points.iterator(); j.hasNext();) {
                    TracePoint point = j.next();
                    if (!activeTracePoint.traceId().equals(point.traceId())) {
                        continue;
                    }
                    if (activeTracePoint.durationNanos() > point.durationNanos()) {
                        // prefer the active trace, it must be a partial trace that hasn't
                        // completed yet
                        j.remove();
                    } else {
                        // otherwise prefer the completed trace
                        i.remove();
                    }
                    // there can be at most one duplicate per id, so ok to break to outer
                    break;
                }
            }
        }

        private String writeResponse(List points, List activePoints,
                boolean limitExceeded, boolean expired, List traceAttributeNames)
                        throws Exception {
            StringBuilder sb = new StringBuilder();
            JsonGenerator jg = jsonFactory.createGenerator(CharStreams.asWriter(sb));
            jg.writeStartObject();
            jg.writeArrayFieldStart("normalPoints");
            for (TracePoint point : points) {
                if (!point.error()) {
                    writePoint(point, jg);
                }
            }
            jg.writeEndArray();
            jg.writeArrayFieldStart("errorPoints");
            for (TracePoint point : points) {
                if (point.error()) {
                    writePoint(point, jg);
                }
            }
            jg.writeEndArray();
            jg.writeArrayFieldStart("activePoints");
            for (TracePoint activePoint : activePoints) {
                writePoint(activePoint, jg);
            }
            jg.writeEndArray();
            if (limitExceeded) {
                jg.writeBooleanField("limitExceeded", true);
            }
            if (expired) {
                jg.writeBooleanField("expired", true);
            }
            jg.writeArrayFieldStart("traceAttributeNames");
            for (String traceAttributeName : traceAttributeNames) {
                jg.writeString(traceAttributeName);
            }
            jg.writeEndArray();
            jg.writeEndObject();
            jg.close();
            return sb.toString();
        }

        private void writePoint(TracePoint point, JsonGenerator jg) throws IOException {
            jg.writeStartArray();
            jg.writeNumber(point.captureTime());
            jg.writeNumber(point.durationNanos() / NANOSECONDS_PER_MILLISECOND);
            jg.writeString(point.serverId());
            jg.writeString(point.traceId());
            jg.writeEndArray();
        }
    }

    // same as TracePointQuery but with milliseconds instead of nanoseconds
    @Value.Immutable
    public abstract static class TracePointRequest {

        public abstract String serverRollup();
        public abstract long from();
        public abstract long to();
        public abstract double responseTimeMillisLow();
        public abstract @Nullable Double responseTimeMillisHigh();
        public abstract @Nullable String transactionType();
        public abstract @Nullable StringComparator transactionNameComparator();
        public abstract @Nullable String transactionName();
        public abstract @Nullable StringComparator headlineComparator();
        public abstract @Nullable String headline();
        public abstract @Nullable StringComparator errorComparator();
        public abstract @Nullable String error();
        public abstract @Nullable StringComparator userComparator();
        public abstract @Nullable String user();
        public abstract @Nullable String attributeName();
        public abstract @Nullable StringComparator attributeValueComparator();
        public abstract @Nullable String attributeValue();

        @Value.Default
        public boolean slowOnly() {
            return false;
        }

        @Value.Default
        public boolean errorOnly() {
            return false;
        }

        public abstract int limit();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy