blob: d9c5d993c0710f896225ba0d99a62689a0f67199 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#ifndef IMPALA_UTIL_STOPWATCH_H
#define IMPALA_UTIL_STOPWATCH_H
#include <boost/cstdint.hpp>
#include <boost/thread/lock_guard.hpp>
#include <util/os-info.h>
#include <util/spinlock.h>
#include <util/time.h>
#include <time.h>
namespace impala {
#define SCOPED_STOP_WATCH(c) \
ScopedStopWatch<MonotonicStopWatch> \
MACRO_CONCAT(STOP_WATCH, __COUNTER__)(c)
#define CONDITIONAL_SCOPED_STOP_WATCH(c, enabled) \
ScopedStopWatch<MonotonicStopWatch> \
MACRO_CONCAT(STOP_WATCH, __COUNTER__)(c, enabled)
#define SCOPED_CONCURRENT_STOP_WATCH(c) \
ScopedStopWatch<ConcurrentStopWatch> \
MACRO_CONCAT(CONCURRENT_STOP_WATCH, __COUNTER__)(c)
/// Utility class to measure time. This is measured using the cpu tick counter which
/// is very low overhead but can be inaccurate if the thread is switched away. This
/// is useful for measuring cpu time at the row batch level (too much overhead at the
/// row granularity).
class StopWatch {
public:
StopWatch() {
total_time_ = 0;
running_ = false;
}
void Start() {
if (!running_) {
start_ = Rdtsc();
running_ = true;
}
}
void Stop() {
total_time_ += RunningTime();
running_ = false;
}
/// Returns total time in cpu ticks for which the stopwatch was running, including
/// the time since Start() was called, if it is currently running.
uint64_t ElapsedTime() const {
return total_time_ + RunningTime();
}
static uint64_t Rdtsc() {
uint32_t lo, hi;
__asm__ __volatile__ (
"xorl %%eax,%%eax \n cpuid"
::: "%rax", "%rbx", "%rcx", "%rdx");
__asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
return (uint64_t)hi << 32 | lo;
}
private:
/// Returns total time in cpu ticks since Start() was called. If not running, returns 0.
uint64_t RunningTime() const {
return running_ ? Rdtsc() - start_ : 0;
}
uint64_t start_, total_time_;
bool running_;
};
/// Stop watch for reporting elapsed time in nanosec based on clock_gettime (Linux) or
/// MonotonicNanos (Apple). It is not affected by cpu frequency changes and it is not
/// affected by user setting the system clock. A monotonic clock represents monotonic
/// time since some unspecified starting point. It is good for computing elapsed time.
///
/// The time values are in nanoseconds. For most machine configurations, the clock
/// resolution will be 1 nanosecond. We fall back to low resolution in configurations
/// where the clock is expensive. For those machine configurations (notably EC2), the
/// clock resolution will be that of the system jiffy, which is between 1 and 10
/// milliseconds.
class MonotonicStopWatch {
public:
MonotonicStopWatch() : start_(0), total_time_(0),
time_ceiling_(0), running_(false) {
}
void Start() {
if (!running_) {
start_ = Now();
running_ = true;
}
}
void Stop() {
total_time_ += RunningTime();
running_ = false;
}
/// Set the time ceiling of the stop watch to Now(). The stop watch won't run past the
/// ceiling.
void SetTimeCeiling() { time_ceiling_ = Now(); }
/// Restarts the timer. Returns the elapsed time until this point.
uint64_t Reset() {
uint64_t ret = RunningTime();
if (running_) {
start_ = Now();
time_ceiling_ = 0;
}
return ret;
}
/// Returns total time in nanoseconds for which the stopwatch was running, including
/// the time since Start() was called, if it is currently running.
uint64_t ElapsedTime() const {
return total_time_ + RunningTime();
}
/// Returns an representation of the current time in nanoseconds. It can be used to
/// measure time durations by repeatedly calling this function and comparing the result.
/// While this function returns nanoseconds, its resolution may be as large as
/// milliseconds, depending on OsInfo::fast_clock().
static inline int64_t Now() {
#if defined(__APPLE__)
// Apple does not support clock_gettime.
return MonotonicNanos();
#else
// Use a fast but low-resolution clock if the high-resolution clock is slow because
// Now() can be called frequently (IMPALA-2407).
timespec ts;
clock_gettime(OsInfo::fast_clock(), &ts);
return ts.tv_sec * NANOS_PER_SEC + ts.tv_nsec;
#endif
}
private:
/// Start epoch value.
uint64_t start_;
/// Total elapsed time in nanoseconds.
uint64_t total_time_;
/// Upper bound of the running time as a epoch value. If the value is larger than 0,
/// the stopwatch interprets this as a time ceiling is set.
uint64_t time_ceiling_;
/// True if stopwatch is running.
bool running_;
/// Returns the time since Start() was called, if running. Otherwise return 0.
/// If time_ceiling_ is set, the stop watch won't run pass the ceiling.
uint64_t RunningTime() const {
if (!running_) return 0;
uint64_t end = Now();
if (time_ceiling_ > 0) {
if (time_ceiling_ < start_) return 0;
if (time_ceiling_ < end) end = time_ceiling_;
}
// IMPALA-7963: On some buggy kernels, time can go backwards slightly. Log a warning
// and return 0 in this case.
if (end < start_) {
LOG(INFO) << "WARNING: time went backwards from " << start_ << " to " << end;
return 0;
}
return end - start_;
}
};
/// Utility class to measure multiple threads concurrent wall time.
/// If a thread is already running, the following thread won't reset the stop watch.
/// The stop watch is stopped only when all threads finish their work.
class ConcurrentStopWatch {
public:
ConcurrentStopWatch() : busy_threads_(0), last_lap_start_(0) {}
void Start() {
boost::lock_guard<SpinLock> l(thread_counter_lock_);
if (busy_threads_ == 0) {
msw_.Start();
}
++busy_threads_;
}
void Stop() {
boost::lock_guard<SpinLock> l(thread_counter_lock_);
DCHECK_GT(busy_threads_, 0);
--busy_threads_;
if (busy_threads_ == 0) {
msw_.Stop();
}
}
/// Returns delta wall time since last time LapTime() is called.
uint64_t LapTime() {
boost::lock_guard<SpinLock> l(thread_counter_lock_);
uint64_t now = msw_.ElapsedTime();
uint64_t lap_duration = now - last_lap_start_;
last_lap_start_ = now;
return lap_duration;
}
uint64_t TotalRunningTime() const { return msw_.ElapsedTime(); }
private:
MonotonicStopWatch msw_;
/// Lock with busy_threads_.
SpinLock thread_counter_lock_;
/// Track how many threads are currently busy.
int busy_threads_;
/// Track when the last time LapTime() is called so we can calculate lap time.
uint64_t last_lap_start_;
};
/// Utility class that starts the stop watch in the constructor and stops the watch when
/// the object goes out of scope. If the optional argument 'enabled' is false, the
/// stopwatch is not updated.
/// 'T' must implement the StopWatch interface "Start", "Stop".
template<class T>
class ScopedStopWatch {
public:
ScopedStopWatch(T* sw, bool enabled = true) :
sw_(sw), enabled_(enabled) {
DCHECK(sw != NULL);
if (enabled_) sw_->Start();
}
~ScopedStopWatch() {
if (enabled_) sw_->Stop();
}
private:
T* sw_;
bool enabled_;
};
}
#endif