blob: 2982ab4b56d443ef0a34fdd876560edcbb771fc3 [file] [log] [blame]
// Copyright 2019 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 <lib/inspect-vmo/block.h>
#include <lib/inspect-vmo/scanner.h>
#include <lib/inspect-vmo/snapshot.h>
#include <iterator>
#include <stack>
#include <unordered_map>
#include "fuchsia/inspect/cpp/fidl.h"
#include "lib/fit/bridge.h"
#include "lib/inspect/hierarchy.h"
#include "lib/inspect/reader.h"
namespace inspect {
namespace {
hierarchy::Node FidlObjectToNode(fuchsia::inspect::Object obj) {
std::vector<hierarchy::Property> properties;
std::vector<hierarchy::Metric> metrics;
for (auto& metric : *obj.metrics) {
if (metric.value.is_uint_value()) {
metrics.push_back(
hierarchy::Metric(std::move(metric.key),
hierarchy::UIntMetric(metric.value.uint_value())));
} else if (metric.value.is_int_value()) {
metrics.push_back(
hierarchy::Metric(std::move(metric.key),
hierarchy::IntMetric(metric.value.int_value())));
} else if (metric.value.is_double_value()) {
metrics.push_back(hierarchy::Metric(
std::move(metric.key),
hierarchy::DoubleMetric(metric.value.double_value())));
}
}
for (auto& property : *obj.properties) {
if (property.value.is_str()) {
properties.push_back(hierarchy::Property(
std::move(property.key),
hierarchy::StringProperty(std::move(property.value.str()))));
} else if (property.value.is_bytes()) {
properties.push_back(hierarchy::Property(
std::move(property.key),
hierarchy::ByteVectorProperty(std::move(property.value.bytes()))));
}
}
return hierarchy::Node(std::move(obj.name), std::move(properties),
std::move(metrics));
}
ObjectHierarchy Read(std::shared_ptr<component::Object> object_root,
int depth) {
if (depth == 0) {
return ObjectHierarchy(FidlObjectToNode(object_root->ToFidl()), {});
} else {
std::vector<ObjectHierarchy> children;
auto child_names = object_root->GetChildren();
for (auto& child_name : *child_names) {
auto child_obj = object_root->GetChild(child_name);
if (child_obj) {
children.emplace_back(Read(child_obj, depth - 1));
}
}
return ObjectHierarchy(FidlObjectToNode(object_root->ToFidl()),
std::move(children));
}
}
} // namespace
ObjectHierarchy ReadFromObject(const inspect::Object& object, int depth) {
return Read(object.object_dir().object(), depth);
}
ObjectReader::ObjectReader(
fidl::InterfaceHandle<fuchsia::inspect::Inspect> inspect_handle)
: state_(std::make_shared<internal::ObjectReaderState>()) {
state_->inspect_ptr_.Bind(std::move(inspect_handle));
}
fit::promise<fuchsia::inspect::Object> ObjectReader::Read() const {
fit::bridge<fuchsia::inspect::Object> bridge;
state_->inspect_ptr_->ReadData(bridge.completer.bind());
return bridge.consumer.promise_or(fit::error());
}
fit::promise<ChildNameVector> ObjectReader::ListChildren() const {
fit::bridge<ChildNameVector> bridge;
state_->inspect_ptr_->ListChildren(bridge.completer.bind());
return bridge.consumer.promise_or(fit::error());
}
fit::promise<ObjectReader> ObjectReader::OpenChild(
std::string child_name) const {
fuchsia::inspect::InspectPtr child_ptr;
fit::bridge<bool> bridge;
state_->inspect_ptr_->OpenChild(child_name, child_ptr.NewRequest(),
bridge.completer.bind());
ObjectReader reader(child_ptr.Unbind());
return bridge.consumer.promise_or(fit::error())
.and_then([ret = std::move(reader)](
const bool& success) mutable -> fit::result<ObjectReader> {
if (success) {
return fit::ok(ObjectReader(std::move(ret)));
} else {
return fit::error();
}
});
}
fit::promise<std::vector<ObjectReader>> ObjectReader::OpenChildren() const {
return ListChildren()
.and_then([reader = *this](const ChildNameVector& children) {
std::vector<fit::promise<ObjectReader>> opens;
for (const auto& child_name : *children) {
opens.emplace_back(reader.OpenChild(child_name));
}
return fit::join_promise_vector(std::move(opens));
})
.and_then([](std::vector<fit::result<ObjectReader>>& objects) {
std::vector<ObjectReader> result;
for (auto& obj : objects) {
if (obj.is_ok()) {
result.emplace_back(obj.take_value());
}
}
return fit::ok(std::move(result));
});
}
fit::promise<ObjectHierarchy> ReadFromFidl(ObjectReader reader, int depth) {
auto reader_promise = reader.Read();
if (depth == 0) {
return reader_promise.and_then([reader](fuchsia::inspect::Object& obj) {
return fit::ok(ObjectHierarchy(FidlObjectToNode(std::move(obj)), {}));
});
} else {
auto children_promise =
reader.OpenChildren()
.and_then(
[depth](std::vector<ObjectReader>& result)
-> fit::promise<std::vector<fit::result<ObjectHierarchy>>> {
std::vector<fit::promise<ObjectHierarchy>> children;
for (auto& reader : result) {
children.emplace_back(
ReadFromFidl(std::move(reader), depth - 1));
}
return fit::join_promise_vector(std::move(children));
})
.and_then([](std::vector<fit::result<ObjectHierarchy>>& result) {
std::vector<ObjectHierarchy> children;
for (auto& res : result) {
if (res.is_ok()) {
children.emplace_back(res.take_value());
}
}
return fit::ok(std::move(children));
});
return fit::join_promises(std::move(reader_promise),
std::move(children_promise))
.and_then([reader](
std::tuple<fit::result<fuchsia::inspect::Object>,
fit::result<std::vector<ObjectHierarchy>>>&
result) mutable -> fit::result<ObjectHierarchy> {
if (!std::get<0>(result).is_ok() || !std::get<0>(result).is_ok()) {
return fit::error();
}
return fit::ok(ObjectHierarchy(
FidlObjectToNode(std::get<0>(result).take_value()),
std::get<1>(result).take_value()));
});
}
}
namespace vmo {
namespace internal {
// A ParsedObject contains parsed information for an object.
// It is built iteratively as children and values are discovered.
//
// A ParsedObject is valid only if it has been initialized with a name and
// parent index (which happens when its OBJECT_VALUE block is read).
//
// A ParsedObject is "complete" when the number of children in the parsed
// hierarchy matches an expected count. At this point the ObjectHierarchy may be
// removed and the ParsedObject discarded.
struct ParsedObject {
// The object hierarchy being parsed out of the buffer.
// Metrics and properties are parsed into here as they are read.
ObjectHierarchy hierarchy;
// The number of children expected for this object.
// The object is considered "complete" once the number of children in the
// hierarchy matches this count.
size_t children_count = 0;
// The index of the parent, only valid if this object is initialized.
BlockIndex parent;
// Initializes the stored object with the given name and parent.
void InitializeObject(std::string name, BlockIndex parent) {
hierarchy.node().name() = std::move(name);
this->parent = parent;
initialized_ = true;
}
explicit operator bool() { return initialized_; }
bool is_complete() { return hierarchy.children().size() == children_count; }
private:
bool initialized_ = false;
};
// The |Reader| supports reading the contents of a |Snapshot|.
// This class constructs a hierarchy of objects contained in the snapshot
// if the snapshot is valid.
class Reader {
public:
Reader(Snapshot snapshot) : snapshot_(std::move(snapshot)) {}
// Read the contents of the snapshot and return the root object.
fit::result<ObjectHierarchy> Read();
private:
// Gets a pointer to the ParsedObject for the given index. A new ParsedObject
// is created if one did not exist previously for the index.
ParsedObject* GetOrCreate(BlockIndex index);
void InnerScanBlocks();
// Initialize an Object for the given BlockIndex.
void InnerCreateObject(BlockIndex index, const Block* block);
// Parse a metric block and attach it to the given parent.
void InnerParseMetric(ParsedObject* parent, const Block* block);
// Parse a property block and attach it to the given parent.
void InnerParseProperty(ParsedObject* parent, const Block* block);
// Helper to interpret the given block as a NAME block and return a
// copy of the name contents.
std::string GetAndValidateName(BlockIndex index);
// Contents of the read VMO.
Snapshot snapshot_;
// Map of block index to the parsed object being constructed for that address.
std::unordered_map<BlockIndex, ParsedObject> parsed_objects_;
};
std::string Reader::GetAndValidateName(BlockIndex index) {
Block* block = snapshot_.GetBlock(index);
if (!block) {
return nullptr;
}
size_t size = OrderToSize(GetOrder(block));
size_t len = NameBlockFields::Length::Get<size_t>(block->header);
if (len > size) {
return nullptr;
}
return std::string(block->payload.data, len);
}
void Reader::InnerScanBlocks() {
ScanBlocks(
snapshot_.data(), snapshot_.size(),
[this](BlockIndex index, const Block* block) {
BlockType type = GetType(block);
if (index == 0) {
if (type != BlockType::kHeader) {
return;
}
} else if (type == BlockType::kObjectValue) {
// This block defines an Object, use the value to fill out the name of
// the ParsedObject.
InnerCreateObject(index, block);
} else if (type == BlockType::kIntValue ||
type == BlockType::kUintValue ||
type == BlockType::kDoubleValue ||
type == BlockType::kArrayValue) {
// This block defines a metric for an Object, parse the metric into
// the metrics field of the ParsedObject.
auto parent_index =
ValueBlockFields::ParentIndex::Get<BlockIndex>(block->header);
InnerParseMetric(GetOrCreate(parent_index), block);
} else if (type == BlockType::kPropertyValue) {
// This block defines a property for an Object, parse the property
// into the properties field of the ParsedObject.
auto parent_index =
ValueBlockFields::ParentIndex::Get<BlockIndex>(block->header);
InnerParseProperty(GetOrCreate(parent_index), block);
}
});
}
fit::result<ObjectHierarchy> Reader::Read() {
if (!snapshot_) {
// Snapshot is invalid, return a default object.
return fit::error();
}
// Scan blocks into the parsed_object map. This creates ParsedObjects with
// metrics, properties, and an accurate count of the number of expected
// children. ParsedObjects with a valid OBJECT_VALUE block are initialized
// with a name and parent index.
InnerScanBlocks();
// Stack of completed objects to process. Entries consist of the completed
// ObjectHierarchy and the block index of their parent.
std::stack<std::pair<ObjectHierarchy, BlockIndex>> complete_objects;
// Iterate over the map of parsed objects and find those objects that are
// already "complete." These objects are moved to the complete_objects map for
// bottom-up processing.
auto it = parsed_objects_.begin();
while (it != parsed_objects_.end()) {
if (!it->second) {
// The object is not valid, ignore.
it = parsed_objects_.erase(it);
continue;
}
if (it->second.is_complete()) {
// The object is valid and complete, push it onto the stack.
complete_objects.push(
std::make_pair(std::move(it->second.hierarchy), it->second.parent));
it = parsed_objects_.erase(it);
continue;
}
++it;
}
// Construct a valid hierarchy from the bottom up by attaching completed
// objects to their parent object. Once a parent becomes complete, add it to
// the stack to recursively bubble the completed children towards the root.
while (complete_objects.size() > 0) {
auto obj = std::move(complete_objects.top());
complete_objects.pop();
if (obj.second == 0) {
// We stop once we find a complete object with parent 0. This is assumed
// to be the root, so we return it.
// TODO(crjohns): Deal with the case of multiple roots, if we decide to
// support it.
return fit::ok(std::move(obj.first));
}
// Get the parent object, which was created during block scanning.
auto it = parsed_objects_.find(obj.second);
ZX_ASSERT(it != parsed_objects_.end());
auto* parent = &it->second;
parent->hierarchy.children().emplace_back(std::move(obj.first));
if (parent->is_complete()) {
// The parent object is now complete, push it onto the stack.
complete_objects.push(
std::make_pair(std::move(parent->hierarchy), parent->parent));
parsed_objects_.erase(it);
}
}
// We processed all completed objects but could not find a complete root,
// return an error.
return fit::error();
}
ParsedObject* Reader::GetOrCreate(BlockIndex index) {
return &parsed_objects_.emplace(index, ParsedObject()).first->second;
}
hierarchy::ArrayDisplayFormat ArrayFormatToDisplay(ArrayFormat format) {
switch (format) {
case ArrayFormat::kLinearHistogram:
return hierarchy::ArrayDisplayFormat::LINEAR_HISTOGRAM;
case ArrayFormat::kExponentialHistogram:
return hierarchy::ArrayDisplayFormat::EXPONENTIAL_HISTOGRAM;
default:
return hierarchy::ArrayDisplayFormat::FLAT;
}
}
void Reader::InnerParseMetric(ParsedObject* parent, const Block* block) {
auto name = GetAndValidateName(
ValueBlockFields::NameIndex::Get<size_t>(block->header));
if (name.empty()) {
return;
}
auto& parent_metrics = parent->hierarchy.node().metrics();
BlockType type = GetType(block);
switch (type) {
case BlockType::kIntValue:
parent_metrics.emplace_back(hierarchy::Metric(
std::move(name), hierarchy::IntMetric(block->payload.i64)));
return;
case BlockType::kUintValue:
parent_metrics.emplace_back(hierarchy::Metric(
std::move(name), hierarchy::UIntMetric(block->payload.u64)));
return;
case BlockType::kDoubleValue:
parent_metrics.emplace_back(hierarchy::Metric(
std::move(name), hierarchy::DoubleMetric(block->payload.f64)));
return;
case BlockType::kArrayValue: {
BlockType entry_type =
ArrayBlockPayload::EntryType::Get<BlockType>(block->payload.u64);
uint8_t count =
ArrayBlockPayload::Count::Get<uint8_t>(block->payload.u64);
if (GetArraySlot<const int64_t>(block, count - 1) == nullptr) {
// Block does not store the entire array.
return;
}
auto array_format = ArrayFormatToDisplay(
ArrayBlockPayload::Flags::Get<ArrayFormat>(block->payload.u64));
if (entry_type == BlockType::kIntValue) {
std::vector<int64_t> values;
std::copy(GetArraySlot<const int64_t>(block, 0),
GetArraySlot<const int64_t>(block, count),
std::back_inserter(values));
parent_metrics.emplace_back(hierarchy::Metric(
std::move(name),
hierarchy::IntArray(std::move(values), array_format)));
} else if (entry_type == BlockType::kUintValue) {
std::vector<uint64_t> values;
std::copy(GetArraySlot<const uint64_t>(block, 0),
GetArraySlot<const uint64_t>(block, count),
std::back_inserter(values));
parent_metrics.emplace_back(hierarchy::Metric(
std::move(name),
hierarchy::UIntArray(std::move(values), array_format)));
} else if (entry_type == BlockType::kDoubleValue) {
std::vector<double> values;
std::copy(GetArraySlot<const double>(block, 0),
GetArraySlot<const double>(block, count),
std::back_inserter(values));
parent_metrics.emplace_back(hierarchy::Metric(
std::move(name),
hierarchy::DoubleArray(std::move(values), array_format)));
}
return;
}
default:
return;
}
}
void Reader::InnerParseProperty(ParsedObject* parent, const Block* block) {
auto name = GetAndValidateName(
ValueBlockFields::NameIndex::Get<size_t>(block->header));
if (name.empty()) {
return;
}
size_t remaining_length =
PropertyBlockPayload::TotalLength::Get<size_t>(block->payload.u64);
const size_t total_length = remaining_length;
size_t current_offset = 0;
char buf[total_length];
BlockIndex cur_extent =
PropertyBlockPayload::ExtentIndex::Get<BlockIndex>(block->payload.u64);
auto* extent = snapshot_.GetBlock(cur_extent);
while (remaining_length > 0) {
if (!extent || GetType(extent) != BlockType::kExtent) {
break;
}
size_t len = fbl::min(remaining_length, PayloadCapacity(GetOrder(extent)));
memcpy(buf + current_offset, extent->payload.data, len);
remaining_length -= len;
current_offset += len;
extent = snapshot_.GetBlock(
ExtentBlockFields::NextExtentIndex::Get<BlockIndex>(extent->header));
}
auto& parent_properties = parent->hierarchy.node().properties();
if (PropertyBlockPayload::Flags::Get<uint8_t>(block->payload.u64) &
static_cast<uint8_t>(inspect::vmo::PropertyFormat::kBinary)) {
parent_properties.emplace_back(inspect::hierarchy::Property(
std::move(name), inspect::hierarchy::ByteVectorProperty(
std::vector<uint8_t>(buf, buf + total_length))));
} else {
parent_properties.emplace_back(inspect::hierarchy::Property(
std::move(name),
inspect::hierarchy::StringProperty(std::string(buf, total_length))));
}
}
void Reader::InnerCreateObject(BlockIndex index, const Block* block) {
auto name = GetAndValidateName(
ValueBlockFields::NameIndex::Get<BlockIndex>(block->header));
if (name.empty()) {
return;
}
auto* parsed_object = GetOrCreate(index);
auto parent_index =
ValueBlockFields::ParentIndex::Get<BlockIndex>(block->header);
parsed_object->InitializeObject(std::move(name), parent_index);
if (parent_index && parent_index != index) {
// Only link to a parent if the parent can be valid (not index 0).
auto* parent = GetOrCreate(parent_index);
parent->children_count += 1;
}
}
} // namespace internal
} // namespace vmo
fit::result<ObjectHierarchy> ReadFromSnapshot(vmo::Snapshot snapshot) {
vmo::internal::Reader reader(std::move(snapshot));
return reader.Read();
}
fit::result<ObjectHierarchy> ReadFromVmo(const zx::vmo& vmo) {
inspect::vmo::Snapshot snapshot;
if (inspect::vmo::Snapshot::Create(std::move(vmo), &snapshot) != ZX_OK) {
return fit::error();
}
return ReadFromSnapshot(std::move(snapshot));
}
ObjectHierarchy ReadFromFidlObject(fuchsia::inspect::Object object) {
return ObjectHierarchy(FidlObjectToNode(std::move(object)), {});
}
} // namespace inspect