1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2018 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18#pragma once
19
20#include <platform/processclock.h>
21
22/**
23 * Timer wrappers which provides a RAII-style mechanism for timing the duration
24 * of sections of code; where multiple listeners can record the same duration.
25 *
26 * The motiviation for this class is we have regions of code which multiple
27 * listeners want to time. We don't want to just have each listener perform
28 * it's reading of the clock as that is (a) costly and (b) gives slightly
29 * different time values to each listener. Instead, this class handles reading
30 * the time (just once at start and stop); and then passes it onto each
31 * listener.
32 *
33 * On construction, it reads the current time of the ProcessClock; and calls the
34 * start() method in each passed in listener object.
35 * On destruction (i.e. when this object goes out of scope), reads
36 * ProcessClock::now a second time, and calls end() on each listener object.
37 *
38 * Example usage:
39 *
40 * {
41 *     ... some scope we wish to time
42 *     ScopedTimer2<MicrosecondsStopwatch, MicrosecondsStopwatch> timer
43 *             (MicrosecondStopwatch(stats.histogram1),
44 *             (MicrosecondStopwatch(stats.histogram2));
45 *     // start() called on MicrosecondStopwatch
46 *     ...
47 *  } // at end of scope stop() called on MicrosecondStopwatch.
48 *
49 * Note: The listener objects are passed by rvalue-reference; this is helpful
50 *       as it means we can move them into the ScopeTimer object (and hence
51 *       keep them around to call stop() on). However this doesn't implement
52 *       perfect forwarding, so you should ensure the Listener object can be
53 *       copied/moved safely.
54 *
55 * Note(2): This initial implementation has a simple, dumb implementation of
56 *       different classes for each count of listeners. It could well be
57 *       possible to do something clever with variadic templates to genericize
58 *       this, but given we only ever use either 1 or 2 listeners that seems
59 *       overkill at the present time :)
60 */
61template <class Listener1>
62class ScopeTimer1 {
63public:
64    ScopeTimer1(Listener1&& listener1)
65        : startTime(ProcessClock::now()),
66          listener1(std::forward<Listener1>(listener1)) {
67        const auto startTime = ProcessClock::now();
68        listener1.start(startTime);
69    }
70
71    ~ScopeTimer1() {
72        const auto endTime = ProcessClock::now();
73        listener1.stop(endTime);
74    }
75
76private:
77    const ProcessClock::time_point startTime;
78    Listener1 listener1;
79};
80
81/// ScopeTimer taking two listener objects.
82template <class Listener1, class Listener2>
83class ScopeTimer2 {
84public:
85    ScopeTimer2(Listener1&& listener1, Listener2&& listener2)
86        : startTime(ProcessClock::now()),
87          listener1(std::forward<Listener1>(listener1)),
88          listener2(std::forward<Listener2>(listener2)) {
89        listener1.start(startTime);
90        listener2.start(startTime);
91    }
92
93    ~ScopeTimer2() {
94        const auto endTime = ProcessClock::now();
95        listener1.stop(endTime);
96        listener2.stop(endTime);
97    }
98
99private:
100    const ProcessClock::time_point startTime;
101    Listener1 listener1;
102    Listener2 listener2;
103};
104