001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.util;
018
019import java.io.Serializable;
020import java.text.DecimalFormat;
021
022/**
023 * Primarily used in unit tests, but can be used to track elapsed time for a request or portion of any other operation
024 * so long as all the timer methods are called on the same thread in which it was started. Calling start on
025 * multiple threads will cause the the times to be aggregated.
026 */
027public class Timer implements Serializable, StringBuilderFormattable
028{
029    private static final long serialVersionUID = 9175191792439630013L;
030
031    private final String name;        // The timer's name
032    public enum Status {
033        Started, Stopped, Paused
034    }
035    private Status status; // The timer's status
036    private long elapsedTime;         // The elapsed time
037    private final int iterations;
038    private static long NANO_PER_SECOND = 1000000000L;
039    private static long NANO_PER_MINUTE = NANO_PER_SECOND * 60;
040    private static long NANO_PER_HOUR = NANO_PER_MINUTE * 60;
041    private ThreadLocal<Long> startTime = new ThreadLocal<Long>() {
042            @Override protected Long initialValue() {
043                return 0L;
044            }
045    };
046
047
048    /**
049     * Constructor.
050     * @param name the timer name.
051     */
052    public Timer(final String name)
053    {
054        this(name, 0);
055    }
056
057    /**
058     * Constructor.
059     *
060     * @param name the timer name.
061     */
062    public Timer(final String name, final int iterations)
063    {
064        this.name = name;
065        status = Status.Stopped;
066        this.iterations = (iterations > 0) ? iterations : 0;
067    }
068
069    /**
070     * Start the timer.
071     */
072    public synchronized void start()
073    {
074        startTime.set(System.nanoTime());
075        elapsedTime = 0;
076        status = Status.Started;
077    }
078
079    public synchronized void startOrResume() {
080        if (status == Status.Stopped) {
081            start();
082        } else {
083            resume();
084        }
085    }
086
087    /**
088     * Stop the timer.
089     */
090    public synchronized String stop()
091    {
092        elapsedTime += System.nanoTime() - startTime.get();
093        startTime.set(0L);
094        status = Status.Stopped;
095        return toString();
096    }
097
098    /**
099     * Pause the timer.
100     */
101    public synchronized void pause()
102    {
103        elapsedTime += System.nanoTime() - startTime.get();
104        startTime.set(0L);
105        status = Status.Paused;
106    }
107
108    /**
109     * Resume the timer.
110     */
111    public synchronized void resume()
112    {
113        startTime.set(System.nanoTime());
114        status = Status.Started;
115    }
116
117    /**
118     * Accessor for the name.
119     * @return the timer's name.
120     */
121    public String getName()
122    {
123        return name;
124    }
125
126    /**
127     * Access the elapsed time.
128     *
129     * @return the elapsed time.
130     */
131    public long getElapsedTime()
132    {
133        return elapsedTime / 1000000;
134    }
135
136    /**
137     * Access the elapsed time.
138     *
139     * @return the elapsed time.
140     */
141    public long getElapsedNanoTime()
142    {
143        return elapsedTime;
144    }
145
146    /**
147     * Returns the name of the last operation performed on this timer (Start, Stop, Pause or
148     * Resume).
149     * @return the string representing the last operation performed.
150     */
151    public Status getStatus()
152    {
153        return status;
154    }
155
156    /**
157     * Returns the String representation of the timer based upon its current state
158     */
159    @Override
160    public String toString()
161    {
162        final StringBuilder result = new StringBuilder();
163        formatTo(result);
164        return result.toString();
165    }
166
167    @Override
168    public void formatTo(final StringBuilder buffer) {
169        buffer.append("Timer ").append(name);
170        switch (status) {
171            case Started:
172                buffer.append(" started");
173                break;
174            case Paused:
175                buffer.append(" paused");
176                break;
177            case Stopped:
178                long nanoseconds = elapsedTime;
179                // Get elapsed hours
180                long hours = nanoseconds / NANO_PER_HOUR;
181                // Get remaining nanoseconds
182                nanoseconds = nanoseconds % NANO_PER_HOUR;
183                // Get minutes
184                long minutes = nanoseconds / NANO_PER_MINUTE;
185                // Get remaining nanoseconds
186                nanoseconds = nanoseconds % NANO_PER_MINUTE;
187                // Get seconds
188                long seconds = nanoseconds / NANO_PER_SECOND;
189                // Get remaining nanoseconds
190                nanoseconds = nanoseconds % NANO_PER_SECOND;
191
192                String elapsed = Strings.EMPTY;
193
194                if (hours > 0) {
195                    elapsed += hours + " hours ";
196                }
197                if (minutes > 0 || hours > 0) {
198                    elapsed += minutes + " minutes ";
199                }
200
201                DecimalFormat numFormat;
202                numFormat = new DecimalFormat("#0");
203                elapsed += numFormat.format(seconds) + '.';
204                numFormat = new DecimalFormat("000000000");
205                elapsed += numFormat.format(nanoseconds) + " seconds";
206                buffer.append(" stopped. Elapsed time: ").append(elapsed);
207                if (iterations > 0) {
208                    nanoseconds = elapsedTime / iterations;
209                    // Get elapsed hours
210                    hours = nanoseconds / NANO_PER_HOUR;
211                    // Get remaining nanoseconds
212                    nanoseconds = nanoseconds % NANO_PER_HOUR;
213                    // Get minutes
214                    minutes = nanoseconds / NANO_PER_MINUTE;
215                    // Get remaining nanoseconds
216                    nanoseconds = nanoseconds % NANO_PER_MINUTE;
217                    // Get seconds
218                    seconds = nanoseconds / NANO_PER_SECOND;
219                    // Get remaining nanoseconds
220                    nanoseconds = nanoseconds % NANO_PER_SECOND;
221
222                    elapsed = Strings.EMPTY;
223
224                    if (hours > 0) {
225                        elapsed += hours + " hours ";
226                    }
227                    if (minutes > 0 || hours > 0) {
228                        elapsed += minutes + " minutes ";
229                    }
230
231                    numFormat = new DecimalFormat("#0");
232                    elapsed += numFormat.format(seconds) + '.';
233                    numFormat = new DecimalFormat("000000000");
234                    elapsed += numFormat.format(nanoseconds) + " seconds";
235                    buffer.append(" Average per iteration: ").append(elapsed);
236                }
237                break;
238            default:
239                buffer.append(' ').append(status);
240                break;
241        }
242    }
243
244    @Override
245    public boolean equals(final Object o) {
246        if (this == o) {
247            return true;
248        }
249        if (!(o instanceof Timer)) {
250            return false;
251        }
252
253        final Timer timer = (Timer) o;
254
255        if (elapsedTime != timer.elapsedTime) {
256            return false;
257        }
258        if (startTime != timer.startTime) {
259            return false;
260        }
261        if (name != null ? !name.equals(timer.name) : timer.name != null) {
262            return false;
263        }
264        if (status != null ? !status.equals(timer.status) : timer.status != null) {
265            return false;
266        }
267
268        return true;
269    }
270
271    @Override
272    public int hashCode() {
273        int result;
274        result = (name != null ? name.hashCode() : 0);
275        result = 29 * result + (status != null ? status.hashCode() : 0);
276        long time = startTime.get();
277        result = 29 * result + (int) (time ^ (time >>> 32));
278        result = 29 * result + (int) (elapsedTime ^ (elapsedTime >>> 32));
279        return result;
280    }
281
282}