blob: 78fa9cc72fa6719b8cf8e3bacb9a02f64402168f [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/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) {
// TODO(smbug.com/20): Send data to dockyard.
for (auto iter = list_.begin(); iter != list_.end(); ++iter) {
FXL_LOG(INFO) << iter->first << ": " << iter->second;
}
}
private:
SampleList list_;
// Helper to add a value to the sample |list|.
void AddKoidValue(zx_koid_t koid, const std::string path,
dockyard::SampleValue value) {
std::ostringstream label;
label << "koid:" << koid << ":" << path;
list_.emplace_back(label.str(), value);
}
// |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);
// TODO(smbug.com/20): gather more info.
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);
// TODO(smbug.com/20): gather more info.
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);
// TODO(smbug.com/20): gather more info.
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,
harvester::DockyardProxy* dockyard_proxy)
: cycle_period_(cycle_period),
root_resource_(root_resource),
dispatcher_(dispatcher),
dockyard_proxy_(dockyard_proxy) {}
void Harvester::GatherData() {
GatherCpuSamples();
GatherMemorySamples();
GatherThreadSamples();
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), NULL, NULL);
if (err != ZX_OK) {
FXL_LOG(ERROR) << "ZX_INFO_KMEM_STATS error " << zx_status_get_string(err);
return;
}
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;
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.UploadTaskInfo(dockyard_proxy_);
}
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