001 /*
002 * Copyright 2010-2015 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
017 package org.jetbrains.kotlin.utils;
018
019 import com.intellij.openapi.diagnostic.Logger;
020 import org.jetbrains.annotations.NotNull;
021
022 import java.io.PrintStream;
023 import java.util.Arrays;
024 import java.util.Stack;
025 import java.util.concurrent.locks.ReentrantLock;
026
027 public 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 //noinspection UseOfSystemOutOrSystemErr
041 return create(name, System.out);
042 }
043
044 @NotNull
045 public static Profiler create(@NotNull String name, @NotNull PrintStream out) {
046 return create(name, new PrintingLogger(out));
047 }
048
049 @NotNull
050 public static Profiler create(@NotNull String name, @NotNull Logger log) {
051 Profiler profiler = new Profiler(name, log);
052 PROFILERS.get().push(profiler);
053 return profiler;
054 }
055
056 public static Profiler getFromContext() {
057 Stack<Profiler> profilers = PROFILERS.get();
058 if (profilers.isEmpty()) {
059 throw new UnsupportedOperationException();
060 }
061 return profilers.peek();
062 }
063
064 private final String name;
065 private final Logger log;
066 private long start = Long.MAX_VALUE;
067 private long cumulative = 0;
068 private boolean paused = true;
069 private StackTraceElement[] stackTrace;
070 private boolean mute;
071
072 private Profiler(@NotNull String name, @NotNull Logger log) {
073 this.name = name;
074 this.log = log;
075 }
076
077 public Profiler recordStackTrace(int depth) {
078 return recordStackTrace(1 /*skipping this frame*/, depth);
079 }
080
081 public Profiler recordStackTrace(int skip, int depth) {
082 StackTraceElement[] trace = new Throwable().getStackTrace();
083
084 int from = 1 + skip;
085 if (from >= trace.length) return this;
086
087 int to;
088 if (depth == -1) {
089 to = trace.length;
090 }
091 else {
092 to = Math.min(skip + depth + 1, trace.length);
093 }
094
095 stackTrace = Arrays.copyOfRange(trace, from, to);
096 return this;
097 }
098
099 public Profiler resetStackTrace() {
100 stackTrace = null;
101 return this;
102 }
103
104 public Profiler printStackTrace() {
105 if (stackTrace != null && log.isDebugEnabled()) {
106 OUT_LOCK.lock();
107 try {
108 for (StackTraceElement element : stackTrace) {
109 println("\tat ", element);
110 }
111 }
112 finally {
113 OUT_LOCK.unlock();
114 }
115 }
116 return this;
117 }
118
119 public Profiler printEntering() {
120 println("Entering ", name);
121 return this;
122 }
123
124 public Profiler printThreadName() {
125 println(Thread.currentThread().getName() + " ", name);
126 return this;
127 }
128
129 public Profiler start() {
130 if (paused) {
131 start = System.nanoTime();
132 paused = false;
133 }
134 return this;
135 }
136
137 public Profiler end() {
138 long result = cumulative;
139 if (!paused) {
140 result += System.nanoTime() - start;
141 }
142 paused = true;
143 cumulative = 0;
144
145 if (!mute && log.isDebugEnabled()) {
146 OUT_LOCK.lock();
147 try {
148 println(name, " took ", format(result));
149 printStackTrace();
150 }
151 finally {
152 OUT_LOCK.unlock();
153 }
154 }
155
156 return this;
157 }
158
159 public Profiler pause() {
160 if (!paused) {
161 cumulative += System.nanoTime() - start;
162 paused = true;
163 }
164 return this;
165 }
166
167 public Profiler mute() {
168 mute = true;
169 return this;
170 }
171
172 public Profiler unmute() {
173 mute = false;
174 return this;
175 }
176
177 public Profiler println(Object message) {
178 if (!mute && log.isDebugEnabled()) {
179 log.debug(String.valueOf(message));
180 }
181 return this;
182 }
183
184 public Profiler println(Object a, Object b) {
185 if (!mute && log.isDebugEnabled()) {
186 OUT_LOCK.lock();
187 try {
188 log.debug(String.valueOf(a) + b);
189 }
190 finally {
191 OUT_LOCK.unlock();
192 }
193 }
194 return this;
195 }
196
197 public Profiler println(Object a, Object b, Object c) {
198 if (!mute && log.isDebugEnabled()) {
199 OUT_LOCK.lock();
200 try {
201 log.debug(String.valueOf(a) + b + c);
202 }
203 finally {
204 OUT_LOCK.unlock();
205 }
206 }
207 return this;
208 }
209
210 public Profiler println(Object a, Object b, Object c, Object... rest) {
211 if (!mute && log.isDebugEnabled()) {
212 OUT_LOCK.lock();
213 try {
214 StringBuilder sb = new StringBuilder();
215 sb.append(a);
216 sb.append(b);
217 sb.append(c);
218 for (Object o : rest) {
219 sb.append(o);
220 }
221 log.debug(sb.toString());
222 }
223 finally {
224 OUT_LOCK.unlock();
225 }
226 }
227 return this;
228 }
229
230 private static String format(long delta) {
231 return String.format("%.3fs", delta / 1e9);
232 }
233 }