/* * Copyright (C) 2017 The Android Open Source Project * * 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 "inplace_sampler_lib.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOG_TAG "InplaceSampler" #include #include "environment.h" #include "UnixSocket.h" #include "utils.h" #define DEFAULT_SIGNO SIGRTMAX static constexpr int DEFAULT_SAMPLE_FREQ = 4000; static constexpr int CHECK_THREADS_INTERVAL_IN_MS = 200; namespace { struct ThreadInfo { std::string name; }; // SampleManager controls the whole sampling process: // Read commands from simpleperf // Set up timers to send signals for each profiled thread regularly. // Send thread info and map info to simpleperf. class SampleManager { public: SampleManager(std::unique_ptr conn) : conn_(std::move(conn)), tid_(gettid()), signo_(DEFAULT_SIGNO), sample_freq_(DEFAULT_SAMPLE_FREQ), sample_period_in_ns_(0), dump_callchain_(false), monitor_all_threads_(true) { } void Run(); private: bool HandleMessage(const UnixSocketMessage& msg); bool ParseStartProfilingMessage(const UnixSocketMessage& msg); bool SendStartProfilingReplyMessage(bool ok); bool StartProfiling(); bool InstallSignalHandler(); bool CheckThreads(); bool CheckThreadNameChange(uint64_t timestamp); bool CheckMapChange(uint64_t timestamp); void SendThreadMapInfo(); void SendFakeSampleRecord(); std::unique_ptr conn_; int tid_; int signo_; uint32_t sample_freq_; uint32_t sample_period_in_ns_; bool dump_callchain_; bool monitor_all_threads_; std::set monitor_tid_filter_; std::map threads_; std::map maps_; std::queue> thread_map_info_q_; IOEventLoop loop_; }; void SampleManager::Run() { auto read_callback = [&](const UnixSocketMessage& msg) { return HandleMessage(msg); }; auto close_callback = [&]() { return loop_.ExitLoop(); }; if (!conn_->PrepareForIO(loop_, read_callback, close_callback)) { return; } loop_.RunLoop(); } bool SampleManager::HandleMessage(const UnixSocketMessage& msg) { if (msg.type == START_PROFILING) { if (!ParseStartProfilingMessage(msg)) { if (!SendStartProfilingReplyMessage(false)) { return false; } return conn_->NoMoreMessage(); } if (!SendStartProfilingReplyMessage(true)) { return false; } return StartProfiling(); } if (msg.type == END_PROFILING) { // Close connection after clearing send buffer. return conn_->NoMoreMessage(); } LOG(ERROR) << "Unexpected msg type: " << msg.type; return false; } bool SampleManager::ParseStartProfilingMessage(const UnixSocketMessage& msg) { char* option = const_cast(msg.data); while (option != nullptr && *option != '\0') { char* next_option = strchr(option, ' '); if (next_option != nullptr) { *next_option++ = '\0'; } char* equal_op = strchr(option, '='); if (equal_op != nullptr) { char* key = option; *equal_op = '\0'; char* value = equal_op + 1; if (strcmp(key, "freq") == 0) { sample_freq_ = atoi(value); } else if (strcmp(key, "signal") == 0) { signo_ = atoi(value); } else if (strcmp(key, "tids") == 0) { monitor_all_threads_ = false; while (*value != '\0') { int tid = static_cast(strtol(value, &value, 10)); monitor_tid_filter_.insert(tid); if (*value == ',') { ++value; } } } else if (strcmp(key, "dump_callchain") == 0) { dump_callchain_ = (strcmp(value, "1") == 0); } } option = next_option; } if (sample_freq_ == 0 || sample_freq_ > 1000000000) { LOG(ERROR) << "Unexpected sample_freq: " << sample_freq_; return false; } if (sample_freq_ == 1) { sample_period_in_ns_ = 999999999; } else { sample_period_in_ns_ = 1000000000 / sample_freq_; } return true; } bool SampleManager::SendStartProfilingReplyMessage(bool ok) { const char* s = ok ? "ok" : "error"; size_t size = sizeof(UnixSocketMessage) + strlen(s) + 1; std::unique_ptr data(new char[size]); UnixSocketMessage* msg = reinterpret_cast(data.get()); msg->len = size; msg->type = START_PROFILING_REPLY; strcpy(msg->data, s); return conn_->SendMessage(*msg, true); } bool SampleManager::StartProfiling() { if (!InstallSignalHandler()) { return false; } if (!CheckThreads()) { return false; } timeval tv; tv.tv_sec = CHECK_THREADS_INTERVAL_IN_MS / 1000; tv.tv_usec = CHECK_THREADS_INTERVAL_IN_MS % 1000 * 1000; return loop_.AddPeriodicEvent(tv, [&]() { return CheckThreads(); }); } bool SampleManager::InstallSignalHandler() { return true; } bool SampleManager::CheckThreads() { uint64_t timestamp = GetSystemClock(); if (!CheckMapChange(timestamp)) { return false; } if (!CheckThreadNameChange(timestamp)) { return false; } SendThreadMapInfo(); // For testing. SendFakeSampleRecord(); return true; } bool SampleManager::CheckThreadNameChange(uint64_t timestamp) { std::vector tids = GetThreadsInProcess(getpid()); std::map current; for (auto& tid : tids) { if (tid == tid_) { // Skip sample thread. continue; } if (monitor_all_threads_ || monitor_tid_filter_.find(tid) != monitor_tid_filter_.end()) { std::string name; if (GetThreadName(tid, &name)) { current[tid] = name; } } } // Check new threads or threads with new names. for (auto& pair : current) { pid_t tid = pair.first; auto it = threads_.find(tid); if (it == threads_.end() || it->second.name != pair.second) { threads_[tid].name = pair.second; size_t size = sizeof(UnixSocketMessage) + sizeof(uint64_t) + sizeof(uint32_t) + pair.second.size() + 1; std::unique_ptr data(new char[size]); UnixSocketMessage* msg = reinterpret_cast(data.get()); msg->len = size; msg->type = THREAD_INFO; char* p = msg->data; MoveToBinaryFormat(timestamp, p); MoveToBinaryFormat(static_cast(tid), p); MoveToBinaryFormat(pair.second.c_str(), pair.second.size() + 1, p); thread_map_info_q_.push(std::move(data)); } } // Check deleted threads. for (auto it = threads_.begin(); it != threads_.end();) { int tid = it->first; if (current.find(tid) == current.end()) { it = threads_.erase(it); } else { ++it; } } return true; } bool SampleManager::CheckMapChange(uint64_t timestamp) { std::vector maps; if (!GetThreadMmapsInProcess(getpid(), &maps)) { return false; } // Check new maps or changed maps. for (auto& map : maps) { if (!map.executable) { continue; } auto it = maps_.find(map.start_addr); if (it == maps_.end() || it->second.len != map.len || it->second.pgoff != map.pgoff || it->second.name != map.name) { maps_[map.start_addr] = map; size_t size = sizeof(UnixSocketMessage) + sizeof(uint64_t) * 4 + map.name.size() + 1; std::unique_ptr data(new char[size]); UnixSocketMessage* msg = reinterpret_cast(data.get()); msg->len = size; msg->type = MAP_INFO; char* p = msg->data; MoveToBinaryFormat(timestamp, p); MoveToBinaryFormat(map.start_addr, p); MoveToBinaryFormat(map.len, p); MoveToBinaryFormat(map.pgoff, p); MoveToBinaryFormat(map.name.c_str(), map.name.size() + 1, p); thread_map_info_q_.push(std::move(data)); } } return true; } void SampleManager::SendThreadMapInfo() { while (!thread_map_info_q_.empty()) { auto& data = thread_map_info_q_.front(); UnixSocketMessage* msg = reinterpret_cast(data.get()); if (!conn_->SendMessage(*msg, false)) { break; } thread_map_info_q_.pop(); } } static void FakeFunction() { } void SampleManager::SendFakeSampleRecord() { size_t size = sizeof(UnixSocketMessage) + sizeof(uint64_t) * 2 + sizeof(uint32_t) * 3; std::unique_ptr data(new char[size]); UnixSocketMessage* msg = reinterpret_cast(data.get()); uint64_t ip = static_cast(reinterpret_cast(&FakeFunction)); msg->len = size; msg->type = SAMPLE_INFO; char* p = msg->data; MoveToBinaryFormat(GetSystemClock(), p); MoveToBinaryFormat(static_cast(tid_), p); MoveToBinaryFormat(1u, p); MoveToBinaryFormat(1u, p); MoveToBinaryFormat(ip, p); conn_->SendMessage(*msg, false); } static void* CommunicationThread(void*) { pthread_setname_np(pthread_self(), "inplace_sampler"); std::string server_path = "inplace_sampler_server_" + std::to_string(getpid()); std::unique_ptr server = UnixSocketServer::Create(server_path, true); if (server == nullptr) { LOG(ERROR) << "failed to create server at path " << server_path; return nullptr; } LOG(INFO) << "Create inplace_sampler_server at " << server_path; while (true) { std::unique_ptr conn = server->AcceptConnection(); if (conn == nullptr) { break; } SampleManager manager(std::move(conn)); manager.Run(); } return nullptr; } __attribute__((constructor)) void InitSampler() { pthread_attr_t attr; if (pthread_attr_init(&attr) != 0) { LOG(ERROR) << "pthread_attr_init failed"; return; } if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0) { LOG(ERROR) << "pthread_attr_setdetachstate failed"; return; } pthread_t thread; if (pthread_create(&thread, &attr, CommunicationThread, nullptr) != 0) { LOG(ERROR) << "pthread_create failed"; return; } pthread_attr_destroy(&attr); } } // namespace