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 start() {
125            if (paused) {
126                start = System.nanoTime();
127                paused = false;
128            }
129            return this;
130        }
131    
132        public Profiler end() {
133            long result = cumulative;
134            if (!paused) {
135                result += System.nanoTime() - start;
136            }
137            paused = true;
138            cumulative = 0;
139    
140            if (log.isDebugEnabled()) {
141                OUT_LOCK.lock();
142                try {
143                    println(name, " took ", format(result));
144                    printStackTrace();
145                }
146                finally {
147                    OUT_LOCK.unlock();
148                }
149            }
150    
151            return this;
152        }
153    
154        public Profiler pause() {
155            if (!paused) {
156                cumulative += System.nanoTime() - start;
157                paused = true;
158            }
159            return this;
160        }
161    
162        public Profiler mute() {
163            mute = true;
164            return this;
165        }
166    
167        public Profiler unmute() {
168            mute = false;
169            return this;
170        }
171    
172        public Profiler println(Object message) {
173            if (!mute && log.isDebugEnabled()) {
174                log.debug(String.valueOf(message));
175            }
176            return this;
177        }
178    
179        public Profiler println(Object a, Object b) {
180            if (!mute && log.isDebugEnabled()) {
181                OUT_LOCK.lock();
182                try {
183                    log.debug(String.valueOf(a) + b);
184                }
185                finally {
186                    OUT_LOCK.unlock();
187                }
188            }
189            return this;
190        }
191    
192        public Profiler println(Object a, Object b, Object c) {
193            if (!mute && log.isDebugEnabled()) {
194                OUT_LOCK.lock();
195                try {
196                    log.debug(String.valueOf(a) + b + c);
197                }
198                finally {
199                    OUT_LOCK.unlock();
200                }
201            }
202            return this;
203        }
204    
205        public Profiler println(Object a, Object b, Object c, Object... rest) {
206            if (!mute && log.isDebugEnabled()) {
207                OUT_LOCK.lock();
208                try {
209                    StringBuilder sb = new StringBuilder();
210                    sb.append(a);
211                    sb.append(b);
212                    sb.append(c);
213                    for (Object o : rest) {
214                        sb.append(o);
215                    }
216                    log.debug(sb.toString());
217                }
218                finally {
219                    OUT_LOCK.unlock();
220                }
221            }
222            return this;
223        }
224    
225        private static String format(long delta) {
226            return String.format("%.3fs", delta / 1e9);
227        }
228    }