cpp/profiler/ThreadTimer.cpp (113 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 "ThreadTimer.h" #include <errno.h> #include <signal.h> #include <fb/log.h> #include <profilo/util/common.h> #include <random> #include <stdexcept> #include <system_error> namespace facebook { namespace profilo { namespace profiler { namespace { constexpr auto kNanosecondsInMicrosecond = 1000; constexpr auto kMicrosecondsInSecond = 1000 * 1000; constexpr auto kMicrosecondsInMillisecond = 1000; // --- thread-specific timer wrappers // getCpuClockIdFromTid - obtain the thread-specific clock_id for a kernel tid. // // Notes: // Reasons we can't rely on bionic variants of: // - clock_getcpuclockid(): // On 5.x and older: it is not available. // On 6.x and newer: it fails with ESRCH. Under the hood, this happens // because clock_getcpuclockid() only succeeds if the clock_id THREAD bit is // set (non main threads). // - pthread_getcpuclockid(): // We don't have pthread_ids when discovering existing threads via /proc. // On OS 4.1-4.3, due to a bug in Bionic, value generated by // pthread_getcpuclockid() is wrong // // So we have to roll our own implementation based on pthread_getcpuclockid and // clock_getcpuclockid. clockid_t getCpuClockIdFromTid(pid_t tid) { clockid_t result; result = ~static_cast<clockid_t>(tid) << 3; // Bits 0 and 1: (0 = CPUCLOCK_PROF, 1 = CPUCLOCK_VIRT, 2 = CPUCLOCK_SCHED) // result |= 0; // CPUCLOCK_PROF also seems to work // result |= 1; // CPUCLOCK_VIRT also seems to work result |= 2; // CPUCLOCK_SCHED, per pthread_getcpuclockid // Bit 2: (1 = THREAD, 0 = PROCESS) // result |= (0 << 2); // clock_getcpuclockid() sets this to 0, but it fails result |= (1 << 2); // pthread_getcpuclockid() sets this to 1 return result; } bool createThreadTimer( pid_t ktid, timer_t* timerId, bool wallClockModeEnabled) { clockid_t clockid = wallClockModeEnabled ? CLOCK_MONOTONIC : getCpuClockIdFromTid(ktid); struct sigevent sigev; sigev.sigev_notify = SIGEV_THREAD_ID; sigev.sigev_signo = SIGPROF; sigev._sigev_un._tid = ktid; /* ID of kernel thread to signal */ sigev.sigev_value.sival_int = ThreadTimer::encodeType( wallClockModeEnabled ? ThreadTimer::Type::WallTime : ThreadTimer::Type::CpuTime); if (timer_create(clockid, &sigev, timerId) != 0) { return false; } return true; } bool startThreadTimer(timer_t timerId, int samplingRateMs) { itimerval tv = getInitialItimerval(samplingRateMs); struct itimerspec itimer; itimer.it_interval.tv_sec = tv.it_interval.tv_sec; itimer.it_interval.tv_nsec = tv.it_interval.tv_usec * kNanosecondsInMicrosecond; itimer.it_value.tv_sec = tv.it_value.tv_sec; itimer.it_value.tv_nsec = tv.it_value.tv_usec * kNanosecondsInMicrosecond; if (timer_settime(timerId, 0, &itimer, NULL) != 0) { return false; } return true; } void deleteThreadTimer(timer_t timerId) { if (timer_delete(timerId) == -1) { FBLOGV("INFO: Cannot delete profiling timer: %s", strerror(errno)); } } } // namespace // get timer repeat interval and initial offset itimerval getInitialItimerval(int samplingRateMs) { auto sampleRateMicros = samplingRateMs * kMicrosecondsInMillisecond; // Generate random initial delay. Used to calculate the initial trace delay // to avoid sampling bias. // Narrowing cast is acceptable, the lower bits should have // all the entropy anyway. std::mt19937 randGenerator(static_cast<uint32_t>(monotonicTime())); std::uniform_int_distribution<> randDistribution(1, sampleRateMicros); auto sampleStartDelayMicros = randDistribution(randGenerator); int32_t sampleStartDelaySeconds = 0; int32_t sampleRateSeconds = 0; if (sampleStartDelayMicros >= kMicrosecondsInSecond) { sampleStartDelaySeconds = sampleStartDelayMicros / kMicrosecondsInSecond; sampleStartDelayMicros = sampleStartDelayMicros % kMicrosecondsInSecond; } if (sampleRateMicros >= kMicrosecondsInSecond) { sampleRateSeconds = sampleRateMicros / kMicrosecondsInSecond; sampleRateMicros = sampleRateMicros % kMicrosecondsInSecond; } itimerval tv{}; tv.it_value.tv_sec = sampleStartDelaySeconds; tv.it_value.tv_usec = sampleStartDelayMicros; tv.it_interval.tv_sec = sampleRateSeconds; tv.it_interval.tv_usec = sampleRateMicros; return tv; } ThreadTimer::ThreadTimer(int32_t tid, int samplingRateMs, Type timerType) : tid_(tid), samplingRateMs_(samplingRateMs), timerType_(timerType) { if (!createThreadTimer(tid_, &timerId_, timerType == Type::WallTime)) { // e.g. tid died throw std::system_error(errno, std::system_category(), "createThreadTimer"); } if (!startThreadTimer(timerId_, samplingRateMs_)) { // e.g. tid died throw std::system_error(errno, std::system_category(), "startThreadTimer"); } } ThreadTimer::~ThreadTimer() { if (timerId_ == INVALID_TIMER_ID) { // Expected when creating new ThreadTimer objects return; } deleteThreadTimer(timerId_); } long ThreadTimer::typeSeed = 0; ThreadTimer::Type ThreadTimer::decodeType(long salted) { auto type = static_cast<Type>(salted ^ typeSeed); if (type != Type::CpuTime && type != Type::WallTime) { throw std::runtime_error("invalid timer type"); } return type; } long ThreadTimer::encodeType(Type type) { while (typeSeed == 0) { typeSeed = rand(); } return static_cast<long>(type) ^ typeSeed; } } // namespace profiler } // namespace profilo } // namespace facebook