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

com.google.javascript.jscomp.Tracer Maven / Gradle / Ivy

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * Copyright 2002 The Closure Compiler 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 com.google.javascript.jscomp;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.nullToEmpty;
import static java.lang.Math.max;
import static java.lang.Math.min;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jspecify.nullness.Nullable;

/**
 * Tracer provides a simple way to trace the handling of a request.
 *
 * By timing likely slow points in the code you can quickly pinpoint
 * why a request is slow.
 *
 * 

Example usage: *

 * Tracer.initCurrentThreadTrace(); // must be called in each Thread
 * Tracer wholeRequest = new Tracer(null, "Request " + params);
 * try {
 *   ...
 *   t = new Tracer("Database", "getName()");
 *   try {
 *     name = database.getName();
 *   } finally {
 *     t.stop();
 *   }
 *   ...
 *   t = new Tracer(null, "call sendmail");
 *   try {
 *     sendMessage();
 *   } finally {
 *     t.stop();
 *   }
 *   ...
 *   t = new Tracer("Database", "updateinfo()");
 *   try {
 *     database.updateinfo("new info");
 *   } finally {
 *     t.stop();
 *   }
 *   ...
 * } finally {
 *   if (wholeRequest.stop() > 1000) {
 *     // more than a second, better log
 *     Tracer.logAndClearCurrentThreadTrace();
 *   } else {
 *     Tracer.clearCurrentThreadTrace();
 *   }
 * }
 * 
* * Now slow requests will produce a report like this: *
 *       10.452 Start        Request cmd=dostuff
 *     3 10.455 Start        [Database] getName()
 *    34 10.489 Done   34 ms [Database] getName()
 *     3 10.491 Start        call sendmail
 *  1042 11.533 Done 1042 ms call sendmail
 *     0 11.533 Start        [Database] updateinfo()
 *     3 11.536 Done    3 ms [Database] updateinfo()
 *    64 11.600 Done 1148 ms Request cmd=dostuff
 *   TOTAL Database 2 (37 ms)
 * 
* * If you enabled pretty-printing by calling {@link Tracer#setPrettyPrint}, * it will print more easily readable reports that use indentation to visualize * the tracer hierarchy and dynamically adjusts the padding to handle large * durations. Like: *
 *       10.452 Start        Request cmd=dostuff
 *     3 10.455 Start        | [Database] getName()
 *    34 10.489 Done   34 ms | [Database] getName()
 *     3 10.491 Start        | call sendmail
 *  1042 11.533 Done 1042 ms | call sendmail
 *     0 11.533 Start        | [Database] updateinfo()
 *     3 11.536 Done    3 ms | [Database] updateinfo()
 *    64 11.600 Done 1148 ms Request cmd=dostuff
 *   TOTAL Database 2 (37 ms)
 * 
* Pretty-printing is an application global setting and should only be called * in the main setup of an application, not in library code. * * Now you can easily see that sendmail is causing your problems, not * the two database calls. * * You can easily add additional tracing statistics to your Trace output by * adding additional tracing statistics. Simply add to your initialization code: *
 *    Tracer.addTracingStatistic(myTracingStatistic)
 * 
* where myTracingStatistic implements the {@link TracingStatistic} interface. * The class com.google.monitoring.tracing.TracingStatistics contains * several useful statistics such as CPU time, wait time, and memory usage. * If you add your own tracing statistics, the output is not quite as pretty, * but includes additional useful information. *

If a Trace is given a type (the first argument to the constructor) and * multiple Traces are done on that type then a "TOTAL line will be * produced showing the total number of traces and the sum of the time * ("TOTAL Database 2 (37 ms)" in our example). These traces should be * mutually exclusive or else the sum won't make sense (the time will * be double counted if the second starts before the first ends). * *

It is also possible to have a "silent" Tracer which does not appear * in the trace because it was faster than the silence threshold. This * threshold can be set for the for the current ThreadTrace with * setDefaultSilenceThreshold(threshold), or on a per-Tracer basis with * t.stop(threshold). Silent tracers are still counted in the type * totals, so these events are not completely lost. * *

WARNING: This code makes a big assumption that * everything for a given trace is done within a single thread. * It uses threads to identify requests. It is fine to have multiple * requests traced in multiple simultaneous threads but it is not ok * to have any given request traced in multiple threads. (the results * will be scattered across reports). * * Java objects do not support destructors (as in C++) so Tracer is not robust * when exceptions are thrown. Each Tracer object should be wrapped in a * try/finally block so that if an exception is thrown, the Tracer.stop() * method is guaranteed to be called. * *

A thread must call {@link Tracer#initCurrentThreadTrace()} to enable the * Tracer logging, otherwise Tracer does nothing. The requirement to call * {@code initCurrentThreadTrace} avoids the situation where Tracer is called * without the explicit knowledge of the application authors because they * happen to use a class in another package that uses Tracer. If {@link * Tracer#logCurrentThreadTrace} is called without calling {@link * Tracer#initCurrentThreadTrace()}, then a Third Eye WARNING message is logged, * which should help track down the problem. */ final class Tracer { // package-private for access from unit tests static final Logger logger = Logger.getLogger(Tracer.class.getName()); /** * Whether pretty printing is enabled. This is intended to be set once * at application startup. */ private static volatile boolean defaultPrettyPrint; /* This list is guaranteed to only increase in length. It contains * a list of additional statistics that the user wants to keep track * of. */ private static final List extraTracingStatistics = new CopyOnWriteArrayList<>(); /** Values returned by extraTracingStatistics */ private long[] extraTracingValues; /** The type for grouping traces, may be null */ private final @Nullable String type; /** A comment string for the report */ private final String comment; /** Start time of the trace */ private final long startTimeMs; /** Stop time of the trace, non-final */ private long stopTimeMs; /** * Record our starter thread in order to trap Traces that are started in one * thread and stopped in another */ final Thread startThread; /** * We limit the number of events in a Trace in order to catch memory * leaks (a thread that keeps logging events and never clears them). * This number is arbitrary and can be increased if necessary (though * if there are more than 1000 events then the Tracer is probably being * misused). */ static final int MAX_TRACE_SIZE = 1000; /** * For unit testing. Can't use {@link com.google.common.time.Clock} because * this code is in base and has minimal dependencies. */ static interface InternalClock { long currentTimeMillis(); } /** * Default clock that calls through to the system clock. Can be overridden * in unit tests. */ static InternalClock clock = new InternalClock() { @Override public long currentTimeMillis() { return System.currentTimeMillis(); } }; /** * Create and start a tracer. * Both type and comment may be null. See class comment for usage. * * @param type The type for totaling * @param comment Comment about this tracer */ Tracer(@Nullable String type, @Nullable String comment) { this.type = type; this.comment = nullToEmpty(comment); startTimeMs = clock.currentTimeMillis(); startThread = Thread.currentThread(); if (!extraTracingStatistics.isEmpty()) { int size = extraTracingStatistics.size(); extraTracingValues = new long[size]; int i = 0; for (TracingStatistic tracingStatistic : extraTracingStatistics) { extraTracingValues[i] = tracingStatistic.start(startThread); i++; } } ThreadTrace trace = getThreadTrace(); // Do nothing if the current thread trace wasn't initialized. if (!trace.isInitialized()) { return; } // Check if we are creating too many Tracers. if (trace.events.size() >= MAX_TRACE_SIZE) { logger.log(Level.WARNING, "Giant thread trace. Too many Tracers created. " + "Clearing to avoid memory leak.", new Throwable(trace.toString())); trace.truncateEvents(); } // Check if we forgot to close the Tracers. if (trace.outstandingEvents.size() >= MAX_TRACE_SIZE) { logger.log(Level.WARNING, "Too many outstanding Tracers. Tracer.stop() is missing " + "or Tracer.stop() is not wrapped in a " + "try/finally block. " + "Clearing to avoid memory leak.", new Throwable(trace.toString())); trace.truncateOutstandingEvents(); } trace.startEvent(this); } /** * Create a tracer that isn't summed as part of a total * * @param comment Comment about this tracer */ Tracer(String comment) { this(null, comment); } /** * Converts 'v' to a string and pads it with up to 16 spaces for * improved alignment. * @param v The value to convert. * @param digitsColumnWidth The desired with of the string. */ private static String longToPaddedString(long v, int digitsColumnWidth) { int digitWidth = numDigits(v); StringBuilder sb = new StringBuilder(); appendSpaces(sb, digitsColumnWidth - digitWidth); sb.append(v); return sb.toString(); } /** * Gets the number of digits in an integer when printed in base 10. Assumes * a positive integer. * @param n The value. * @return The number of digits in the string. */ private static int numDigits(long n) { int i = 0; do { i++; n = n / 10; } while (n > 0); return i; } /** * Gets a string of spaces of the length specified. * @param sb The string builder to append to. * @param numSpaces The number of spaces in the string. */ @VisibleForTesting static void appendSpaces(StringBuilder sb, int numSpaces) { if (numSpaces > 16) { logger.warning("Tracer.appendSpaces called with large numSpaces"); // Avoid long loop in case some bug in the caller numSpaces = 16; } while (numSpaces >= 5) { sb.append(" "); numSpaces -= 5; } // We know it's less than 5 now switch (numSpaces) { case 1: sb.append(" "); break; case 2: sb.append(" "); break; case 3: sb.append(" "); break; case 4: sb.append(" "); break; } } /** * Adds a new tracing statistic to a trace * * @param tracingStatistic to enable a run * @return The index of this statistic (for use with stat.extraInfo()), or * -1 if the statistic is not enabled. */ static int addTracingStatistic(TracingStatistic tracingStatistic) { // Check to see if we can enable the tracing statistic before actually // adding it. if (tracingStatistic.enable()) { // No synchronization needed, since this is a copy-on-write array. extraTracingStatistics.add(tracingStatistic); // 99.9% of the time, this will be O(1) and return // extraTracingStatistics.length - 1 return extraTracingStatistics.lastIndexOf(tracingStatistic); } else { return -1; } } /** * For testing purposes only. These removes all current tracers. * Severe errors can occur if there are any active tracers going on * when this is called. * * The test suite uses this to remove any tracers that it has added. */ @VisibleForTesting static void clearTracingStatisticsTestingOnly() { extraTracingStatistics.clear(); } /** * Stop the trace. * This may only be done once and must be done from the same thread * that started it. * @param silenceThreshold Traces for time less than silence_threshold * ms will be left out of the trace report. A value of -1 indicates * that the current ThreadTrace silence_threshold should be used. * @return The time that this trace actually ran */ long stop(int silenceThreshold) { checkState(Thread.currentThread() == startThread); ThreadTrace trace = getThreadTrace(); // Do nothing if the thread trace was not initialized. if (!trace.isInitialized()) { return 0; } stopTimeMs = clock.currentTimeMillis(); if (extraTracingValues != null) { // We use extraTracingValues.length rather than // extraTracingStatistics.size() because a new statistic may // have been added for (int i = 0; i < extraTracingValues.length; i++) { long value = extraTracingStatistics.get(i).stop(startThread); extraTracingValues[i] = value - extraTracingValues[i]; } } // Do nothing if the thread trace was not initialized. if (!trace.isInitialized()) { return 0; } trace.endEvent(this, silenceThreshold); return stopTimeMs - startTimeMs; } /** Stop the trace using the default silenceThreshold * * @return The time that this trace actually ran. */ long stop() { return stop(-1); } @Override public String toString() { if (type == null) { return comment; } else { return "[" + type + "] " + comment; } } static void setDefaultSilenceThreshold(int threshold) { getThreadTrace().defaultSilenceThreshold = threshold; } /** * Initialize the trace associated with the current thread by clearing * out any existing trace. There shouldn't be a trace so if one is * found we log it as an error. */ static void initCurrentThreadTrace() { ThreadTrace events = getThreadTrace(); if (!events.isEmpty()) { logger.log(Level.WARNING, "Non-empty timer log:\n" + events, new Throwable()); clearThreadTrace(); // Grab a new thread trace if we find a previous non-empty ThreadTrace. events = getThreadTrace(); } // Mark the thread trace as initialized. events.init(); } static void initCurrentThreadTrace(int defaultSilenceThreshold) { initCurrentThreadTrace(); setDefaultSilenceThreshold(defaultSilenceThreshold); } /** * Returns a timer report similar to the one described in the class comment. * * @return The timer report as a string */ static String getCurrentThreadTraceReport() { return getThreadTrace().toString(); } /** * Logs a timer report similar to the one described in the class comment. */ static void logCurrentThreadTrace() { ThreadTrace trace = getThreadTrace(); // New threads must call Tracer.initCurrentThreadTrace() before Tracer // statistics are gathered. This is a recent change (Jun 2007) that // prevents spurious Third Eye messages when an application uses a class in // a different package that happens to call Tracer without knowledge of the // application authors. if (!trace.isInitialized()) { logger.log( Level.INFO, "Tracer log requested for this thread but was not " + "initialized using Tracer.initCurrentThreadTrace().", new Throwable()); return; } if (!trace.isEmpty()) { logger.log(Level.INFO, "timers:\n{0}", getCurrentThreadTraceReport()); } } /** * Throw away any Trace associated with the current thread. */ static void clearCurrentThreadTrace() { clearThreadTrace(); } /** * logCurrentThreadTrace() then clearCurrentThreadTrace() */ static void logAndClearCurrentThreadTrace() { logCurrentThreadTrace(); clearThreadTrace(); } /** * Sets whether pretty printing is enabled. See class-level comment. This * only affects tracers created after this is called. * @param enabled Whether to enable pretty printing. */ static void setPrettyPrint(boolean enabled) { defaultPrettyPrint = enabled; } /** Statistics for a given tracer type */ static final class Stat { private int count; private int silent; private int clockTime; private int[] extraInfo; /** total count of tracers of a type, including silent * * @return total count of tracers, including silent tracers */ int getCount() { return count; } /** total count of silent tracers of a type * * @return total count of silent tracers */ int getSilentCount() { return silent; } /** total time spent in tracers of a type, in ms * * @return total time spent in tracer, in ms */ int getTotalTime() { return clockTime; } /** total time spent doing additional things that we are clocking */ @VisibleForTesting int getExtraInfo(int index) { return index >= extraInfo.length ? 0 : extraInfo[index]; } } /** This map tracks counts of tracers for each type over all time. */ private static @Nullable AtomicTracerStatMap typeToCountMap; /** This map tracks counts of silent tracers for each type over all time. */ private static @Nullable AtomicTracerStatMap typeToSilentMap; /** This map tracks time (ms) for each type over all time. */ private static @Nullable AtomicTracerStatMap typeToTimeMap; /** * This method MUST be called before getTypeToCountMap (and friends) * will return a valid map. This is because computing this information * imposes a synchronization penalty on every Tracer that is stopped. */ static synchronized void enableTypeMaps() { if (typeToCountMap == null) { typeToCountMap = new AtomicTracerStatMap(); typeToSilentMap = new AtomicTracerStatMap(); typeToTimeMap = new AtomicTracerStatMap(); } } /** * Used for exporting this data via varz. Accesses to this map must be synchronized on the map. If * enableTypeMaps has not been called, this will return null. */ static @Nullable Map getTypeToCountMap() { return typeToCountMap != null ? typeToCountMap.getMap() : null; } /** * Used for exporting this data via varz. Accesses to this map must be synchronized on the map. If * enableTypeMaps has not been called, this will return null. */ static @Nullable Map getTypeToSilentMap() { return typeToSilentMap != null ? typeToSilentMap.getMap() : null; } /** * Used for exporting this data via varz. Accesses to this map must be synchronized on the map. If * enableTypeMaps has not been called, this will return null. */ static @Nullable Map getTypeToTimeMap() { return typeToTimeMap != null ? typeToTimeMap.getMap() : null; } /** Gets the Stat for a tracer type; never returns null */ static Stat getStatsForType(String type) { Stat stat = getThreadTrace().stats.get(type); return stat != null ? stat : ZERO_STAT; } private static final Stat ZERO_STAT = new Stat(); /** Return the sec.ms part of time (if time = "20:06:11.566", "11.566") */ private static String formatTime(long time) { int sec = (int) ((time / 1000) % 60); int ms = (int) (time % 1000); return String.format("%02d.%03d", sec, ms); } /** An event is created every time a Tracer is created or stopped */ private static final class Event { final boolean isStart; // else is_stop final Tracer tracer; Event(boolean start, Tracer t) { isStart = start; tracer = t; } long eventTime() { return isStart ? tracer.startTimeMs : tracer.stopTimeMs; } /** * Converts the event to a formatted string. * @param prevEventTime The time of the previous event which appears at * the left most part of the trace line. * @param indent The indentation to put before the tracer to show the * hierarchy. * @param digitsColWidth How many characters the digits should use. * @return The formatted string. */ String toString(long prevEventTime, String indent, int digitsColWidth) { StringBuilder sb = new StringBuilder(120); if (prevEventTime == -1) { appendSpaces(sb, digitsColWidth); } else { sb.append(longToPaddedString( eventTime() - prevEventTime, digitsColWidth)); } sb.append(' '); sb.append(formatTime(eventTime())); if (isStart) { sb.append(" Start "); appendSpaces(sb, digitsColWidth); sb.append(" "); } else { sb.append(" Done "); long delta = tracer.stopTimeMs - tracer.startTimeMs; sb.append(longToPaddedString(delta, digitsColWidth)); sb.append(" ms "); if (tracer.extraTracingValues != null) { for (int i = 0; i < tracer.extraTracingValues.length; i++) { delta = tracer.extraTracingValues[i]; sb.append(String.format("%4d", delta)); sb.append(extraTracingStatistics.get(i).getUnits()); sb.append("; "); } } } sb.append(indent); sb.append(tracer); return sb.toString(); } } /** Stores a thread's Trace */ static final class ThreadTrace { /** Events taking less than this number of milliseconds are not reported. */ int defaultSilenceThreshold; // non-final /** The Events corresponding to each startEvent/stopEvent */ final ArrayList events = new ArrayList<>(); /** Tracers that have not had their .stop() called */ final HashSet outstandingEvents = new HashSet<>(); /** Map from type to Stat object */ final Map stats = new HashMap<>(); /** * True if {@code outstandingEvents} has been cleared because we exceeded * the max trace limit. */ boolean isOutstandingEventsTruncated = false; /** * True if {@code events} has been cleared because we exceeded the max * trace limit. */ boolean isEventsTruncated = false; /** * Set to true if {@link Tracer#initCurrentThreadTrace()} was called by * the current thread. */ boolean isInitialized = false; /** * Whether pretty printing is enabled for the trace. */ boolean prettyPrint = false; /** Initialize the trace. */ void init() { isInitialized = true; } /** Is initialized? */ boolean isInitialized() { return isInitialized; } /** * Called by the constructor {@link Tracer#Tracer(String, String)} to create * a start event. */ void startEvent(Tracer t) { events.add(new Event(true, t)); boolean notAlreadyOutstanding = outstandingEvents.add(t); checkState(notAlreadyOutstanding); } /** * Called by {@link Tracer#stop()} to create a stop event. */ void endEvent(Tracer t, int silenceThreshold) { boolean wasOutstanding = outstandingEvents.remove(t); if (!wasOutstanding) { if (isOutstandingEventsTruncated) { // The events stack overflowed and was truncated, so just log a // warning. Otherwise, we get an exception which is extremely // confusing. logger.log(Level.WARNING, "event not found, probably because the event stack " + "overflowed and was truncated", new Throwable()); } else { // throw an exception if the event was not found and the events stack // is pristine throw new IllegalStateException(); } } long elapsed = t.stopTimeMs - t.startTimeMs; if (silenceThreshold == -1) { // use default silenceThreshold = defaultSilenceThreshold; } if (elapsed < silenceThreshold) { // If this one is silent then we need to remove the start Event boolean removed = false; for (int i = 0; i < events.size(); i++) { Event e = events.get(i); if (e.tracer == t) { checkState(e.isStart); events.remove(i); removed = true; break; } } // Only assert if we didn't find the original and the events // weren't truncated. checkState(removed || isEventsTruncated); } else { events.add(new Event(false, t)); } if (t.type != null) { Stat stat = stats.get(t.type); if (stat == null) { stat = new Stat(); if (!extraTracingStatistics.isEmpty()) { stat.extraInfo = new int[extraTracingStatistics.size()]; } stats.put(t.type, stat); } stat.count++; if (typeToCountMap != null) { typeToCountMap.incrementBy(t.type, 1); } stat.clockTime += elapsed; if (typeToTimeMap != null) { typeToTimeMap.incrementBy(t.type, elapsed); } if (stat.extraInfo != null && t.extraTracingValues != null) { int overlapLength = min(stat.extraInfo.length, t.extraTracingValues.length); for (int i = 0; i < overlapLength; i++) { stat.extraInfo[i] += t.extraTracingValues[i]; AtomicTracerStatMap map = extraTracingStatistics.get(i).getTracingStat(); if (map != null) { map.incrementBy(t.type, t.extraTracingValues[i]); } } } if (elapsed < silenceThreshold) { stat.silent++; if (typeToSilentMap != null) { typeToSilentMap.incrementBy(t.type, 1); } } } } boolean isEmpty() { return events.isEmpty() && outstandingEvents.isEmpty(); } void truncateOutstandingEvents() { isOutstandingEventsTruncated = true; outstandingEvents.clear(); } void truncateEvents() { isEventsTruncated = true; events.clear(); } /** Produces the lovely Trace seen in the class comments */ // Nullness checker does not understand that prettyPrint => indent != null @SuppressWarnings("nullness") @Override public String toString() { int numDigits = getMaxDigits(); StringBuilder sb = new StringBuilder(); long etime = -1; Deque indent = prettyPrint ? new ArrayDeque() : null; for (Event e : events) { if (prettyPrint && !e.isStart && !indent.isEmpty()) { indent.pop(); } sb.append(" "); if (prettyPrint) { sb.append(e.toString(etime, Joiner.on("").join(indent), numDigits)); } else { sb.append(e.toString(etime, "", 4)); } etime = e.eventTime(); sb.append('\n'); if (prettyPrint && e.isStart) { indent.push("| "); } } if (!outstandingEvents.isEmpty()) { long now = clock.currentTimeMillis(); sb.append(" Unstopped timers:\n"); for (Tracer t : outstandingEvents) { sb.append(" "). append(t). append(" ("). append(now - t.startTimeMs). append(" ms, started at "). append(formatTime(t.startTimeMs)). append(")\n"); } } for (Map.Entry statEntry : stats.entrySet()) { Stat stat = statEntry.getValue(); if (stat.count > 1) { sb.append(" TOTAL "). append(statEntry.getKey()). append(" "). append(stat.count). append(" ("). append(stat.clockTime). append(" ms"); if (stat.extraInfo != null) { for (int i = 0; i < stat.extraInfo.length; i++) { sb.append("; "); sb.append(stat.extraInfo[i]). append(' '). append(extraTracingStatistics.get(i).getUnits()); } } sb.append(")\n"); } } return sb.toString(); } /** * Gets the maximum number of digits that can appear in the tracer output * in the gaps between tracers or the duration of a tracer. This is used * by the pretty printing case so that all of the tracers are aligned. */ private int getMaxDigits() { long etime = -1; long maxTime = 0; for (Event e : events) { if (etime != -1) { long time = e.eventTime() - etime; maxTime = max(maxTime, time); } if (!e.isStart) { long time = e.tracer.stopTimeMs - e.tracer.startTimeMs; maxTime = max(maxTime, time); } etime = e.eventTime(); } // Minimum is 3 to preserve an indent even when max is small. return max(3, numDigits(maxTime)); } } /** Holds the ThreadTrace for each thread. */ private static final ThreadLocal traces = new ThreadLocal<>(); /** * Get the ThreadTrace for the current thread, creating one if necessary. */ static ThreadTrace getThreadTrace() { ThreadTrace t = traces.get(); if (t == null) { t = new ThreadTrace(); t.prettyPrint = defaultPrettyPrint; traces.set(t); } return t; } /** Remove any ThreadTrace associated with the current thread */ static void clearThreadTrace() { traces.remove(); } /** * A TracingStatistic allows the program to add additional optional * statistics to the trace output. * * The class com.google.monitoring.tracing.TracingStatistics * contains several useful tracing statistics * */ static interface TracingStatistic { /** * This method is called at the start of the trace. It should * return a numeric result indicating the amount of the specific * resource in use before the call started * @param thread The current thread * @return A numeric value indicating the amount of the resource * already used. */ long start(Thread thread); /** * This method is called at the end of the trace. It should * return a numeric result indicating the amount of the specific * resource in use after the call ends. The actual reported result * will be the result end() - start() * @param thread The current thread * @return A numeric value indicating the amount of the resource * currently used. */ long stop(Thread thread); /** * Called when this tracing statistic is first enabled. A return * value of True indicates that this statistic can successfully * run in the current JVM. * * @return An indication of whether this statistic can be * implemented in the current JVM. */ boolean enable(); /** Returns this tracing statistic's trace map. * * @return This tracing statistic's trace map. */ AtomicTracerStatMap getTracingStat(); /** A string that should be appended to the numeric output * indicating what this is. * * @return A string indicating the units of this statistic and what it is. */ String getUnits(); } /** * This class encapsulates a map for keeping track of tracing statistics. * It allows the caller to atomically increment named fields. * */ static final class AtomicTracerStatMap { private final ConcurrentMap map = new ConcurrentHashMap<>(); /** * Atomically increment the specified field by the specified amount. * * @param key the name of the field * @param delta the amount by which to increment the field */ // Nullness checker is not powerful enough to prove null-safety of // this method @SuppressWarnings("nullness") void incrementBy(String key, long delta) { // We use a compareAndSet strategy to update the map, which is much // faster when there isn't too much contention. Look at a value, and // conditionally update the map if the value hasn't changed. // If it has changed, repeat. Long oldValue = map.get(key); if (oldValue == null) { // Currently, the slot is empty oldValue = map.putIfAbsent(key, delta); if (oldValue == null) { // The slot was still empty when we set it return; } else { // Someone filled in the slot behind our back. oldValue has // its current value } } while (true) { if (map.replace(key, oldValue, oldValue + delta)) { break; } // Nullness checker doesn't understand that this cannot return null. oldValue = map.get(key); } } /** * Returns a map of key:value pairs. */ Map getMap() { return map; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy