blob: a4aa0f78508b5e73defbe7b44f2dabb13c56d15e [file]
// Copyright 2023 Google LLC
//
// 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
//
// https://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 "mpact/sim/generic/component.h"
#include <string>
#include <utility>
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "mpact/sim/generic/config.h"
#include "mpact/sim/generic/counters_base.h"
namespace mpact {
namespace sim {
namespace generic {
using ::mpact::sim::proto::ComponentData;
using ::mpact::sim::proto::ComponentValueEntry;
// The Get[ChildComponent, Counter, Config] methods are all identical except
// for the element data type. This template static function factors out the
// commonalities.
template <typename M, typename T>
static T* GetMapEntry(const M& map, absl::string_view name) {
auto ptr = map.find(name);
return (ptr == map.end()) ? nullptr : ptr->second;
}
// The Add[ChildComponent,Counter,Config] methods are all identical except
// for the element data type. This template static function factors out the
// commonalities.
template <typename M, typename T>
static absl::Status AddMapEntry(absl::string_view name, T* entry, M* map) {
if (entry == nullptr) return absl::InvalidArgumentError("entry is nullptr");
auto ptr = map->find(name);
if (ptr != map->end()) {
return absl::InternalError(
absl::StrCat("entry with name '", name, "' already inserted"));
}
map->emplace(name, entry);
return absl::OkStatus();
}
// Constructors.
Component::Component(std::string name) : component_name_(std::move(name)) {}
Component::Component(std::string name, Component* parent)
: component_name_(std::move(name)) {
if (parent != nullptr) {
auto status = parent->AddChildComponent(*this);
if (!status.ok()) {
LOG(ERROR) << absl::StrCat(
"Failed to add child component '", component_name_, "' to parent: '",
parent->component_name(), "': ", status.message());
}
}
}
// Call the generic static function to Add the element.
absl::Status Component::AddChildComponent(Component& child) {
auto status = AddMapEntry(child.component_name(), &child, &child_map_);
if (!status.ok()) return status;
child.SetParent(this);
return absl::OkStatus();
}
absl::Status Component::AddCounter(CounterBaseInterface* counter) {
if (!counter->IsInitialized()) {
return absl::InvalidArgumentError("Counter has not been initialized");
}
return AddMapEntry(counter->GetName(), counter, &counter_map_);
}
absl::Status Component::AddConfig(ConfigBase* config) {
return AddMapEntry(config->name(), config, &config_map_);
}
// Call the generic static function to Get the element.
Component* Component::GetChildComponent(absl::string_view name) const {
return GetMapEntry<ComponentMap, Component>(child_map_, name);
}
absl::Status Component::RemoveChildComponent(absl::string_view name) {
auto iter = child_map_.find(name);
if (iter == child_map_.end())
return absl::NotFoundError(
absl::StrCat("No such child component '", name, "'"));
child_map_.erase(iter);
return absl::OkStatus();
}
CounterBaseInterface* Component::GetCounter(absl::string_view name) const {
return GetMapEntry<CounterMap, CounterBaseInterface>(counter_map_, name);
}
ConfigBase* Component::GetConfig(absl::string_view name) const {
return GetMapEntry<ConfigMap, ConfigBase>(config_map_, name);
}
// Import information from the component data proto.
absl::Status Component::Import(const ComponentData& component_data) {
// Checking that the proto name matches. Recursive calls will not generate
// this error, but need to check at the top level.
if (!component_data.has_name() ||
(component_name() != component_data.name())) {
return absl::InternalError(absl::StrCat(
"Name mismatch on import '", component_name(), "' != '",
(component_data.has_name() ? component_data.name() : ""), "'"));
}
// First import self - as this may cause new child components to be created
// based on the values of the configuration entries.
auto status = ImportSelf(component_data);
if (!status.ok()) return status;
// Then import for the child components.
status = ImportChildren(component_data);
if (!status.ok()) return status;
return absl::OkStatus();
}
absl::Status Component::ImportSelf(const ComponentData& component_data) {
for (auto const& entry : component_data.configuration()) {
if (!entry.has_name()) {
// The proto is malformed.
return absl::InternalError("Missing name in component value");
}
ConfigBase* config = GetConfig(entry.name());
// It's not an error if there are proto values for config entries that
// aren't registered. Just skip and continue.
if (config == nullptr) continue;
auto status = config->Import(&entry);
if (!status.ok()) return status;
}
return absl::OkStatus();
}
absl::Status Component::ImportChildren(const ComponentData& component_data) {
for (auto const& child_data : component_data.component_data()) {
if (!child_data.has_name()) {
return absl::InternalError("Unnamed child component");
}
Component* child = GetChildComponent(child_data.name());
if (child == nullptr) continue;
auto status = child->Import(child_data);
if (!status.ok()) return status;
}
return absl::OkStatus();
}
void Component::ImportDone() const {
// Propagate down the component hierarchy.
for (auto const& [unused, child] : child_map_) {
child->ImportDone();
}
// Notify through callbacks.
for (auto const& callback : callback_vec_) {
callback();
}
}
// Export the information reachable from this component to the mutable
// component data proto.
absl::Status Component::Export(ComponentData* component_data) {
if (component_data == nullptr) {
return absl::InvalidArgumentError("Component data is null");
}
component_data->set_name(component_name());
// Export the configuration values.
for (auto const& [unused, config_pair] : config_map_) {
ComponentValueEntry* entry = component_data->add_configuration();
auto status = config_pair->Export(entry);
if (!status.ok()) return status;
}
// Export the counter values.
for (auto const& [unused, counter_pair] : counter_map_) {
ComponentValueEntry* entry = component_data->add_statistics();
auto status = counter_pair->Export(entry);
if (!status.ok()) return status;
}
// Recursively export child component data.
for (auto const& [unused, child_pair] : child_map_) {
ComponentData* child_data = component_data->add_component_data();
auto status = child_pair->Export(child_data);
if (!status.ok()) return status;
}
return absl::OkStatus();
}
} // namespace generic
} // namespace sim
} // namespace mpact