/* * Profiler.actor.cpp * * This source file is part of the FoundationDB open source project * * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors * * 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 "flow/flow.h" #include "flow/network.h" #ifdef __linux__ #include #include #include #include #include #include #include "flow/Platform.h" #include "flow/actorcompiler.h" // This must be the last include. extern volatile thread_local int profilingEnabled; static uint64_t sys_gettid() { return syscall(__NR_gettid); } struct SignalClosure { void (*func)(int, siginfo_t*, void*, void*); void* userdata; SignalClosure(void (*func)(int, siginfo_t*, void*, void*), void* userdata) : func(func), userdata(userdata) {} static void signal_handler(int s, siginfo_t* si, void* ucontext) { // async signal safe! // This is intended to work as a SIGPROF handler for past and future versions of the flow profiler (when // multiple are running in a process!) So don't change what it does without really good reason SignalClosure* closure = (SignalClosure*)(si->si_value.sival_ptr); closure->func(s, si, ucontext, closure->userdata); } }; struct SyncFileForSim : ReferenceCounted { FILE* f; SyncFileForSim(std::string const& filename) { f = fopen(filename.c_str(), "wb"); } bool isOpen() const { return f != nullptr; } int64_t debugFD() const { return (int64_t)f; } Future read(void* data, int length, int64_t offset) { ASSERT(false); throw internal_error(); } Future write(void const* data, int length, int64_t offset) { ASSERT(isOpen()); fseek(f, offset, SEEK_SET); if (fwrite(data, 1, length, f) != length) throw io_error(); return Void(); } Future truncate(int64_t size) { ASSERT(size == 0); return Void(); } Future flush() { ASSERT(isOpen()); fflush(f); return Void(); } Future sync() { ASSERT(false); throw internal_error(); } Future size() const { ASSERT(false); throw internal_error(); } }; struct Profiler { struct OutputBuffer { std::vector output; OutputBuffer() { output.reserve(100000); } void clear() { output.clear(); } void push(void* ptr) { // async signal safe! if (output.size() < output.capacity()) output.push_back(ptr); } Future writeTo(Reference file, int64_t& offset) { int64_t offs = offset; offset += sizeof(void*) * output.size(); return file->write(&output[0], sizeof(void*) * output.size(), offs); } }; enum { MAX_STACK_DEPTH = 256 }; void* addresses[MAX_STACK_DEPTH]; SignalClosure signalClosure; OutputBuffer* output_buffer; Future actor; sigset_t profilingSignals; static Profiler* active_profiler; BinaryWriter environmentInfoWriter; INetwork* network; timer_t periodicTimer; bool timerInitialized; Profiler(int period, std::string const& outfn, INetwork* network) : signalClosure(signal_handler_for_closure, this), environmentInfoWriter(Unversioned()), network(network), timerInitialized(false) { actor = profile(this, period, outfn); } ~Profiler() { enableSignal(false); if (timerInitialized) { timer_delete(periodicTimer); } } void signal_handler() { // async signal safe! static std::atomic inSigHandler = false; if (inSigHandler.exchange(true)) { return; } if (profilingEnabled) { double t = timer(); output_buffer->push(*(void**)&t); size_t n = platform::raw_backtrace(addresses, 256); for (int i = 0; i < n; i++) output_buffer->push(addresses[i]); output_buffer->push((void*)-1LL); } inSigHandler.store(false); } static void signal_handler_for_closure(int, siginfo_t* si, void*, void* self) { // async signal safe! ((Profiler*)self)->signal_handler(); } void enableSignal(bool enabled) { sigprocmask(enabled ? SIG_UNBLOCK : SIG_BLOCK, &profilingSignals, nullptr); } void phdr(struct dl_phdr_info* info) { environmentInfoWriter << int64_t(1) << info->dlpi_addr << StringRef((const uint8_t*)info->dlpi_name, strlen(info->dlpi_name)); for (int s = 0; s < info->dlpi_phnum; s++) { auto const& h = info->dlpi_phdr[s]; environmentInfoWriter << int64_t(2) << h.p_type << h.p_flags // Word (uint32_t) << h.p_offset // Off (uint64_t) << h.p_vaddr << h.p_paddr // Addr (uint64_t) << h.p_filesz << h.p_memsz << h.p_align; // XWord (uint64_t) } } static int phdr_callback(struct dl_phdr_info* info, size_t size, void* data) { ((Profiler*)data)->phdr(info); return 0; } ACTOR static Future profile(Profiler* self, int period, std::string outfn) { // Open and truncate output file state Reference outFile = makeReference(outfn); if (!outFile->isOpen()) { TraceEvent(SevWarn, "FailedToOpenProfilingOutputFile").detail("Filename", outfn).GetLastError(); return Void(); } // According to folk wisdom, calling this once before setting up the signal handler makes // it async signal safe in practice :-/ platform::raw_backtrace(self->addresses, MAX_STACK_DEPTH); // Write environment information header // At the moment this consists of the output of dl_iterate_phdr, the locations of // all shared objects loaded into this process (to help locate symbols) and the period in ns self->environmentInfoWriter << int64_t(0x101) << int64_t(period * 1000); dl_iterate_phdr(phdr_callback, self); self->environmentInfoWriter << int64_t(0); while (self->environmentInfoWriter.getLength() % sizeof(void*)) self->environmentInfoWriter << uint8_t(0); self->output_buffer = new OutputBuffer; state OutputBuffer* otherBuffer = new OutputBuffer; // The profilingSignals signal set will be used by enableSignal sigemptyset(&self->profilingSignals); sigaddset(&self->profilingSignals, SIGPROF); // Set up profiling signal handler struct sigaction act; act.sa_sigaction = SignalClosure::signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO; sigaction(SIGPROF, &act, nullptr); // Set up periodic profiling timer int period_ns = period * 1000; itimerspec tv; tv.it_interval.tv_sec = 0; tv.it_interval.tv_nsec = period_ns; tv.it_value.tv_sec = 0; tv.it_value.tv_nsec = nondeterministicRandom()->randomInt(period_ns / 2, period_ns + 1); sigevent sev; sev.sigev_notify = SIGEV_THREAD_ID; sev.sigev_signo = SIGPROF; sev.sigev_value.sival_ptr = &(self->signalClosure); sev._sigev_un._tid = sys_gettid(); if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &self->periodicTimer) != 0) { TraceEvent(SevWarn, "FailedToCreateProfilingTimer").GetLastError(); return Void(); } self->timerInitialized = true; if (timer_settime(self->periodicTimer, 0, &tv, nullptr) != 0) { TraceEvent(SevWarn, "FailedToSetProfilingTimer").GetLastError(); return Void(); } state int64_t outOffset = 0; wait(outFile->truncate(outOffset)); wait(outFile->write(self->environmentInfoWriter.getData(), self->environmentInfoWriter.getLength(), outOffset)); outOffset += self->environmentInfoWriter.getLength(); loop { wait(self->network->delay(1.0, TaskPriority::Min) || self->network->delay(2.0, TaskPriority::Max)); self->enableSignal(false); std::swap(self->output_buffer, otherBuffer); self->enableSignal(true); wait(otherBuffer->writeTo(outFile, outOffset)); wait(outFile->flush()); otherBuffer->clear(); } } }; // Outlives main Profiler* Profiler::active_profiler = nullptr; std::string findAndReplace(std::string const& fn, std::string const& symbol, std::string const& value) { auto i = fn.find(symbol); if (i == std::string::npos) return fn; return fn.substr(0, i) + value + fn.substr(i + symbol.size()); } void startProfiling(INetwork* network, Optional maybePeriod /*= {}*/, Optional maybeOutputFile /*= {}*/) { int period; if (maybePeriod.present()) { period = maybePeriod.get(); } else { const char* periodEnv = getenv("FLOW_PROFILER_PERIOD"); period = (periodEnv ? atoi(periodEnv) : 2000); } std::string outputFile; if (maybeOutputFile.present()) { outputFile = std::string((const char*)maybeOutputFile.get().begin(), maybeOutputFile.get().size()); } else { const char* outfn = getenv("FLOW_PROFILER_OUTPUT"); outputFile = (outfn ? outfn : "profile.bin"); } outputFile = findAndReplace( findAndReplace( findAndReplace(outputFile, "%ADDRESS%", findAndReplace(network->getLocalAddress().toString(), ":", ".")), "%PID%", format("%d", getpid())), "%TID%", format("%llx", (long long)sys_gettid())); if (!Profiler::active_profiler) Profiler::active_profiler = new Profiler(period, outputFile, network); } void stopProfiling() { if (Profiler::active_profiler) { Profiler* p = Profiler::active_profiler; Profiler::active_profiler = nullptr; delete p; } } #else void startProfiling(INetwork* network, Optional period, Optional outputFile) {} void stopProfiling() {} #endif