Timing.java revision 988:d81f7c897bdf
1/*
2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package jdk.nashorn.internal.runtime;
26
27import java.io.BufferedReader;
28import java.io.IOException;
29import java.io.StringReader;
30import java.util.ArrayList;
31import java.util.LinkedHashMap;
32import java.util.List;
33import java.util.Map;
34import java.util.concurrent.TimeUnit;
35import java.util.function.Supplier;
36import jdk.nashorn.internal.runtime.logging.DebugLogger;
37import jdk.nashorn.internal.runtime.logging.Loggable;
38import jdk.nashorn.internal.runtime.logging.Logger;
39
40/**
41 * Simple wallclock timing framework
42 */
43@Logger(name="time")
44public final class Timing implements Loggable {
45
46    private DebugLogger log;
47    private TimeSupplier timeSupplier;
48    private final boolean isEnabled;
49    private final long startTime;
50
51    private static final String LOGGER_NAME = Timing.class.getAnnotation(Logger.class).name();
52
53    /**
54     * Instantiate singleton timer for ScriptEnvironment
55     * @param isEnabled true if enabled, otherwise we keep the instance around
56     *      for code brevity and "isEnabled" checks, but never instantiate anything
57     *      inside it
58     */
59    public Timing(final boolean isEnabled) {
60        this.isEnabled = isEnabled;
61        this.startTime = System.nanoTime();
62    }
63
64    /**
65     * Get the log info accumulated by this Timing instance
66     * @return log info as one string
67     */
68    public String getLogInfo() {
69        assert isEnabled();
70        return timeSupplier.get();
71    }
72
73    /**
74     * Get the log info accumulated by this Timing instance
75     * @return log info as and array of strings, one per line
76     */
77    public String[] getLogInfoLines() {
78        assert isEnabled();
79        return timeSupplier.getStrings();
80    }
81
82    /**
83     * Check if timing is enabled
84     * @return true if timing is enabled
85     */
86    boolean isEnabled() {
87        return isEnabled;
88    }
89
90    /**
91     * When timing, this can be called to register a new module for timing
92     * or add to its accumulated time
93     *
94     * @param module   module name
95     * @param durationNano duration to add to accumulated time for module, in nanoseconds.
96     */
97    public void accumulateTime(final String module, final long durationNano) {
98        if (isEnabled()) {
99            ensureInitialized(Context.getContextTrusted());
100            timeSupplier.accumulateTime(module, durationNano);
101        }
102    }
103
104    private DebugLogger ensureInitialized(final Context context) {
105        //lazy init, as there is not necessarily a context available when
106        //a ScriptEnvironment gets initialize
107        if (isEnabled() && log == null) {
108            log = initLogger(context);
109            if (log.isEnabled()) {
110                this.timeSupplier = new TimeSupplier();
111                Runtime.getRuntime().addShutdownHook(
112                        new Thread() {
113                            @Override
114                            public void run() {
115                                //System.err.println because the context and the output streams may be gone
116                                //when the shutdown hook executes
117                                final StringBuilder sb = new StringBuilder();
118                                for (final String str : timeSupplier.getStrings()) {
119                                    sb.append('[').
120                                        append(Timing.getLoggerName()).
121                                        append("] ").
122                                        append(str).
123                                        append('\n');
124                                }
125                                System.err.print(sb);
126                            }
127                        });
128            }
129        }
130        return log;
131    }
132
133    static String getLoggerName() {
134        return LOGGER_NAME;
135    }
136
137    @Override
138    public DebugLogger initLogger(final Context context) {
139        return context.getLogger(this.getClass());
140    }
141
142    @Override
143    public DebugLogger getLogger() {
144        return log;
145    }
146
147    /**
148     * Takes a duration in nanoseconds, and returns a string representation of it rounded to milliseconds.
149     * @param durationNano duration in nanoseconds
150     * @return the string representing the duration in milliseconds.
151     */
152    public static String toMillisPrint(final long durationNano) {
153        return Long.toString(TimeUnit.NANOSECONDS.toMillis(durationNano));
154    }
155
156    final class TimeSupplier implements Supplier<String> {
157        private final Map<String, Long> timings;
158
159        TimeSupplier() {
160            timings   = new LinkedHashMap<>();
161        }
162
163        String[] getStrings() {
164            final List<String> strs = new ArrayList<>();
165            final BufferedReader br = new BufferedReader(new StringReader(get()));
166            String line;
167            try {
168                while ((line = br.readLine()) != null) {
169                    strs.add(line);
170                }
171            } catch (final IOException e) {
172                throw new RuntimeException(e);
173            }
174            return strs.toArray(new String[strs.size()]);
175        }
176
177        @Override
178        public String get() {
179            final long t = System.nanoTime();
180
181            long knownTime = 0L;
182            int  maxKeyLength = 0;
183            int  maxValueLength = 0;
184
185            for (final Map.Entry<String, Long> entry : timings.entrySet()) {
186                maxKeyLength   = Math.max(maxKeyLength, entry.getKey().length());
187                maxValueLength = Math.max(maxValueLength, toMillisPrint(entry.getValue()).length());
188            }
189            maxKeyLength++;
190
191            final StringBuilder sb = new StringBuilder();
192            sb.append("Accumulated complation phase Timings:\n\n");
193            for (final Map.Entry<String, Long> entry : timings.entrySet()) {
194                int len;
195
196                len = sb.length();
197                sb.append(entry.getKey());
198                len = sb.length() - len;
199
200                while (len++ < maxKeyLength) {
201                    sb.append(' ');
202                }
203
204                final Long duration = entry.getValue();
205                final String strDuration = toMillisPrint(duration);
206                len = strDuration.length();
207                for (int i = 0; i < maxValueLength - len; i++) {
208                    sb.append(' ');
209                }
210
211                sb.append(strDuration).
212                    append(" ms\n");
213
214                knownTime += duration;
215            }
216
217            final long total = t - startTime;
218            sb.append('\n');
219            sb.append("Total runtime: ").
220                append(toMillisPrint(total)).
221                append(" ms (Non-runtime: ").
222                append(toMillisPrint(knownTime)).
223                append(" ms [").
224                append((int)(knownTime * 100.0 / total)).
225                append("%])");
226
227            return sb.toString();
228        }
229
230        private void accumulateTime(final String module, final long duration) {
231            Long accumulatedTime = timings.get(module);
232            if (accumulatedTime == null) {
233                accumulatedTime = 0L;
234            }
235            timings.put(module, accumulatedTime + duration);
236        }
237    }
238}
239