com.spotify.heroic.metric.QueryTrace.java Source code

Java tutorial

Introduction

Here is the source code for com.spotify.heroic.metric.QueryTrace.java

Source

/*
 * Copyright (c) 2015 Spotify AB.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.spotify.heroic.metric;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import lombok.Data;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.concurrent.TimeUnit;

@JsonSerialize(using = QueryTrace.Serializer.class)
@JsonDeserialize(using = QueryTrace.Deserializer.class)
public interface QueryTrace {
    Identifier PASSIVE_IDENTIFIER = new Identifier("NO TRACE");
    PassiveTrace PASSIVE = new PassiveTrace();
    Watch PASSIVE_WATCH = new PassiveWatch();
    NamedWatch PASSIVE_NAMED_WATCH = new PassiveNamedWatch();
    Joiner PASSIVE_JOINER = new PassiveJoiner();

    /**
     * Create an active query trace.
     *
     * @param what Identifier of the trace
     * @return a {@link com.spotify.heroic.metric.QueryTrace}
     * @deprecated use {@link #watch(com.spotify.heroic.metric.QueryTrace.Identifier)}
     */
    static QueryTrace of(final Identifier what) {
        return new ActiveTrace(what, 0L, ImmutableList.of());
    }

    /**
     * Create an active query trace.
     *
     * @param what Identifier of the trace
     * @param elapsed How long the query trace has elapsed for
     * @return a {@link com.spotify.heroic.metric.QueryTrace}
     * @deprecated use {@link #watch(com.spotify.heroic.metric.QueryTrace.Identifier)}
     */
    static QueryTrace of(final Identifier what, final long elapsed) {
        return new ActiveTrace(what, elapsed, ImmutableList.of());
    }

    /**
     * Create an active query trace.
     *
     * @param what Identifier of the trace
     * @param elapsed How long the query trace has elapsed for
     * @param children Children of the query trace
     * @return a {@link com.spotify.heroic.metric.QueryTrace}
     * @deprecated use {@link #watch(com.spotify.heroic.metric.QueryTrace.Identifier)}
     */
    static QueryTrace of(final Identifier what, final long elapsed, final List<QueryTrace> children) {
        return new ActiveTrace(what, elapsed, children);
    }

    /**
     * Create a new identifier for a class.
     *
     * @param where Class associated with the identifier
     * @return an {@link com.spotify.heroic.metric.QueryTrace.Identifier}
     */
    static Identifier identifier(Class<?> where) {
        return new Identifier(where.getName());
    }

    /**
     * Create a new identifier for a class and a what
     *
     * @param where Class associated with the identifier
     * @param what String describing what is being traced
     * @return an {@link com.spotify.heroic.metric.QueryTrace.Identifier}
     */
    static Identifier identifier(Class<?> where, String what) {
        return new Identifier(where.getName() + "#" + what);
    }

    /**
     * Create a new identifier for a free text description.
     *
     * @param description Description telling what is being traced.
     * @return an {@link com.spotify.heroic.metric.QueryTrace.Identifier}
     */
    static Identifier identifier(String description) {
        return new Identifier(description);
    }

    /**
     * Create a new watch.
     *
     * @return a {@link com.spotify.heroic.metric.QueryTrace.Watch}
     * @deprecated use {@link Tracing#watch()}
     */
    static Watch watch() {
        return new ActiveWatch(Stopwatch.createStarted());
    }

    /**
     * Create a new watch.
     *
     * @return a {@link com.spotify.heroic.metric.QueryTrace.Watch}
     * @deprecated use {@link Tracing#watch(Identifier)}
     */
    static NamedWatch watch(final Identifier what) {
        return new ActiveNamedWatch(what, Stopwatch.createStarted());
    }

    /**
     * Format the current trace onto the given PrintWriter.
     *
     * @param out {@link java.io.PrintWriter} to format to
     */
    void formatTrace(PrintWriter out);

    /**
     * Format the current trace onto the given PrintWriter with the given prefix.
     *
     * @param out {@link java.io.PrintWriter} to format to
     * @param prefix prefix to prepend
     */
    void formatTrace(PrintWriter out, String prefix);

    /**
     * @deprecated use {@link #elapsed()}
     */
    @Deprecated
    long getElapsed();

    /**
     * @deprecated use {@link #what()}
     */
    @Deprecated
    Identifier getWhat();

    /**
     * @deprecated use {@link #children()}
     */
    @Deprecated
    List<QueryTrace> getChildren();

    /**
     * How long the trace elapsed for.
     *
     * @return microseconds
     */
    long elapsed();

    /**
     * Get the identifier for this trace.
     *
     * @return an identifier
     */
    Identifier what();

    /**
     * Get a list of all immediate children to this trace.
     *
     * @return a list
     */
    List<QueryTrace> children();

    @JsonTypeName("passive")
    @Data
    class PassiveTrace implements QueryTrace {
        PassiveTrace() {
        }

        @JsonCreator
        public static PassiveTrace create() {
            return PASSIVE;
        }

        @Override
        public void formatTrace(final PrintWriter out) {
            formatTrace(out, "");
        }

        @Override
        public void formatTrace(final PrintWriter out, final String prefix) {
            out.println(prefix + "NO TRACE");
        }

        @JsonIgnore
        @Override
        public long getElapsed() {
            return elapsed();
        }

        @JsonIgnore
        @Override
        public Identifier getWhat() {
            return what();
        }

        @JsonIgnore
        @Override
        public List<QueryTrace> getChildren() {
            return children();
        }

        @Override
        public long elapsed() {
            return 0L;
        }

        @Override
        public Identifier what() {
            return PASSIVE_IDENTIFIER;
        }

        @Override
        public List<QueryTrace> children() {
            return ImmutableList.of();
        }
    }

    @JsonTypeName("active")
    @Data
    class ActiveTrace implements QueryTrace {
        private final Identifier what;
        private final long elapsed;
        private final List<QueryTrace> children;

        @Override
        public void formatTrace(PrintWriter out) {
            formatTrace(out, "");
        }

        @Override
        public void formatTrace(PrintWriter out, String prefix) {
            out.println(prefix + what + " (in " + readableTime(elapsed) + ")");

            for (final QueryTrace child : children) {
                child.formatTrace(out, prefix + "  ");
            }
        }

        @Override
        public long elapsed() {
            return elapsed;
        }

        @Override
        public Identifier what() {
            return what;
        }

        @Override
        public List<QueryTrace> children() {
            return children;
        }

        private String readableTime(long elapsed) {
            if (elapsed > 1000000000) {
                return String.format("%.3fs", elapsed / 1000000000d);
            }

            if (elapsed > 1000000) {
                return String.format("%.3fms", elapsed / 1000000d);
            }

            if (elapsed > 1000) {
                return String.format("%.3fus", elapsed / 1000d);
            }

            return elapsed + "ns";
        }

        /**
         * Convert to model.
         *
         * @return a new json model
         */
        ActiveJsonModel toModel() {
            return new ActiveJsonModel(what, elapsed, children);
        }
    }

    interface Watch {
        /**
         * End the current watch and return a trace.
         * <p>
         * The same watch can be used multiple times, weven when ended.
         *
         * @param what The thing that is being traced
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace end(final Identifier what);

        /**
         * End the current watch and return a trace with the given child.
         * <p>
         * The same watch can be used multiple times, weven when ended.
         *
         * @param what The thing that is being traced
         * @param child Child to add to the new {@link com.spotify.heroic.metric.QueryTrace}
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace end(final Identifier what, final QueryTrace child);

        /**
         * End the current watch and return a trace with the given children.
         * <p>
         * The same watch can be used multiple times, weven when ended.
         *
         * @param what The thing that is being traced
         * @param children Children to add to the new {@link com.spotify.heroic.metric.QueryTrace}
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace end(final Identifier what, final List<QueryTrace> children);

        /**
         * How long this trace has elapsed for.
         *
         * @return microseconds
         * @deprecated Makes not distinction between passive and active watches
         */
        long elapsed();
    }

    /**
     * A watch that is measuring.
     */
    @Data
    class ActiveWatch implements Watch {
        private final Stopwatch w;

        @Override
        public QueryTrace end(final Identifier what) {
            return new ActiveTrace(what, elapsed(), ImmutableList.of());
        }

        @Override
        public QueryTrace end(final Identifier what, final QueryTrace child) {
            return new ActiveTrace(what, elapsed(), ImmutableList.of(child));
        }

        @Override
        public QueryTrace end(final Identifier what, final List<QueryTrace> children) {
            return new ActiveTrace(what, elapsed(), children);
        }

        @Override
        public long elapsed() {
            return w.elapsed(TimeUnit.MICROSECONDS);
        }
    }

    /**
     * A watch that is not measuring.
     */
    @Data
    class PassiveWatch implements Watch {
        @Override
        public QueryTrace end(final Identifier what) {
            return PASSIVE;
        }

        @Override
        public QueryTrace end(final Identifier what, final QueryTrace child) {
            return PASSIVE;
        }

        @Override
        public QueryTrace end(final Identifier what, final List<QueryTrace> children) {
            return PASSIVE;
        }

        @Override
        public long elapsed() {
            return 0L;
        }
    }

    interface NamedWatch {
        /**
         * End the current watch and return a trace.
         * <p>
         * The same watch can be used multiple times, weven when ended.
         *
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace end();

        /**
         * End the current watch and return a trace with the given child.
         * <p>
         * The same watch can be used multiple times, weven when ended.
         *
         * @param child Child to add to the new {@link com.spotify.heroic.metric.QueryTrace}
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace end(QueryTrace child);

        /**
         * End the current watch and return a trace with the given children.
         * <p>
         * The same watch can be used multiple times, weven when ended.
         *
         * @param children Children to add to the new {@link com.spotify.heroic.metric.QueryTrace}
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace end(List<QueryTrace> children);

        /**
         * Create a joiner from the current watch.
         *
         * @return a {@link com.spotify.heroic.metric.QueryTrace.Joiner}
         */
        Joiner joiner();

        /**
         * Create another watch with the same configuration as this watch (active or passive).
         *
         * @param what What is being watched
         * @return a {@link com.spotify.heroic.metric.QueryTrace.NamedWatch}
         */
        NamedWatch watch(Identifier what);

        /**
         * Create another watch that copies everything from this watch, while extending the id.
         *
         * @param appendName What to extend the identifier with
         * @return a {@link com.spotify.heroic.metric.QueryTrace.NamedWatch}
         */
        NamedWatch extendIdentifier(String appendName);

        /**
         * How long this trace has elapsed for.
         *
         * @return milliseconds since unix epoch
         * @deprecated Makes no distinction between active and passive watches.
         */
        long elapsed();
    }

    @Data
    class ActiveNamedWatch implements NamedWatch {
        private final Identifier what;
        private final Stopwatch w;

        @Override
        public QueryTrace end() {
            return new ActiveTrace(what, elapsed(), ImmutableList.of());
        }

        @Override
        public QueryTrace end(final QueryTrace child) {
            return new ActiveTrace(what, elapsed(), ImmutableList.of(child));
        }

        @Override
        public QueryTrace end(final List<QueryTrace> children) {
            return new ActiveTrace(what, elapsed(), children);
        }

        @Override
        public Joiner joiner() {
            return new ActiveJoiner(this);
        }

        @Override
        public NamedWatch watch(final Identifier what) {
            return new ActiveNamedWatch(what, Stopwatch.createStarted());
        }

        @Override
        public NamedWatch extendIdentifier(String appendName) {
            return new ActiveNamedWatch(what.extend(appendName), w);
        }

        @Override
        public long elapsed() {
            return w.elapsed(TimeUnit.MICROSECONDS);
        }
    }

    @Data
    class PassiveNamedWatch implements NamedWatch {
        @Override
        public QueryTrace end() {
            return PASSIVE;
        }

        @Override
        public QueryTrace end(final QueryTrace child) {
            return PASSIVE;
        }

        @Override
        public QueryTrace end(final List<QueryTrace> children) {
            return PASSIVE;
        }

        @Override
        public Joiner joiner() {
            return PASSIVE_JOINER;
        }

        @Override
        public NamedWatch watch(final Identifier what) {
            return PASSIVE_NAMED_WATCH;
        }

        @Override
        public NamedWatch extendIdentifier(String appendName) {
            return PASSIVE_NAMED_WATCH;
        }

        @Override
        public long elapsed() {
            return 0L;
        }
    }

    @Data
    class Identifier {
        private final String name;

        @JsonCreator
        public Identifier(@JsonProperty("name") String name) {
            this.name = name;
        }

        public Identifier extend(String key) {
            return new Identifier(name + "[" + key + "]");
        }

        @Override
        public String toString() {
            return name;
        }
    }

    /**
     * Joiner multiple query traces into one.
     */
    interface Joiner {
        /**
         * Add a child trace that should be part of the resulting trace.
         *
         * @param trace Child trace to add
         */
        void addChild(QueryTrace trace);

        /**
         * Create a new trace with a snapshot of the current list of children.
         *
         * @return a {@link com.spotify.heroic.metric.QueryTrace}
         */
        QueryTrace result();
    }

    /**
     * A joiner that joins the given children into a single trace.
     */
    @Data
    class ActiveJoiner implements Joiner {
        private final QueryTrace.NamedWatch watch;

        private final ImmutableList.Builder<QueryTrace> children = ImmutableList.builder();

        @Override
        public void addChild(final QueryTrace trace) {
            children.add(trace);
        }

        @Override
        public QueryTrace result() {
            return watch.end(children.build());
        }
    }

    /**
     * A joiner that does nothing.
     */
    class PassiveJoiner implements Joiner {
        PassiveJoiner() {
        }

        @Override
        public void addChild(final QueryTrace trace) {
            /* do nothing */
        }

        @Override
        public QueryTrace result() {
            return PASSIVE;
        }
    }

    class Serializer extends JsonSerializer<QueryTrace> {
        @Override
        public void serialize(final QueryTrace value, final JsonGenerator gen, final SerializerProvider serializers)
                throws IOException {
            if (value instanceof PassiveTrace) {
                gen.writeNull();
                return;
            }

            gen.writeObject(((ActiveTrace) value).toModel());
        }
    }

    class Deserializer extends JsonDeserializer<QueryTrace> {
        @Override
        public QueryTrace getNullValue(final DeserializationContext ctxt) throws JsonMappingException {
            return PASSIVE;
        }

        @Override
        public QueryTrace deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException {
            return p.readValueAs(ActiveJsonModel.class).toActive();
        }
    }

    /**
     * Intermediate JSON model used for serializing and deserializing active values.
     * <p>
     * <p>Attempting to immediately serialize something extending
     * {@link com.spotify.heroic.metric.QueryTrace} would cause infinite recursion since the same
     * serializer would be called over and over. This model breaks that.
     */
    @Data
    class ActiveJsonModel {
        private final Identifier what;
        private final long elapsed;
        private final List<QueryTrace> children;

        /**
         * Convert to active instance.
         *
         * @return a new active instance
         */
        QueryTrace toActive() {
            return new ActiveTrace(what, elapsed, children);
        }
    }
}