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    }