001/*
002 * Copyright 2010-2012 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.utils;
018
019import org.jetbrains.annotations.NotNull;
020import org.jetbrains.annotations.Nullable;
021
022import java.io.PrintStream;
023import java.util.Arrays;
024import java.util.Stack;
025import java.util.concurrent.locks.ReentrantLock;
026
027public class Profiler {
028    // The stack is synchronized here: this is intentional
029    private static final ThreadLocal<Stack<Profiler>> PROFILERS = new ThreadLocal<Stack<Profiler>>() {
030        @Override
031        protected Stack<Profiler> initialValue() {
032            return new Stack<Profiler>();
033        }
034    };
035
036    private static final ReentrantLock OUT_LOCK = new ReentrantLock();
037
038    @NotNull
039    public static Profiler create(@NotNull String name) {
040        return create(name, null);
041    }
042
043    @NotNull
044    public static Profiler create(@NotNull String name, @Nullable PrintStream out) {
045        Profiler profiler = new Profiler(name, out);
046        PROFILERS.get().push(profiler);
047        return profiler;
048    }
049
050    public static Profiler getFromContext() {
051        Stack<Profiler> profilers = PROFILERS.get();
052        if (profilers.isEmpty()) {
053            throw new UnsupportedOperationException();
054        }
055        return profilers.peek();
056    }
057
058    private final String name;
059    private final PrintStream out;
060    private long start = Long.MAX_VALUE;
061    private long cumulative = 0;
062    private boolean paused = true;
063    private StackTraceElement[] stackTrace;
064    private boolean mute;
065
066    private Profiler(@NotNull String name, @Nullable PrintStream out) {
067        this.name = name;
068        this.out = out == null ? System.out : out;
069    }
070
071    public Profiler recordStackTrace(int depth) {
072        return recordStackTrace(1 /*skipping this frame*/, depth);
073    }
074
075    public Profiler recordStackTrace(int skip, int depth) {
076        StackTraceElement[] trace = new Throwable().getStackTrace();
077
078        int from = 1 + skip;
079        if (from >= trace.length) return this;
080
081        int to;
082        if (depth == -1) {
083            to = trace.length;
084        }
085        else {
086            to = Math.min(skip + depth + 1, trace.length);
087        }
088
089        stackTrace = Arrays.copyOfRange(trace, from, to);
090        return this;
091    }
092
093    public Profiler resetStackTrace() {
094        stackTrace = null;
095        return this;
096    }
097
098    public Profiler printStackTrace() {
099        if (stackTrace != null) {
100            OUT_LOCK.lock();
101            try {
102                for (StackTraceElement element : stackTrace) {
103                    println("\tat ", element);
104                }
105            }
106            finally {
107                OUT_LOCK.unlock();
108            }
109        }
110        return this;
111    }
112
113    public Profiler printEntering() {
114        println("Entering ", name);
115        return this;
116    }
117
118    public Profiler start() {
119        if (paused) {
120            start = System.nanoTime();
121            paused = false;
122        }
123        return this;
124    }
125
126    public Profiler end() {
127        long result = cumulative;
128        if (!paused) {
129            result += System.nanoTime() - start;
130        }
131        paused = true;
132        cumulative = 0;
133
134        OUT_LOCK.lock();
135        try {
136            println(name, " took ", format(result));
137            printStackTrace();
138        }
139        finally {
140            OUT_LOCK.unlock();
141        }
142
143        return this;
144    }
145
146    public Profiler pause() {
147        if (!paused) {
148            cumulative += System.nanoTime() - start;
149            paused = true;
150        }
151        return this;
152    }
153
154    public Profiler mute() {
155        mute = true;
156        return this;
157    }
158
159    public Profiler unmute() {
160        mute = false;
161        return this;
162    }
163
164    public Profiler println(Object message) {
165        if (!mute) {
166            out.println(message);
167        }
168        return this;
169    }
170
171    public Profiler println(Object a, Object b) {
172        if (!mute) {
173            OUT_LOCK.lock();
174            try {
175                out.print(a);
176                out.println(b);
177            }
178            finally {
179                OUT_LOCK.unlock();
180            }
181        }
182        return this;
183    }
184
185    public Profiler println(Object a, Object b, Object c) {
186        if (!mute) {
187            OUT_LOCK.lock();
188            try {
189                out.print(a);
190                out.print(b);
191                out.println(c);
192            }
193            finally {
194                OUT_LOCK.unlock();
195            }
196        }
197        return this;
198    }
199
200    public Profiler println(Object a, Object b, Object c, Object... rest) {
201        if (!mute) {
202            OUT_LOCK.lock();
203            try {
204                out.print(a);
205                out.print(b);
206                out.print(c);
207                for (Object o : rest) {
208                    out.print(o);
209                }
210                out.println();
211            }
212            finally {
213                OUT_LOCK.unlock();
214            }
215        }
216        return this;
217    }
218
219    private static String format(long delta) {
220        return String.format("%.3fs", delta / 1e9);
221    }
222}