blob: 18c2ff6310e68845627667e23129a6b6563192d4 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "harvester.h"
#include <lib/async/cpp/task.h>
#include <lib/async/dispatcher.h>
#include <lib/inspect/query/discover.h>
#include <lib/zx/time.h>
#include <task-utils/walker.h>
#include <zircon/status.h>
#include <chrono>
#include <memory>
#include <string>
#include "src/lib/fxl/logging.h"
namespace harvester {
namespace {
// Utility function to label and append a cpu sample to the |list|. |cpu| is the
// index returned from the kernel. |path| is the kind of sample, e.g.
// "interrupt_count".
void AddCpuValue(SampleList* list, size_t cpu, const std::string path,
dockyard::SampleValue value) {
std::ostringstream label;
label << "cpu:" << cpu << ":" << path;
list->emplace_back(label.str(), value);
}
class TaskHarvester final : public TaskEnumerator {
public:
TaskHarvester() {}
// After gathering the data, upload it to |dockyard|.
void UploadTaskInfo(const std::unique_ptr<DockyardProxy>& dockyard_proxy) {
#ifdef VERBOSE_OUTPUT
for (const auto& int_sample : int_sample_list_) {
FXL_LOG(INFO) << int_sample.first << ": " << int_sample.second;
}
for (const auto& string_sample : string_sample_list_) {
FXL_LOG(INFO) << string_sample.first << ": " << string_sample.second;
}
#endif // VERBOSE_OUTPUT
dockyard_proxy->SendSampleList(int_sample_list_);
dockyard_proxy->SendStringSampleList(string_sample_list_);
int_sample_list_.clear();
string_sample_list_.clear();
}
private:
SampleList int_sample_list_;
StringSampleList string_sample_list_;
// Gather stats for a specific job.
// |koid| must refer to the same job as the job handle.
void AddJobInfo(zx_handle_t job, zx_koid_t koid) {
zx_info_job_t info;
zx_status_t status =
zx_object_get_info(job, ZX_INFO_JOB, &info, sizeof(info),
/*actual=*/nullptr, /*available=*/nullptr);
if (status != ZX_OK) {
FXL_LOG(WARNING) << "AddJobInfo failed for koid " << koid << " ("
<< status << ")";
return;
}
AddKoidValue(koid, "kill_on_oom", info.kill_on_oom);
}
// Helper to add a value to the sample |int_sample_list_|.
void AddKoidValue(zx_koid_t koid, const std::string path,
dockyard::SampleValue value) {
std::ostringstream label;
label << "koid:" << koid << ":" << path;
int_sample_list_.emplace_back(label.str(), value);
}
// Helper to add a value to the string list.
void AddKoidString(zx_koid_t koid, const std::string path,
std::string value) {
std::ostringstream label;
label << "koid:" << koid << ":" << path;
string_sample_list_.emplace_back(label.str(), value);
}
// Helper to add the name of a koid to the string list.
// |koid| must refer to the same task as the task handle.
void AddKoidName(zx_handle_t task, zx_koid_t koid) {
char name[ZX_MAX_NAME_LEN];
zx_status_t status =
zx_object_get_property(task, ZX_PROP_NAME, &name, sizeof(name));
if (status != ZX_OK) {
FXL_LOG(WARNING) << "AddKoidName failed for koid " << koid << " ("
<< status << ")";
return;
}
AddKoidString(koid, "name", name);
#ifdef VERBOSE_OUTPUT
FXL_LOG(INFO) << "name " << name;
#endif // VERBOSE_OUTPUT
}
// Gather stats for a specific process.
// |koid| must refer to the same process as the process handle.
void AddProcessStats(zx_handle_t process, zx_koid_t koid) {
zx_info_task_stats_t info;
zx_status_t status =
zx_object_get_info(process, ZX_INFO_TASK_STATS, &info, sizeof(info),
/*actual=*/nullptr, /*available=*/nullptr);
if (status != ZX_OK) {
FXL_LOG(WARNING) << "AddKoidName failed for koid " << koid << " ("
<< status << ")";
return;
}
AddKoidValue(koid, "memory_mapped_bytes", info.mem_mapped_bytes);
AddKoidValue(koid, "memory_private_bytes", info.mem_private_bytes);
AddKoidValue(koid, "memory_shared_bytes", info.mem_shared_bytes);
AddKoidValue(koid, "memory_scaled_shared_bytes",
info.mem_scaled_shared_bytes);
}
// Gather stats for a specific thread.
// |koid| must refer to the same thread as the thread handle.
void AddThreadStats(zx_handle_t thread, zx_koid_t koid) {
zx_info_thread_t info;
zx_status_t status =
zx_object_get_info(thread, ZX_INFO_THREAD, &info, sizeof(info),
/*actual=*/nullptr, /*available=*/nullptr);
if (status != ZX_OK) {
FXL_LOG(WARNING) << "AddThreadStats failed for koid " << koid << " ("
<< status << ")";
return;
}
AddKoidValue(koid, "thread_state", info.state);
}
// |TaskEnumerator| Callback for a job.
zx_status_t OnJob(int depth, zx_handle_t job, zx_koid_t koid,
zx_koid_t parent_koid) override {
AddKoidValue(koid, "type", dockyard::KoidType::JOB);
AddKoidValue(koid, "parent_koid", parent_koid);
AddKoidName(job, koid);
AddJobInfo(job, koid);
return ZX_OK;
}
// |TaskEnumerator| Callback for a process.
zx_status_t OnProcess(int depth, zx_handle_t process, zx_koid_t koid,
zx_koid_t parent_koid) override {
AddKoidValue(koid, "type", dockyard::KoidType::PROCESS);
AddKoidValue(koid, "parent_koid", parent_koid);
AddKoidName(process, koid);
AddProcessStats(process, koid);
return ZX_OK;
}
// |TaskEnumerator| Callback for a thread.
zx_status_t OnThread(int depth, zx_handle_t thread, zx_koid_t koid,
zx_koid_t parent_koid) override {
AddKoidValue(koid, "type", dockyard::KoidType::THREAD);
AddKoidValue(koid, "parent_koid", parent_koid);
AddKoidName(thread, koid);
AddThreadStats(thread, koid);
return ZX_OK;
}
// |TaskEnumerator| Enable On*() calls.
bool has_on_job() const final { return true; }
bool has_on_process() const final { return true; }
bool has_on_thread() const final { return true; }
};
} // namespace
std::ostream& operator<<(std::ostream& out, const DockyardProxyStatus& status) {
switch (status) {
case DockyardProxyStatus::OK:
return out << "OK (0)";
case DockyardProxyStatus::ERROR:
return out << "ERROR (-1)";
}
FXL_NOTREACHED();
return out;
}
Harvester::Harvester(zx::duration cycle_period, zx_handle_t root_resource,
async_dispatcher_t* dispatcher,
std::unique_ptr<DockyardProxy> dockyard_proxy)
: cycle_period_(cycle_period),
root_resource_(root_resource),
dispatcher_(dispatcher),
dockyard_proxy_(std::move(dockyard_proxy)) {}
void Harvester::GatherData() {
GatherCpuSamples();
GatherMemorySamples();
GatherThreadSamples();
// TODO(smbug.com/16): These should be enabled on demand.
// GatherInspectableComponents();
// GatherComponentIntrospection();
// TODO(smbug.com/18): make this actually run at rate (i.e. remove drift from
// execution time).
async::PostDelayedTask(
dispatcher_, [this] { GatherData(); }, cycle_period_);
}
void Harvester::GatherCpuSamples() {
// TODO(smbug.com/34): Determine the array size at runtime (32 is arbitrary).
zx_info_cpu_stats_t stats[32];
size_t actual, avail;
zx_status_t err = zx_object_get_info(root_resource_, ZX_INFO_CPU_STATS,
&stats, sizeof(stats), &actual, &avail);
if (err != ZX_OK) {
FXL_LOG(ERROR) << "ZX_INFO_CPU_STATS returned " << err << "("
<< zx_status_get_string(err) << ")";
return;
}
auto now = std::chrono::high_resolution_clock::now();
auto cpu_time = std::chrono::duration_cast<std::chrono::nanoseconds>(
now.time_since_epoch())
.count();
SampleList list;
for (size_t i = 0; i < actual; ++i) {
// Note: stats[i].flags are not currently recorded.
// Kernel scheduler counters.
AddCpuValue(&list, i, "reschedules", stats[i].reschedules);
AddCpuValue(&list, i, "context_switches", stats[i].context_switches);
AddCpuValue(&list, i, "meaningful_irq_preempts", stats[i].irq_preempts);
AddCpuValue(&list, i, "preempts", stats[i].preempts);
AddCpuValue(&list, i, "yields", stats[i].yields);
// CPU level interrupts and exceptions.
uint64_t busy_time =
cpu_time > stats[i].idle_time ? cpu_time - stats[i].idle_time : 0ull;
AddCpuValue(&list, i, "busy_time", busy_time);
AddCpuValue(&list, i, "idle_time", stats[i].idle_time);
AddCpuValue(&list, i, "external_hardware_interrupts", stats[i].ints);
AddCpuValue(&list, i, "timer_interrupts", stats[i].timer_ints);
AddCpuValue(&list, i, "timer_callbacks", stats[i].timers);
AddCpuValue(&list, i, "syscalls", stats[i].syscalls);
// Inter-processor interrupts.
AddCpuValue(&list, i, "reschedule_ipis", stats[i].reschedule_ipis);
AddCpuValue(&list, i, "generic_ipis", stats[i].generic_ipis);
}
DockyardProxyStatus status = dockyard_proxy_->SendSampleList(list);
if (status != DockyardProxyStatus::OK) {
FXL_LOG(ERROR) << "SendSampleList failed (" << status << ")";
}
}
void Harvester::GatherMemorySamples() {
zx_info_kmem_stats_t stats;
zx_status_t err = zx_object_get_info(
root_resource_, ZX_INFO_KMEM_STATS, &stats, sizeof(stats),
/*actual=*/nullptr, /*available=*/nullptr);
if (err != ZX_OK) {
FXL_LOG(ERROR) << "ZX_INFO_KMEM_STATS error " << zx_status_get_string(err);
return;
}
#ifdef VERBOSE_OUTPUT
FXL_LOG(INFO) << "free memory total " << stats.free_bytes << ", heap "
<< stats.free_heap_bytes << ", vmo " << stats.vmo_bytes
<< ", mmu " << stats.mmu_overhead_bytes << ", ipc "
<< stats.ipc_bytes;
#endif // VERBOSE_OUTPUT
const std::string DEVICE_TOTAL = "memory:device_total_bytes";
const std::string DEVICE_FREE = "memory:device_free_bytes";
const std::string KERNEL_TOTAL = "memory:kernel_total_bytes";
const std::string KERNEL_FREE = "memory:kernel_free_bytes";
const std::string KERNEL_OTHER = "memory:kernel_other_bytes";
const std::string VMO = "memory:vmo_bytes";
const std::string MMU_OVERHEAD = "memory:mmu_overhead_bytes";
const std::string IPC = "memory:ipc_bytes";
const std::string OTHER = "memory:device_other_bytes";
SampleList list;
// Memory for the entire machine.
list.push_back(std::make_pair(DEVICE_TOTAL, stats.total_bytes));
list.push_back(std::make_pair(DEVICE_FREE, stats.free_bytes));
// Memory in the kernel.
list.push_back(std::make_pair(KERNEL_TOTAL, stats.total_heap_bytes));
list.push_back(std::make_pair(KERNEL_FREE, stats.free_heap_bytes));
list.push_back(std::make_pair(KERNEL_OTHER, stats.wired_bytes));
// Categorized memory.
list.push_back(std::make_pair(MMU_OVERHEAD, stats.mmu_overhead_bytes));
list.push_back(std::make_pair(VMO, stats.vmo_bytes));
list.push_back(std::make_pair(IPC, stats.ipc_bytes));
list.push_back(std::make_pair(OTHER, stats.other_bytes));
DockyardProxyStatus status = dockyard_proxy_->SendSampleList(list);
if (status != DockyardProxyStatus::OK) {
FXL_LOG(ERROR) << "SendSampleList failed (" << status << ")";
}
}
void Harvester::GatherThreadSamples() {
TaskHarvester task_harvester;
task_harvester.WalkRootJobTree();
task_harvester.UploadTaskInfo(dockyard_proxy_);
}
void Harvester::GatherInspectableComponents() {
// Gather a list of components that contain inspect data.
const std::string path = "/hub";
StringSampleList string_sample_list;
for (auto& location : inspect::SyncFindPaths(path)) {
std::ostringstream label;
label << "inspectable:" << location.AbsoluteFilePath();
string_sample_list.emplace_back(label.str(), location.file_name);
}
dockyard_proxy_->SendStringSampleList(string_sample_list);
}
void Harvester::GatherComponentIntrospection() {
std::string fake_json_data = "{ \"test\": 5 }";
DockyardProxyStatus status = dockyard_proxy_->SendInspectJson(
"inspect:/hub/fake/234/faux.Inspect", fake_json_data);
if (status != DockyardProxyStatus::OK) {
FXL_LOG(ERROR) << "SendSampleList failed (" << status << ")";
}
}
} // namespace harvester