cpp/perfevents/detail/ClockOffsetMeasurement.cpp (131 lines of code) (raw):
/**
* Copyright 2004-present, Facebook, Inc.
*
* 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.
*/
#include <profilo/perfevents/Session.h>
#include <profilo/perfevents/detail/ClockOffsetMeasurement.h>
#include <stdint.h>
#include <atomic>
#include <condition_variable>
#include <mutex>
#include <thread>
namespace facebook {
namespace perfevents {
namespace detail {
namespace clock {
namespace {
struct ClockOffsetListener : public RecordListener {
ClockOffsetListener(std::atomic<int64_t>& faultTime)
: fault_time_(faultTime) {
fault_time_ = -1;
}
virtual void onMmap(const RecordMmap& record) {}
virtual void onSample(const EventType eventType, const RecordSample& record) {
if (eventType == EVENT_TYPE_MINOR_FAULTS) {
fault_time_ = record.time();
}
}
virtual void onForkEnter(const RecordForkExit& record) {}
virtual void onForkExit(const RecordForkExit& record) {}
virtual void onLost(const RecordLost& record) {}
virtual void onReaderStop() {}
std::atomic<int64_t>& fault_time_;
};
} // namespace
int64_t measureOffsetFromPerfClock(clockid_t clockid) {
// The idea here is to
// 1) start a session looking for minor faults from a target thread *only*
// 2) capture the clockid_t timestamp before
// 3) incur a minor fault via mmap(3)
// 4) capture the clockid_t timestamp after
// 5) average the clockid_t timestamps and compute an offset from the value
// that the session sees.
//
// This requires coordinating multiple threads:
// a) the caller thread is orchestrating the whole thing
// b) a measurement thread will incur the actual minor fault
// c) a session thread will actually read the perf events for the measurement
// thread
std::atomic<int32_t> threadID{};
std::atomic<int64_t> faultClockTime{};
std::atomic<int64_t> faultKernelTime{};
// Coordinating condition variables, flags, and mutex.
std::mutex cond_mutex;
bool measurement_thread_has_started = false, session_has_started = false;
std::condition_variable measurement_thread_has_started_cond;
std::condition_variable session_has_started_cond;
std::thread measurementThread([&] {
threadID = syscall(__NR_gettid);
faultClockTime = -1;
// We've populated the thread ID, notify the main thread.
{
std::lock_guard<std::mutex> lg(cond_mutex);
measurement_thread_has_started = true;
}
measurement_thread_has_started_cond.notify_all();
// Wait until the perf events session has been started
{
std::unique_lock<std::mutex> lock(cond_mutex);
session_has_started_cond.wait(lock, [&] { return session_has_started; });
}
// We need new address space in order to incur an actual fault. malloc() may
// reuse memory.
void* area = mmap(
nullptr,
PAGE_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
/*fd*/ -1,
/*offset*/ 0);
if (area == MAP_FAILED) {
return;
}
struct timespec before {
}, after{};
if (clock_gettime(clockid, &before)) {
return;
}
// incur actual fault
*reinterpret_cast<uint32_t*>(area) = 0xfaceb00c;
if (clock_gettime(clockid, &after)) {
return;
}
if (munmap(area, PAGE_SIZE)) {
return;
}
int64_t beforeTs =
static_cast<int64_t>(before.tv_sec) * 1000000000 + before.tv_nsec;
int64_t afterTs =
static_cast<int64_t>(after.tv_sec) * 1000000000 + after.tv_nsec;
faultClockTime = beforeTs + (afterTs - beforeTs) / 2;
});
// Wait for the measurement thread to start so we can read the thread id.
{
std::unique_lock<std::mutex> lock(cond_mutex);
measurement_thread_has_started_cond.wait(
lock, [&] { return measurement_thread_has_started; });
}
std::vector<EventSpec> eventSpecs(1);
eventSpecs[0] = EventSpec{
.type = EVENT_TYPE_MINOR_FAULTS,
.tid = threadID.load(),
};
SessionSpec sessionSpec{
.fallbacks = 0,
.maxAttachIterations = 1,
.maxAttachedFdsRatio = 1.0f,
};
Session session(
eventSpecs,
sessionSpec,
std::make_unique<ClockOffsetListener>(faultKernelTime));
if (!session.attach()) {
//
// Let the measurement thread run and wait for it to finish before
// exiting with an error.
//
{
std::lock_guard<std::mutex> lg(cond_mutex);
session_has_started = true;
}
session_has_started_cond.notify_all();
measurementThread.join();
return INT64_MIN;
}
std::thread sessionThread([&] {
{
std::lock_guard<std::mutex> lg(cond_mutex);
session_has_started = true;
}
session_has_started_cond.notify_all();
session.run();
});
{
std::unique_lock<std::mutex> lock(cond_mutex);
session_has_started_cond.wait(lock, [&] { return session_has_started; });
}
measurementThread.join();
session.stop();
session.detach();
sessionThread.join();
if (faultClockTime.load() == -1 || faultKernelTime.load() == -1) {
return INT64_MIN;
}
return faultClockTime.load() - faultKernelTime.load();
}
} // namespace clock
} // namespace detail
} // namespace perfevents
} // namespace facebook