cpp/profiler/TimerManager.cpp (143 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 "TimerManager.h"
#include "SamplingProfiler.h"
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <fb/log.h>
#include <random>
#include <stdexcept>
#include <profilo/util/ProcFsUtils.h>
#include <profilo/util/common.h>
namespace facebook {
namespace profilo {
namespace profiler {
namespace {
// Keep in sync with ProfiloConstants.java:
constexpr int TRACE_CONFIG_PARAM_LOGGER_PRIORITY_DEFAULT = 5;
constexpr auto kNanosecondsInSecond = 1000 * 1000 * 1000;
constexpr auto kNanosecondsInMillisecond = 1000 * 1000;
struct timespec getAbsTimeInFutureMs(int futureMs) {
struct timespec abs_time;
if (clock_gettime(CLOCK_REALTIME, &abs_time) == -1) {
FBLOGE("clock_gettime %s", strerror(errno));
abort(); // Something went wrong
}
long timeout_nsec = abs_time.tv_nsec + futureMs * kNanosecondsInMillisecond;
abs_time.tv_nsec = timeout_nsec % kNanosecondsInSecond;
abs_time.tv_sec += timeout_nsec / kNanosecondsInSecond;
return abs_time;
}
} // namespace
void TimerManager::updateThreadTimers() {
// Modifies state_.threadTimers
// Must not be concurrent with stopThreadTimers()
util::ThreadList threads;
try {
threads = util::threadListFromProcFs();
} catch (const std::system_error& e) {
// threadListFromProcFs can throw an error. Ignore it.
return;
}
if (state_.whitelist != nullptr) {
// only process whitelisted threads
auto liveWhitelistedThreads = util::ThreadList();
std::unique_lock<std::mutex> lock(state_.whitelist->whitelistedThreadsMtx);
for (int32_t tid : state_.whitelist->whitelistedThreads) {
if (threads.find(tid) != threads.end()) {
liveWhitelistedThreads.emplace(tid);
}
}
threads = std::move(liveWhitelistedThreads);
}
// Delete timers for threads that have died
for (auto iter = state_.threadTimers.begin();
iter != state_.threadTimers.end();) {
if (threads.find(iter->first) == threads.end()) {
iter = state_.threadTimers.erase(iter); // RAII deletes timer
} else {
++iter;
}
}
// Start timers for threads that are new
for (auto& tid : threads) {
if (state_.threadTimers.find(tid) != state_.threadTimers.end()) {
continue;
}
try {
std::vector<ThreadTimer> timers;
if (state_.cpuClockModeEnabled) {
timers.emplace_back(ThreadTimer(
tid, state_.samplingRateMs, ThreadTimer::Type::CpuTime));
}
if (state_.wallClockModeEnabled) {
timers.emplace_back(ThreadTimer(
tid, state_.samplingRateMs, ThreadTimer::Type::WallTime));
}
if (timers.size() > 0) {
bool ok = state_.threadTimers.emplace(tid, std::move(timers)).second;
if (!ok) {
FBLOGE("state_.threadTimers.insert failed");
}
}
} catch (const std::system_error& e) {
// thread may have ended
FBLOGV("ThreadTimer could not be created for tid %d", tid);
}
}
}
// must be started after sampling is enabled
void TimerManager::threadDetectLoop() {
{
int err = pthread_setname_np(pthread_self(), "Prflo:ThrdDetct");
if (err) {
FBLOGE("threadDetectLoop: pthread_setname_np: %s", strerror(err));
}
// https://stackoverflow.com/questions/17398075/change-native-thread-priority-on-android-in-c-c
if (setpriority(
PRIO_PROCESS, 0, TRACE_CONFIG_PARAM_LOGGER_PRIORITY_DEFAULT)) {
FBLOGE("threadDetectLoop: setpriority: %s", strerror(errno));
}
}
FBLOGV("ThreadDetectLoop thread %d is going into the loop...", threadID());
int res;
bool done;
struct timespec nextThreadDetectWakeup = getAbsTimeInFutureMs(0);
do {
res = sem_timedwait(&state_.threadDetectSem, &nextThreadDetectWakeup);
done = state_.isThreadDetectLoopDone.load();
if (!done && res == -1 && errno == ETIMEDOUT) {
// timed out
nextThreadDetectWakeup =
getAbsTimeInFutureMs(state_.threadDetectIntervalMs);
updateThreadTimers();
res = 0;
}
} while (!done && (res == 0 || errno == EINTR));
if (res != 0) {
FBLOGV("ThreadDetectLoop exiting with ERROR: %s", strerror(errno));
}
state_.threadTimers.clear();
FBLOGV("ThreadDetectLoop thread is shutting down...");
} // namespace profiler
// --- Public API ---
TimerManager::TimerManager(
int threadDetectIntervalMs,
int samplingRateMs,
bool cpuClockModeEnabled,
bool wallClockModeEnabled,
std::shared_ptr<Whitelist> whitelist) {
state_.threadDetectIntervalMs = threadDetectIntervalMs;
state_.samplingRateMs = samplingRateMs;
state_.cpuClockModeEnabled = cpuClockModeEnabled;
state_.wallClockModeEnabled = wallClockModeEnabled;
state_.whitelist = whitelist;
state_.isThreadDetectLoopDone.store(false);
if (sem_init(&state_.threadDetectSem, 0, 0)) {
throw std::system_error(
errno, std::system_category(), "TimerManager sem_init failed");
}
}
void TimerManager::start() {
// Create worker to detects new threads & starts profiling on them
state_.threadDetectThread =
std::thread(&TimerManager::threadDetectLoop, this);
}
void TimerManager::stop() {
state_.isThreadDetectLoopDone.store(true);
sem_post(&state_.threadDetectSem); // wake up
state_.threadDetectThread.join();
}
} // namespace profiler
} // namespace profilo
} // namespace facebook