001/*
002 * (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others.
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 * Contributors:
017 *     Nuxeo - initial API and implementation
018 */
019package org.nuxeo.runtime.util;
020
021import java.text.DecimalFormat;
022import java.util.HashMap;
023import java.util.Map;
024import java.util.Objects;
025import java.util.concurrent.TimeUnit;
026
027/**
028 * Usage:
029 *
030 * <pre>
031 * <code>
032 * Watch w = new Watch()
033 * w.start();
034 * ...
035 * w.start("interval-name")
036 * w.stop("interval-name")
037 * ..
038 * w.stop()
039 * </code>
040 * </pre>
041 *
042 * @author bogdan
043 * @since 9.2
044 */
045public class Watch {
046
047    public final TimeInterval total;
048
049    public final Map<String, TimeInterval> intervals;
050
051    public Watch() {
052        this(new HashMap<>());
053    }
054
055    public Watch(Map<String, TimeInterval> intervals) {
056        this.total = new TimeInterval("total");
057        this.intervals = Objects.requireNonNull(intervals, "Given intervals can't be null");
058    }
059
060    public Watch start() {
061        // reset if needed
062        total.t0 = 0;
063        total.t1 = 0;
064        intervals.clear();
065
066        total.start();
067        return this;
068    }
069
070    public Watch stop() {
071        total.stop();
072        return this;
073    }
074
075    public Watch start(String interval) {
076        intervals.computeIfAbsent(interval, TimeInterval::new).start();
077        return this;
078    }
079
080    public Watch stop(String interval) {
081        TimeInterval ti = intervals.get(interval);
082        if (ti != null) {
083            ti.stop();
084        }
085        return this;
086    }
087
088    public long elapsed(TimeUnit unit) {
089        return total.elapsed(unit);
090    }
091
092    public long elapsed(String name, TimeUnit unit) {
093        TimeInterval ti = intervals.get(name);
094        if (ti != null) {
095            return ti.elapsed(unit);
096        }
097        return 0;
098    }
099
100    public TimeInterval getTotal() {
101        return total;
102    }
103
104    public TimeInterval[] getIntervals() {
105        return intervals.values().toArray(new TimeInterval[intervals.size()]);
106    }
107
108    public static class TimeInterval implements Comparable<TimeInterval> {
109
110        protected final String name;
111
112        protected long t0;
113
114        protected long t1;
115
116        public TimeInterval(String name) {
117            this.name = name;
118        }
119
120        public String getName() {
121            return name;
122        }
123
124        /**
125         * Elapsed time in nano seconds
126         */
127        public long elapsed() {
128            return t1 - t0;
129        }
130
131        public long elapsed(TimeUnit unit) {
132            return unit.convert(t1 - t0, TimeUnit.NANOSECONDS);
133        }
134
135        protected void start() {
136            this.t0 = System.nanoTime();
137        }
138
139        protected void stop() {
140            this.t1 = System.nanoTime();
141        }
142
143        public boolean isStopped() {
144            return t1 != 0;
145        }
146
147        @Override
148        public int compareTo(TimeInterval o) {
149            long dt = (t1 - t0) - (o.t1 - o.t0); // this may be out of range for an int
150            return dt < 0 ? -1 : (dt > 0 ? 1 : 0);
151        }
152
153        public String formatSeconds() {
154            return new DecimalFormat("0.000").format(((double) this.t1 - this.t0) / 1000000000);
155        }
156
157        @Override
158        public String toString() {
159            return name + ": " + this.formatSeconds() + " sec.";
160        }
161
162    }
163
164}