velox/experimental/codegen/utils/timer/NestedScopedTimer.h (224 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed 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.
*/
#pragma once
#include <fmt/format.h>
#include <algorithm>
#include <chrono>
#include <map>
#include <queue>
#include <stack>
#include <thread>
#include <vector>
#include "boost/accumulators/accumulators.hpp"
#include "boost/accumulators/statistics.hpp"
namespace facebook::velox::codegen {
/// Represent a sequence of Start/End events from collected form Scoped Timers
/// We split the recorded event as Start/End to be able to recover nesting
/// structure offline \tparam Cl Time source Clock type, follow TrivialClock
/// concept \tparam EI EventInfo stored for each events
template <typename Cl, typename EI>
struct EventSequence {
using Clock = Cl;
using EventInfo = EI;
static constexpr size_t defaultSize = 100;
EventSequence() {
startEvents.reserve(defaultSize);
endEvents.reserve(defaultSize);
}
explicit EventSequence(size_t reservedSize) {
startEvents.reserve(reservedSize);
endEvents.reserve(reservedSize);
}
struct Event {
EventInfo info;
typename Cl::time_point timePoint;
Event(const EventInfo& i, const typename Cl::time_point& t)
: info(i), timePoint(t) {}
Event() = default;
};
void addStartEvent(const EventInfo& info) {
startEvents.emplace_back(info, Clock::now());
}
void addEndEvent(const EventInfo& info) {
endEvents.emplace_back(info, Clock::now());
}
void clear() {
startEvents.clear();
endEvents.clear();
}
std::vector<Event> startEvents;
std::vector<Event> endEvents;
size_t pos;
};
template <typename Clock>
using NamedEventSequence = EventSequence<Clock, const char*>;
using NamedSteadyClockEventSequence =
NamedEventSequence<std::chrono::steady_clock>;
/// Scoped Timer nested structure modeled as a tree
/// \tparam Clock Time source
template <typename Clock>
struct TimerTree {
enum class EventType {
START,
END,
};
using EventVector = std::vector<
std::pair<EventType, typename NamedEventSequence<Clock>::Event>>;
typename NamedEventSequence<Clock>::EventInfo
info; // Timer information (eg name)
std::map<
typename NamedEventSequence<Clock>::EventInfo,
std::shared_ptr<TimerTree<Clock>>>
children;
std::vector<typename Clock::time_point> timePoints; // Start/End time events
// Absolute time statistics (ns)
double min_, max_, mean_, sum_, variance_;
// relative times (ns)
double timeRelativeToParentTimer_;
double timeRelativeToTotalTime_;
/// compute current timer statistics
void computeNodeStatistics() {
boost::accumulators::accumulator_set<
double,
boost::accumulators::stats<
boost::accumulators::tag::sum,
boost::accumulators::tag::min,
boost::accumulators::tag::max,
boost::accumulators::tag::mean,
boost::accumulators::tag::lazy_variance>>
accumulatorSet;
for (size_t i = 0; i + 1 < timePoints.size(); i += 2) {
auto durations = timePoints[i + 1] - timePoints[i];
accumulatorSet(static_cast<double>(durations.count()));
};
min_ = boost::accumulators::min(accumulatorSet);
max_ = boost::accumulators::max(accumulatorSet);
mean_ = boost::accumulators::mean(accumulatorSet);
sum_ = boost::accumulators::sum(accumulatorSet);
variance_ = boost::accumulators::variance(accumulatorSet);
}
static void computeStatistics(TimerTree<Clock> root) {
std::vector<TimerTree<Clock>*> nodes;
nodes.push_back(&root);
root.computeNodeStatistics();
for (size_t currentNode = 0; currentNode < nodes.size(); ++currentNode) {
for (auto& [name, child] : nodes[currentNode]->children) {
child->computeNodeStatistics();
child->timeRelativeToParentTimer_ =
(child->sum_ / nodes[currentNode]->sum_) * 100.00;
child->timeRelativeToTotalTime_ = (child->sum_ / root.sum_) * 100.00;
nodes.push_back(child.get());
}
}
}
/// Given an eventSequence object, merge startEvents and endEvents into a
/// single sorted event streams and tag the new events as start/end
/// startEvents = [T1,T3],endEvents = [T2,T4] -->
/// [{START,T1},{END,T2},{START,T3},{END,T4}] \param eventSequence \return
/// merged tagged event stream
static EventVector mergeEventVectors(
const NamedEventSequence<Clock>& eventSequence) {
EventVector startEvents;
EventVector endEvents;
std::transform(
eventSequence.startEvents.begin(),
eventSequence.startEvents.end(),
std::back_inserter(startEvents),
[](const auto& event) {
return std::make_pair(EventType::START, event);
});
std::transform(
eventSequence.endEvents.begin(),
eventSequence.endEvents.end(),
std::back_inserter(endEvents),
[](const auto& event) {
return std::make_pair(EventType::END, event);
});
EventVector mergedEvents;
std::merge(
startEvents.begin(),
startEvents.end(),
endEvents.begin(),
endEvents.end(),
std::back_inserter(mergedEvents),
[](const auto& left, const auto& right) {
return left.second.timePoint < right.second.timePoint;
});
return mergedEvents;
}
/// Build a new tree from an event Sequence, this is done by recovering the
/// nested structure of the orignal scoped timers
/// \param eventSequence
/// \return
static std::shared_ptr<TimerTree> fromEventSequence(
const NamedEventSequence<Clock>& eventSequence) {
auto mergedEvents = mergeEventVectors(eventSequence);
// Create a root timer to enclosing all created timer,
auto root = std::make_shared<TimerTree>();
root->info = "<ROOT>";
root->timePoints.push_back(mergedEvents.begin()->second.timePoint);
root->timePoints.push_back(mergedEvents.rbegin()->second.timePoint);
// Add the root timer as the first timer in the stack
std::stack<std::shared_ptr<TimerTree>> activeTimers;
activeTimers.push(root);
for (const auto& event : mergedEvents) {
// New timer starts
if (event.first == EventType::START) {
// Adds the new timer to the parent timer
auto [newTimerIterator, inserted] = activeTimers.top()->children.insert(
{event.second.info, std::make_shared<TimerTree>()});
// Set new timer infos
auto newTimer = newTimerIterator->second;
newTimer->info = event.second.info;
newTimer->timePoints.push_back(event.second.timePoint);
// new timer is now the inner most timer
activeTimers.push(newTimer);
continue;
};
// Timer Ends
if (event.first == EventType::END) {
auto currentTimer = activeTimers.top();
if (event.second.info != currentTimer->info) {
throw std::runtime_error(fmt::format(
"Improper timer nesting, closing timer {}, while timer {} is open",
event.second.info,
currentTimer->info));
};
currentTimer->timePoints.push_back(event.second.timePoint);
activeTimers.pop();
};
}
if (activeTimers.top() != root) {
throw std::runtime_error(
fmt::format("Timer {} was never closed", activeTimers.top()->info));
};
return root;
}
void print(
std::ostream& out,
const std::string& currentIdent,
const std::string& ident) {
out << currentIdent
<< fmt::format(
"[{}] : count = {:d}, min = {:.0f}, max = {:.0f}, avg = {:.0f} , total (ns) = {:.0f} , % parent = {:.2f} , % total = {:.2f}\n",
this->info,
this->timePoints.size() / 2,
min_,
max_,
mean_,
sum_,
timeRelativeToParentTimer_,
timeRelativeToTotalTime_);
auto newIndent = currentIdent + ident;
for (const auto& [key, value] : this->children) {
value->print(out, newIndent, ident);
};
}
};
/// Scoped timer with a name
/// When displayed, timer with the same name a considered equivalement and merge
/// as one WARNING : name is not owned nor copied and expected to be live when
/// the timer is displayed \tparam Clock
template <typename Clock>
struct NamedNestedScopedTimer {
using EventSequence = NamedEventSequence<Clock>;
NamedNestedScopedTimer(const char* name, EventSequence& eventSequence)
: eventSequence_(eventSequence), name_(name) {
eventSequence_.addStartEvent(name);
}
~NamedNestedScopedTimer() {
eventSequence_.addEndEvent(name_);
}
static std::string printCounters(EventSequence& eventSequence) {
auto timerTree = computeTimerTreeStatistics(eventSequence);
return printTimerTreeStatistics(timerTree);
}
static std::shared_ptr<TimerTree<Clock>> computeTimerTreeStatistics(
EventSequence& eventSequence) {
auto timerTree = TimerTree<Clock>::fromEventSequence(eventSequence);
TimerTree<Clock>::computeStatistics(*timerTree);
return timerTree;
}
static std::string printTimerTreeStatistics(
std::shared_ptr<TimerTree<Clock>>& timerTree) {
std::stringstream stream;
timerTree->print(stream, "", "+");
return stream.str();
}
NamedEventSequence<Clock>& eventSequence_;
const char* name_;
};
using SteadyClockNamedScopedTimer =
NamedNestedScopedTimer<std::chrono::steady_clock>;
using DefaultScopedTimer = SteadyClockNamedScopedTimer;
using DefaultEventSequence = DefaultScopedTimer::EventSequence;
} // namespace facebook::velox::codegen