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

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

The 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.local.ui;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import javax.annotation.Nullable;

import org.glowroot.shaded.fasterxml.jackson.core.JsonFactory;
import org.glowroot.shaded.fasterxml.jackson.core.JsonGenerator;
import org.glowroot.shaded.google.common.base.Function;
import org.glowroot.shaded.google.common.base.Strings;
import org.glowroot.shaded.google.common.base.Ticker;
import org.glowroot.shaded.google.common.collect.ImmutableList;
import org.glowroot.shaded.google.common.collect.ImmutableMap;
import org.glowroot.shaded.google.common.collect.Lists;
import org.glowroot.shaded.google.common.collect.Ordering;
import org.glowroot.shaded.google.common.io.CharStreams;

import org.glowroot.common.Clock;
import org.glowroot.config.ConfigService;
import org.glowroot.local.store.QueryResult;
import org.glowroot.local.store.StringComparator;
import org.glowroot.local.store.TraceDao;
import org.glowroot.local.store.TracePoint;
import org.glowroot.local.store.TracePointQuery;
import org.glowroot.transaction.ErrorMessage;
import org.glowroot.transaction.TransactionCollector;
import org.glowroot.transaction.TransactionRegistry;
import org.glowroot.transaction.model.Transaction;

import static org.glowroot.shaded.google.common.base.Preconditions.checkNotNull;
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 TraceDao traceDao;
    private final TransactionRegistry transactionRegistry;
    private final TransactionCollector transactionCollector;
    private final ConfigService configService;
    private final Ticker ticker;
    private final Clock clock;

    TracePointJsonService(TraceDao traceDao, TransactionRegistry transactionRegistry,
            TransactionCollector transactionCollector, ConfigService configService, Ticker ticker,
            Clock clock) {
        this.traceDao = traceDao;
        this.transactionRegistry = transactionRegistry;
        this.transactionCollector = transactionCollector;
        this.configService = configService;
        this.ticker = ticker;
        this.clock = clock;
    }

    @GET("/backend/trace/points")
    String getPoints(String queryString) throws Exception {
        TracePointQuery query = QueryStrings.decode(queryString, TracePointQuery.class);
        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 captureActiveTraces = shouldCaptureActiveTraces();
            List activeTraces = Lists.newArrayList();
            long captureTime = 0;
            long captureTick = 0;
            if (captureActiveTraces) {
                // capture active traces first to make sure that none are missed in the transition
                // between active and pending/stored (possible duplicates are removed below)
                activeTraces = getMatchingActiveTraces();
                // take capture timings after the capture to make sure there are no traces captured
                // that start after the recorded capture time (resulting in negative duration)
                captureTime = clock.currentTimeMillis();
                captureTick = ticker.read();
            }
            QueryResult queryResult = getStoredAndPendingPoints(captureActiveTraces);
            List points = queryResult.records();
            removeDuplicatesBetweenActiveTracesAndPoints(activeTraces, points);
            boolean expired = points.isEmpty() && query.to() < clock.currentTimeMillis()
                    - HOURS.toMillis(configService.getStorageConfig().traceExpirationHours());
            return writeResponse(points, activeTraces, captureTime, captureTick,
                    queryResult.moreAvailable(), expired);
        }

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

        private QueryResult getStoredAndPendingPoints(boolean captureActiveTraces)
                throws SQLException {
            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 = getMatchingPendingPoints();
            } else {
                matchingPendingPoints = ImmutableList.of();
            }
            QueryResult queryResult = traceDao.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 QueryResult(orderedPoints, queryResult.moreAvailable());
        }

        private List getMatchingActiveTraces() {
            List activeTraces = Lists.newArrayList();
            for (Transaction transaction : transactionRegistry.getTransactions()) {
                if (matches(transaction)) {
                    activeTraces.add(transaction);
                }
            }
            Collections.sort(activeTraces,
                    Ordering.natural().onResultOf(new Function() {
                        @Override
                        public Long apply(@Nullable Transaction transaction) {
                            // sorting activeTraces which is List<@NonNull Transaction>
                            checkNotNull(transaction);
                            return transaction.getStartTick();
                        }
                    }));
            if (query.limit() != 0 && activeTraces.size() > query.limit()) {
                activeTraces = activeTraces.subList(0, query.limit());
            }
            return activeTraces;
        }

        private List getMatchingPendingPoints() {
            List points = Lists.newArrayList();
            for (Transaction transaction : transactionCollector.getPendingTransactions()) {
                if (matches(transaction)) {
                    TracePoint point = TracePoint.builder()
                            .id(transaction.getId())
                            .captureTime(clock.currentTimeMillis())
                            .duration(transaction.getDuration())
                            .error(transaction.getErrorMessage() != null)
                            .build();
                    points.add(point);
                }
            }
            return points;
        }

        private boolean matches(Transaction transaction) {
            return matchesDuration(transaction)
                    && matchesTransactionType(transaction)
                    && matchesSlowOnly(transaction)
                    && matchesErrorOnly(transaction)
                    && matchesHeadline(transaction)
                    && matchesTransactionName(transaction)
                    && matchesError(transaction)
                    && matchesUser(transaction)
                    && matchesCustomAttribute(transaction);
        }

        private boolean matchesDuration(Transaction transaction) {
            long duration = transaction.getDuration();
            if (duration < query.durationLow()) {
                return false;
            }
            Long durationHigh = query.durationHigh();
            return durationHigh == null || duration <= durationHigh;
        }

        private boolean matchesTransactionType(Transaction transaction) {
            String transactionType = query.transactionType();
            if (Strings.isNullOrEmpty(transactionType)) {
                return true;
            }
            return transactionType.equals(transaction.getTransactionType());
        }

        private boolean matchesSlowOnly(Transaction transaction) {
            return !query.slowOnly() || transactionCollector.shouldStoreSlow(transaction);
        }

        private boolean matchesErrorOnly(Transaction transaction) {
            return !query.errorOnly() || transactionCollector.shouldStoreError(transaction);
        }

        private boolean matchesHeadline(Transaction transaction) {
            return matchesUsingStringComparator(query.headlineComparator(), query.headline(),
                    transaction.getHeadline());
        }

        private boolean matchesTransactionName(Transaction transaction) {
            return matchesUsingStringComparator(query.transactionNameComparator(),
                    query.transactionName(), transaction.getTransactionName());
        }

        private boolean matchesError(Transaction transaction) {
            ErrorMessage errorMessage = transaction.getErrorMessage();
            String text = errorMessage == null ? null : errorMessage.message();
            return matchesUsingStringComparator(query.errorComparator(), query.error(), text);
        }

        private boolean matchesUser(Transaction transaction) {
            return matchesUsingStringComparator(query.userComparator(), query.user(),
                    transaction.getUser());
        }

        private boolean matchesCustomAttribute(Transaction transaction) {
            if (Strings.isNullOrEmpty(query.customAttributeName())
                    && (query.customAttributeValueComparator() == null
                            || Strings.isNullOrEmpty(query.customAttributeValue()))) {
                // no custom attribute filter
                return true;
            }
            ImmutableMap> customAttributes =
                    transaction.getCustomAttributes().asMap();
            for (Entry> entry : customAttributes.entrySet()) {
                String customAttributeName = entry.getKey();
                if (!matchesUsingStringComparator(StringComparator.EQUALS,
                        query.customAttributeName(), customAttributeName)) {
                    // name doesn't match, no need to test values
                    continue;
                }
                for (String customAttributeValue : entry.getValue()) {
                    if (matchesUsingStringComparator(query.customAttributeValueComparator(),
                            query.customAttributeValue(), customAttributeValue)) {
                        // found matching name and value
                        return true;
                    }
                }
            }
            return false;
        }

        private boolean matchesUsingStringComparator(@Nullable StringComparator requestComparator,
                @Nullable String requestText, @Nullable String traceText) throws AssertionError {
            if (requestComparator == null || Strings.isNullOrEmpty(requestText)) {
                return true;
            } else if (Strings.isNullOrEmpty(traceText)) {
                return false;
            }
            return requestComparator.matches(traceText, requestText);
        }

        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.id().equals(point.id())) {
                    duplicateIndex = i;
                    break;
                }
                if (pendingPoint.duration() > point.duration()) {
                    insertionIndex = i;
                    break;
                }
            }
            if (duplicateIndex != -1) {
                TracePoint point = orderedPoints.get(duplicateIndex);
                if (pendingPoint.duration() > point.duration()) {
                    // 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 removeDuplicatesBetweenActiveTracesAndPoints(List activeTraces,
                List points) {
            for (Iterator i = activeTraces.iterator(); i.hasNext();) {
                Transaction activeTransaction = i.next();
                for (Iterator j = points.iterator(); j.hasNext();) {
                    TracePoint point = j.next();
                    if (!activeTransaction.getId().equals(point.id())) {
                        continue;
                    }
                    if (activeTransaction.getDuration() > point.duration()) {
                        // 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 activeTraces,
                long captureTime, long captureTick, boolean limitExceeded, boolean expired)
                        throws IOException, SQLException {
            StringBuilder sb = new StringBuilder();
            JsonGenerator jg = jsonFactory.createGenerator(CharStreams.asWriter(sb));
            jg.writeStartObject();
            jg.writeArrayFieldStart("normalPoints");
            for (TracePoint point : points) {
                if (!point.error()) {
                    jg.writeStartArray();
                    jg.writeNumber(point.captureTime());
                    jg.writeNumber(point.duration() / NANOSECONDS_PER_MILLISECOND);
                    jg.writeString(point.id());
                    jg.writeEndArray();
                }
            }
            jg.writeEndArray();
            jg.writeArrayFieldStart("errorPoints");
            for (TracePoint point : points) {
                if (point.error()) {
                    jg.writeStartArray();
                    jg.writeNumber(point.captureTime());
                    jg.writeNumber(point.duration() / NANOSECONDS_PER_MILLISECOND);
                    jg.writeString(point.id());
                    jg.writeEndArray();
                }
            }
            jg.writeEndArray();
            jg.writeArrayFieldStart("activePoints");
            for (Transaction activeTrace : activeTraces) {
                jg.writeStartArray();
                jg.writeNumber(captureTime);
                jg.writeNumber(
                        (captureTick - activeTrace.getStartTick()) / NANOSECONDS_PER_MILLISECOND);
                jg.writeString(activeTrace.getId());
                jg.writeEndArray();
            }
            jg.writeEndArray();
            if (limitExceeded) {
                jg.writeBooleanField("limitExceeded", true);
            }
            if (expired) {
                jg.writeBooleanField("expired", true);
            }
            jg.writeEndObject();
            jg.close();
            return sb.toString();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy