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

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

There is a newer version: 0.9.28
Show newest version
/*
 * Copyright 2012-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.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;

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.io.CharStreams;
import org.immutables.value.Value;

import org.glowroot.common.live.LiveTraceRepository;
import org.glowroot.common.live.LiveTraceRepository.Existence;
import org.glowroot.common.model.MutableProfileTree;
import org.glowroot.common.util.Styles;
import org.glowroot.storage.repo.TraceRepository;
import org.glowroot.storage.repo.TraceRepository.HeaderPlus;
import org.glowroot.wire.api.model.ProfileTreeOuterClass.ProfileTree;
import org.glowroot.wire.api.model.TraceOuterClass.Trace;

class TraceCommonService {

    private static final JsonFactory jsonFactory = new JsonFactory();

    private final TraceRepository traceRepository;
    private final LiveTraceRepository liveTraceRepository;

    TraceCommonService(TraceRepository traceRepository, LiveTraceRepository liveTraceRepository) {
        this.traceRepository = traceRepository;
        this.liveTraceRepository = liveTraceRepository;
    }

    @Nullable
    String getHeaderJson(String serverId, String traceId) throws Exception {
        // check active/pending traces first, and lastly stored traces to make sure that the trace
        // is not missed if it is in transition between these states
        Trace.Header header = liveTraceRepository.getHeader(serverId, traceId);
        if (header != null) {
            return toJsonLiveHeader(header);
        }
        HeaderPlus headerPlus = traceRepository.readHeader(serverId, traceId);
        if (headerPlus != null) {
            return toJsonRepoHeader(headerPlus);
        }
        return null;
    }

    // TODO this comment is no longer valid?
    // overwritten entries will return {"overwritten":true}
    // expired (not found) trace will return {"expired":true}
    @Nullable
    String getEntriesJson(String serverId, String traceId) throws Exception {
        // check active/pending traces first, and lastly stored traces to make sure that the trace
        // is not missed if it is in transition between these states
        List entries = liveTraceRepository.getEntries(serverId, traceId);
        if (entries.isEmpty()) {
            entries = traceRepository.readEntries(serverId, traceId);
        }
        return toJson(entries);
    }

    // overwritten profile will return {"overwritten":true}
    // expired (not found) trace will return {"expired":true}
    @Nullable
    String getProfileTreeJson(String serverId, String traceId) throws Exception {
        // check active/pending traces first, and lastly stored traces to make sure that the trace
        // is not missed if it is in transition between these states
        ProfileTree profileTree = liveTraceRepository.getProfileTree(serverId, traceId);
        if (profileTree == null) {
            profileTree = traceRepository.readProfileTree(serverId, traceId);
        }
        return toJson(profileTree);
    }

    @Nullable
    TraceExport getExport(String serverId, String traceId) throws Exception {
        // check active/pending traces first, and lastly stored traces to make sure that the trace
        // is not missed if it is in transition between these states
        Trace trace = liveTraceRepository.getFullTrace(serverId, traceId);
        if (trace != null) {
            Trace.Header header = trace.getHeader();
            return ImmutableTraceExport.builder()
                    .fileName(getFilename(header))
                    .headerJson(toJsonLiveHeader(header))
                    .entriesJson(toJson(trace.getEntryList()))
                    .profileTreeJson(toJson(trace.getProfileTree()))
                    .build();
        }

        HeaderPlus header = traceRepository.readHeader(serverId, traceId);
        if (header == null) {
            return null;
        }
        ImmutableTraceExport.Builder builder = ImmutableTraceExport.builder()
                .fileName(getFilename(header.header()))
                .headerJson(toJsonRepoHeader(header));
        if (header.entriesExistence() == Existence.YES) {
            builder.entriesJson(toJson(traceRepository.readEntries(serverId, traceId)));
        }
        if (header.profileExistence() == Existence.YES) {
            builder.profileTreeJson(toJson(traceRepository.readProfileTree(serverId, traceId)));
        }
        return builder.build();
    }

    private static @Nullable String toJson(List entries) throws IOException {
        if (entries.isEmpty()) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        JsonGenerator jg = jsonFactory.createGenerator(CharStreams.asWriter(sb));
        jg.writeStartArray();
        for (Trace.Entry entry : entries) {
            writeJson(entry, jg);
        }
        jg.writeEndArray();
        jg.close();
        return sb.toString();
    }

    private static @Nullable String toJson(@Nullable ProfileTree profileTree) throws IOException {
        if (profileTree == null) {
            return null;
        }
        MutableProfileTree mutableProfileTree = new MutableProfileTree();
        mutableProfileTree.merge(profileTree);
        return mutableProfileTree.toJson();
    }

    private static String toJsonLiveHeader(Trace.Header header) throws IOException {
        return toJson(header, header.getPartial(),
                header.getEntryCount() > 0 ? Existence.YES : Existence.NO,
                header.getProfileSampleCount() > 0 ? Existence.YES : Existence.NO);
    }

    private static String toJsonRepoHeader(HeaderPlus header) throws IOException {
        return toJson(header.header(), false, header.entriesExistence(), header.profileExistence());
    }

    private static String toJson(Trace.Header header, boolean active, Existence entriesExistence,
            Existence profileExistence) throws IOException {
        StringBuilder sb = new StringBuilder();
        JsonGenerator jg = jsonFactory.createGenerator(CharStreams.asWriter(sb));
        jg.writeStartObject();
        if (active) {
            jg.writeBooleanField("active", active);
        }
        boolean partial = header.getPartial();
        if (partial) {
            jg.writeBooleanField("partial", partial);
        }
        boolean slow = header.getSlow();
        if (slow) {
            jg.writeBooleanField("slow", slow);
        }
        jg.writeNumberField("startTime", header.getStartTime());
        jg.writeNumberField("captureTime", header.getCaptureTime());
        jg.writeNumberField("durationNanos", header.getDurationNanos());
        jg.writeStringField("transactionType", header.getTransactionType());
        jg.writeStringField("transactionName", header.getTransactionName());
        jg.writeStringField("headline", header.getHeadline());
        jg.writeStringField("user", header.getUser());
        List attributes = header.getAttributeList();
        if (!attributes.isEmpty()) {
            jg.writeObjectFieldStart("attributes");
            for (Trace.Attribute attribute : attributes) {
                jg.writeArrayFieldStart(attribute.getName());
                for (String value : attribute.getValueList()) {
                    jg.writeString(value);
                }
                jg.writeEndArray();
            }
            jg.writeEndObject();
        }

        List detailEntries = header.getDetailEntryList();
        if (!detailEntries.isEmpty()) {
            jg.writeFieldName("detail");
            writeDetailEntries(detailEntries, jg);
        }

        if (header.hasError()) {
            jg.writeFieldName("error");
            writeError(header.getError(), jg);
        }
        jg.writeFieldName("rootTimer");
        writeTimer(header.getRootTimer(), jg);
        if (header.hasThreadCpuNanos()) {
            jg.writeNumberField("threadCpuNanos", header.getThreadCpuNanos().getValue());
        }
        if (header.hasThreadBlockedNanos()) {
            jg.writeNumberField("threadBlockedNanos", header.getThreadBlockedNanos().getValue());
        }
        if (header.hasThreadWaitedNanos()) {
            jg.writeNumberField("threadWaitedNanos", header.getThreadWaitedNanos().getValue());
        }
        if (header.hasThreadAllocatedBytes()) {
            jg.writeNumberField("threadAllocatedBytes",
                    header.getThreadAllocatedBytes().getValue());
        }
        List gcActivities = header.getGcActivityList();
        if (!gcActivities.isEmpty()) {
            jg.writeArrayFieldStart("gcActivities");
            for (Trace.GarbageCollectionActivity gcActivity : gcActivities) {
                jg.writeStartObject();
                jg.writeStringField("collectorName", gcActivity.getCollectorName());
                jg.writeNumberField("totalMillis", gcActivity.getTotalMillis());
                jg.writeNumberField("count", gcActivity.getCount());
                jg.writeEndObject();
            }
            jg.writeEndArray();
        }
        jg.writeNumberField("entryCount", header.getEntryCount());
        boolean entryLimitExceeded = header.getEntryLimitExceeded();
        if (entryLimitExceeded) {
            jg.writeBooleanField("entryLimitExceeded", entryLimitExceeded);
        }
        jg.writeNumberField("profileSampleCount", header.getProfileSampleCount());
        boolean profileSampleLimitExceeded = header.getProfileSampleLimitExceeded();
        if (profileSampleLimitExceeded) {
            jg.writeBooleanField("profileSampleLimitExceeded", profileSampleLimitExceeded);
        }
        jg.writeStringField("entriesExistence",
                entriesExistence.name().toLowerCase(Locale.ENGLISH));
        jg.writeStringField("profileExistence",
                profileExistence.name().toLowerCase(Locale.ENGLISH));
        jg.writeEndObject();
        jg.close();
        return sb.toString();
    }

    private static void writeJson(Trace.Entry entry, JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeNumberField("startOffsetNanos", entry.getStartOffsetNanos());
        jg.writeNumberField("durationNanos", entry.getDurationNanos());
        boolean active = entry.getActive();
        if (active) {
            jg.writeBooleanField("active", true);
        }
        jg.writeStringField("message", entry.getMessage());
        List detailEntries = entry.getDetailEntryList();
        if (!detailEntries.isEmpty()) {
            jg.writeFieldName("detail");
            writeDetailEntries(detailEntries, jg);
        }
        List locationStackTraceElements =
                entry.getLocationStackTraceElementList();
        if (!locationStackTraceElements.isEmpty()) {
            jg.writeArrayFieldStart("locationStackTraceElements");
            for (Trace.StackTraceElement stackTraceElement : locationStackTraceElements) {
                writeStackTraceElement(stackTraceElement, jg);
            }
            jg.writeEndArray();
        }
        if (entry.hasError()) {
            jg.writeFieldName("error");
            writeError(entry.getError(), jg);
        }
        List childEntries = entry.getChildEntryList();
        if (!childEntries.isEmpty()) {
            jg.writeArrayFieldStart("childEntries");
            for (Trace.Entry childEntry : childEntries) {
                writeJson(childEntry, jg);
            }
            jg.writeEndArray();
        }
        jg.writeEndObject();
    }

    private static void writeDetailEntries(List detailEntries, JsonGenerator jg)
            throws IOException {
        jg.writeStartObject();
        for (Trace.DetailEntry detailEntry : detailEntries) {
            jg.writeFieldName(detailEntry.getName());
            List childEntries = detailEntry.getChildEntryList();
            List values = detailEntry.getValueList();
            if (!childEntries.isEmpty()) {
                writeDetailEntries(childEntries, jg);
            } else if (values.size() == 1) {
                writeValue(values.get(0), jg);
            } else if (values.size() > 1) {
                jg.writeStartArray();
                for (Trace.DetailValue value : values) {
                    writeValue(value, jg);
                }
                jg.writeEndArray();
            } else {
                jg.writeNull();
            }
        }
        jg.writeEndObject();
    }

    private static void writeValue(Trace.DetailValue value, JsonGenerator jg) throws IOException {
        switch (value.getValCase()) {
            case SVAL:
                jg.writeString(value.getSval());
                break;
            case DVAL:
                jg.writeNumber(value.getDval());
                break;
            case LVAL:
                jg.writeNumber(value.getLval());
                break;
            case BVAL:
                jg.writeBoolean(value.getBval());
                break;
            default:
                throw new IllegalStateException("Unexpected detail value: " + value.getValCase());
        }
    }

    private static void writeError(Trace.Error error, JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeStringField("message", error.getMessage());
        if (error.hasException()) {
            jg.writeFieldName("exception");
            writeThrowable(error.getException(), false, jg);
        }
        jg.writeEndObject();
    }

    private static void writeThrowable(Trace.Throwable throwable, boolean hasEnclosing,
            JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeStringField("display", throwable.getDisplay());
        jg.writeArrayFieldStart("stackTraceElements");
        for (Trace.StackTraceElement stackTraceElement : throwable.getStackTraceElementList()) {
            writeStackTraceElement(stackTraceElement, jg);
        }
        jg.writeEndArray();
        if (hasEnclosing) {
            jg.writeNumberField("framesInCommonWithEnclosing",
                    throwable.getFramesInCommonWithEnclosing());
        }
        if (throwable.hasCause()) {
            jg.writeFieldName("cause");
            writeThrowable(throwable.getCause(), true, jg);
        }
        jg.writeEndObject();
    }

    private static void writeTimer(Trace.Timer timer, JsonGenerator jg) throws IOException {
        jg.writeStartObject();
        jg.writeStringField("name", timer.getName());
        boolean extended = timer.getExtended();
        if (extended) {
            jg.writeBooleanField("extended", extended);
        }
        jg.writeNumberField("totalNanos", timer.getTotalNanos());
        jg.writeNumberField("count", timer.getCount());
        boolean active = timer.getActive();
        if (active) {
            jg.writeBooleanField("active", active);
        }
        List childTimers = timer.getChildTimerList();
        if (!childTimers.isEmpty()) {
            jg.writeArrayFieldStart("childTimers");
            for (Trace.Timer childTimer : childTimers) {
                writeTimer(childTimer, jg);
            }
            jg.writeEndArray();
        }
        jg.writeEndObject();
    }

    private static void writeStackTraceElement(Trace.StackTraceElement stackTraceElement,
            JsonGenerator jg) throws IOException {
        jg.writeString(new StackTraceElement(stackTraceElement.getClassName(),
                stackTraceElement.getMethodName(), stackTraceElement.getFileName(),
                stackTraceElement.getLineNumber()).toString());
    }

    private static String getFilename(Trace.Header header) {
        return "trace-" + new SimpleDateFormat("yyyyMMdd-HHmmss-SSS").format(header.getStartTime());
    }

    @Value.Immutable
    @Styles.AllParameters
    interface TraceExport {
        String fileName();
        String headerJson();
        @Nullable
        String entriesJson();
        @Nullable
        String profileTreeJson();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy