/* * Copyright 2013 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 io.jdev.miniprofiler; import java.util.*; import java.util.concurrent.Callable; /** * Profiler implementation. * * <p>Generally users of the library will not need to * interact directly with this class, instead getting references to the current * profiler through {@link io.jdev.miniprofiler.MiniProfiler#start(String)}, * {@link io.jdev.miniprofiler.MiniProfiler#getCurrentProfiler()}, * {@link io.jdev.miniprofiler.ProfilerProvider#start(String)} and * {@link io.jdev.miniprofiler.MiniProfiler#getCurrentProfiler()}, and then * just treating it as a {@link Profiler}.</p> * * <p>However, writers of custom {@link ProfilerProvider} implementations may * need to construct a new profiler by calling * {@link #ProfilerImpl(String, ProfileLevel, ProfilerProvider)}.</p> */ public class ProfilerImpl implements Profiler { private static final long serialVersionUID = 1; private final UUID id; private final String name; private final long started; private String user; private String machineName; private final ProfileLevel level; private final TimingImpl root; private boolean hasQueryTimings; private TimingImpl head; private boolean stopped; private final ProfilerProvider profilerProvider; /** * Construct a new profiling session. * * <p>This will create an implicit root {@link Timing} step considered to have * started now, with the given root name.</p> * * <p>A new random UUID id is created for every profiler.</p> * * <p>Any profiling steps more verbose than the given level will be ignored.</p> * * <p>The profiler provider constructing the profiler is passed in so that * when {@link #stop()} is called, the profiler can notify the provider to store * the profiling info for later retrieval via * {@link ProfilerProvider#stopSession(ProfilerImpl, boolean)}.</p> * * @param name name of the request, will also be the name of the root child element * @param level the level of the profiler * @param profilerProvider the profiler provider constructing the */ public ProfilerImpl(String name, ProfileLevel level, ProfilerProvider profilerProvider) { this(name, name, level, profilerProvider); } /** * Construct a new profiling session. * * <p>This will create an implicit root {@link Timing} step considered to have * started now, with the given root name.</p> * * <p>A new random UUID id is created for every profiler.</p> * * <p>Any profiling steps more verbose than the given level will be ignored.</p> * * <p>The profiler provider constructing the profiler is passed in so that * when {@link #stop()} is called, the profiler can notify the provider to store * the profiling info for later retrieval via * {@link ProfilerProvider#stopSession(ProfilerImpl, boolean)}.</p> * * @param name name of the request * @param rootName name of the root timing step to start * @param level the level of the profiler * @param profilerProvider the profiler provider constructing the */ public ProfilerImpl(String name, String rootName, ProfileLevel level, ProfilerProvider profilerProvider) { this(null, name, rootName, level, profilerProvider); } /** * Construct a new profiling session. * * <p>This will create an implicit root {@link Timing} step considered to have * started now, with the given root name.</p> * * <p>A new random UUID id is created for every profiler.</p> * * <p>Any profiling steps more verbose than the given level will be ignored.</p> * * <p>The profiler provider constructing the profiler is passed in so that * when {@link #stop()} is called, the profiler can notify the provider to store * the profiling info for later retrieval via * {@link ProfilerProvider#stopSession(ProfilerImpl, boolean)}.</p> * * @param id the id to use, or null to generate a random uuid * @param name name of the request * @param rootName name of the root timing step to start * @param level the level of the profiler * @param profilerProvider the profiler provider constructing the */ public ProfilerImpl(UUID id, String name, String rootName, ProfileLevel level, ProfilerProvider profilerProvider) { this.id = id != null ? id : UUID.randomUUID(); this.name = name; this.profilerProvider = profilerProvider; this.level = level; started = System.currentTimeMillis(); root = new TimingImpl(this, null, rootName); head = root; } public long getDurationMilliseconds() { Long milliseconds = root.getDurationMilliseconds(); return milliseconds != null ? milliseconds : System.currentTimeMillis() - started; } public void stop() { stop(false); } public void stop(boolean discardResults) { if (!stopped) { stopped = true; root.stop(); profilerProvider.stopSession(this, discardResults); } } public Timing step(String name, ProfileLevel level) { if (level.ordinal() > this.level.ordinal()) { return NullTiming.INSTANCE; } else { return new TimingImpl(this, head, name); } } public Timing step(String name) { return step(name, ProfileLevel.Info); } public void step(String name, ProfileLevel level, Runnable block) { Timing timing = step(name, level); try { block.run(); } finally { timing.stop(); } } public void step(String name, Runnable block) { step(name, ProfileLevel.Info, block); } public <T> T step(String name, ProfileLevel level, Callable<T> function) throws Exception { Timing timing = step(name, level); try { return function.call(); } finally { timing.stop(); } } public <T> T step(String name, Callable<T> function) throws Exception { return step(name, ProfileLevel.Info, function); } public void addCustomTiming(String type, String executeType, String command, long duration) { addCustomTiming(type, new CustomTiming(executeType, command, duration)); } public void addCustomTiming(String type, CustomTiming customTiming) { if (head != null) { head.addCustomTiming(type, customTiming); } } public LinkedHashMap<String, Object> toMap() { LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>(11); map.put("Id", id.toString()); map.put("Name", name); map.put("Started", started); map.put("DurationMilliseconds", getDurationMilliseconds()); map.put("MachineName", machineName); map.put("Root", root.toMap()); // TODO support ClientTimings and CustomLinks map.put("ClientTimings", null); return map; } /** * Render a plain test version of this profiler, for logging * * @return the plain text representation */ public String renderPlainText() { StringBuilder text = new StringBuilder(); text.append(machineName).append(" at ").append(new Date()).append("\n"); Stack<Timing> timings = new Stack<Timing>(); timings.push(root); while (!timings.isEmpty()) { Timing timing = timings.pop(); appendTimingPrefix(text, timing); text.append(String.format(" %s = %,dms", timing.getName(), timing.getDurationMilliseconds())); Map<String, List<CustomTiming>> customTimings = timing.getCustomTimings(); if (customTimings != null) { for (Map.Entry<String, List<CustomTiming>> entry : customTimings.entrySet()) { String type = entry.getKey(); List<CustomTiming> typeCustomTimings = entry.getValue(); long sum = 0; for (CustomTiming customTiming : typeCustomTimings) { sum += customTiming.getDurationMilliseconds(); } text.append(String.format(" (%s = %,dms in %d cmd%s)", type, sum, customTimings.size(), customTimings.size() == 1 ? "" : "s")); } } text.append("\n"); List<Timing> children = timing.getChildren(); if (children != null) { for (int i = children.size() - 1; i >= 0; i--) { timings.push(children.get(i)); } } } return text.toString(); } private void appendTimingPrefix(StringBuilder sb, Timing timing) { int depth = timing.getDepth(); for (int i = 0; i < depth; i++) { sb.append('>'); } } public UUID getId() { return id; } public String getMachineName() { return machineName; } public void setMachineName(String machineName) { this.machineName = machineName; } public ProfileLevel getLevel() { return level; } public Timing getRoot() { return root; } public boolean hasQueryTimings() { return hasQueryTimings; } public Timing getHead() { return head; } public void setHead(TimingImpl head) { this.head = head; } public long getStarted() { return started; } @Override public String getUser() { return user; } @Override public void setUser(String user) { this.user = user; } public void setHasQueryTimings(boolean hasQueryTimings) { this.hasQueryTimings = hasQueryTimings; } @Override public void close() { stop(); } }