blob: 3fd8fbeb588bb490eed41a7282d200b0ce8b6404 [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/async-loop/cpp/loop.h>
#include <lib/fit/defer.h>
#include <lib/inspect/deprecated/object_dir.h>
#include <lib/inspect/inspect.h>
#include <lib/sys/cpp/component_context.h>
#include <string>
/* Inspection Example App
*
* This app demonstrates common features of the Inspect API.
*
* The specific application is an employee task manager. Each |Employee| has a
* number of |Task|s assigned and may have a number of additional |Employee|s
* reporting to them. The full tree of |Task| and |Employee| are exposed over
* the Inspect API.
*
* We are concerned with obtaining each |Employee|'s individual performance and
* the performance of their direct reports. In both cases, |EmployeePerformance|
* is simply the average completion of assigned |Task|s, from 0.0 to 1.0.
*
*/
namespace {
// Global metric counts.
// Global metrics should be raw pointers that are set back to nullptr when
// deleted. Additional concurrency control should be used in multithreaded
// settings.
inspect::UIntMetric* number_of_employees = nullptr;
inspect::UIntMetric* number_of_tasks = nullptr;
// Set the pointers to global values.
// Returns a deferred_action that automatically sets the global pointers to null
// when it goes out of scope.
fit::deferred_action<fit::closure> SetGlobals(
inspect::UIntMetric* employee_count, inspect::UIntMetric* task_count) {
number_of_employees = employee_count;
number_of_tasks = task_count;
return fit::defer(fit::closure([] {
number_of_employees = nullptr;
number_of_tasks = nullptr;
}));
}
// Changes the global count of employees by the given amount.
void CountEmployees(int change) {
if (number_of_employees) {
number_of_employees->Add(change);
}
}
// Changes the global count of tasks by the given amount.
void CountTasks(int change) {
if (number_of_tasks) {
number_of_tasks->Add(change);
}
}
} // namespace
// A |Task| represents something that needs to be done. It consists of a bug
// number for each tracking as well as a human-readable name.
// It also contains a completion ratio out of 1.0 that can be set dynamically.
class Task {
public:
// Construct a new |Task|.
// Note that the constructor takes an |inspect::Node|; this object is
// provided by the parent to allow linking into the inspect hierarchy.
Task(std::string bug_number, std::string name, inspect::Node object)
: bug_number_(std::move(bug_number)),
name_(std::move(name)),
object_(std::move(object)) {
// Increment the global count metric.
CountTasks(1);
// Create two |StringProperty| members to hold the data for this |Task|.
bug_number_property_ = object_.CreateStringProperty("bug", bug_number_);
name_property_ = object_.CreateStringProperty("name", name_);
// The completion of the task is an |inspect::DoubleMetric|.
completion_metric_ = object_.CreateDoubleMetric("completion", 0);
}
// Destroy the task by decrementing the global count metric.
~Task() { CountTasks(-1); }
// Sets the completion of the |Task|.
void SetCompletion(double completion) {
completion_ = completion < 0 ? 0 : completion > 1 ? 1 : completion;
completion_metric_.Set(completion_);
}
// Gets the completion of the |Task|.
double GetCompletion() const { return completion_; }
private:
std::string bug_number_;
std::string name_;
double completion_ = 0;
// Object for this |Task|. All exposed properties and metrics are rooted on
// this Object.
inspect::Node object_;
// |StringProperty| for bug number and name.
inspect::StringProperty bug_number_property_;
inspect::StringProperty name_property_;
// Metric for this |Task|'s completion.
inspect::DoubleMetric completion_metric_;
};
// Structure representing an |Employee|'s performance.
// Simply holds the total tasks the |Employee| has and the sum of their
// completion ratios.
struct EmployeePerformance {
uint64_t total_tasks;
double total_completion;
// Calculates average completion.
double CalculateCompletion() {
return total_tasks != 0 ? total_completion / total_tasks : 1;
}
// Adds another |EmployeePerformance| to this one, useful for getting the
// average completion of a list of reports.
EmployeePerformance& operator+=(const EmployeePerformance& other) {
total_tasks += other.total_tasks;
total_completion += other.total_completion;
return *this;
}
};
// |Employee| represents an individual employee of our company.
// They consist of a name and email, a list of assigned |Task|s, and a list of
// |Employee|s that directly report to this |Employee|.
class Employee {
public:
// Create a new |Employee|.
// Note that the constructor takes an |inspect::Node| that we may use to
// expose our own metrics, properties, and children Objects.
Employee(std::string name, std::string email, inspect::Node object)
: name_(std::move(name)),
email_(std::move(email)),
object_(std::move(object)) {
// Increment the global employee count.
CountEmployees(1);
// Create an |inspect::StringProperty| for the name and email of this
// employee.
name_property_ = object_.CreateStringProperty("name", name_);
email_property_ = object_.CreateStringProperty("email", email_);
// |Task| objects are nested under another child object, called "tasks".
task_object_ = object_.CreateChild("tasks");
// Each |Employee| reporting to this |Employee| are nested under another
// child object, called "reports".
report_object_ = object_.CreateChild("reports");
// Create an |inspect::LazyMetric| for this |Employee|'s personal
// performance. The "personal_performance" of an |Employee| is the average
// completion of their |Task|s.
lazy_metrics_.emplace_back(object_.CreateLazyMetric(
"personal_performance", [this](component::Metric* out) {
// Callbacks have an "out" parameter that is set to the desired value.
// In this case, set it to the double value of our
// |EmployeePerformance|.
out->SetDouble(GetPerformance().CalculateCompletion());
}));
// Create an |inspect::LazyMetric| for the performance of this
// |Employee|'s reports. The "report" performance of an |Employee| is the
// average completion of all |Task|s assigned to their direct reports.
lazy_metrics_.emplace_back(object_.CreateLazyMetric(
"report_performance", [this](component::Metric* out) {
// Add together the performance for each report, and set the result in
// the out parameter.
EmployeePerformance perf = {};
for (const auto& report : reports_) {
perf += report->GetPerformance();
}
out->SetDouble(perf.CalculateCompletion());
}));
}
// Destroy an |Employee| by subtracting from the global employee count.
~Employee() { CountEmployees(-1); }
// |Employee|s may be moved but not copied.
// This is often necessary because inspect::* types are not copyable.
Employee(Employee&&) = default;
Employee(const Employee&) = delete;
Employee& operator=(Employee&&) = default;
Employee& operator=(const Employee&) = delete;
// Add a new |Task| to this |Employee|.
// |Employee|s always try to assign |Task|s to the report with the least
// number of existing |Task|s before taking a |Task| a task for themself.
//
// Returns a pointer to the |Task| for additional modification.
Task* AddTask(std::string bug_number, std::string name) {
size_t least_loaded_count = GetTaskCount();
Employee* least_loaded_employee = this;
// Iterate over reports to find the report with the least number of existing
// tasks.
for (auto& report : reports_) {
if (report->GetTaskCount() <= least_loaded_count) {
least_loaded_count = report->GetTaskCount();
least_loaded_employee = report.get();
}
}
if (least_loaded_employee == this) {
// If this |Employee| is the least loaded, take the |Task|...
return tasks_
.emplace_back(std::make_unique<Task>(
std::move(bug_number), std::move(name),
// Note: We need to pass an Object linked under this Object into
// the new child. We use |inspect::UniqueName| to assign a
// globally unique suffix to the child's name.
task_object_.CreateChild(inspect::UniqueName("task-"))))
.get();
} else {
// ... otherwise, recursively add the |Task| to the least loaded report.
return least_loaded_employee->AddTask(std::move(bug_number),
std::move(name));
}
}
// Gets the number of |Task|s this |Employee| has.
size_t GetTaskCount() const { return tasks_.size(); }
// Add a new |Employee| reporting to this |Employee|.
Employee* AddReport(std::string name, std::string email) {
return reports_
.emplace_back(std::make_unique<Employee>(
std::move(name), std::string(email),
// Note: We need to pass an Object linked under this Object into the
// new child. We use the |email| directly, since elsewhere we
// guarantee everyone's emails are unique.
report_object_.CreateChild(std::move(email))))
.get();
}
// Gets the performance for this |Employee|.
EmployeePerformance GetPerformance() const {
EmployeePerformance ret = {.total_tasks = tasks_.size(),
.total_completion = 0};
for (const auto& task : tasks_) {
ret.total_completion += task->GetCompletion();
}
return ret;
}
private:
std::string name_;
std::string email_;
// Vector of |Task|s assigned to this |Employee|.
std::vector<std::unique_ptr<Task>> tasks_;
// Vector of |Employee|s reporting to this |Employee|.
std::vector<std::unique_ptr<Employee>> reports_;
// Object under which this |Employee| can expose inspect information.
inspect::Node object_;
// Properties for name and email.
inspect::StringProperty name_property_;
inspect::StringProperty email_property_;
// Object under which this |Employee| nests |Task|s.
inspect::Node task_object_;
// Object under which this |Employee| nests reporting |Employee|s.
inspect::Node report_object_;
// Container for various computed "Lazy" metrics we wish to expose.
std::vector<inspect::LazyMetric> lazy_metrics_;
};
int main(int argc, const char** argv) {
// Standard component setup, create an event loop and obtain the
// |StartupContext|.
async::Loop loop(&kAsyncLoopConfigAttachToThread);
auto context = sys::ComponentContext::Create();
// Create a root object and bind it to out/
auto root_object_dir = component::ObjectDir::Make("root");
inspect::Node root_object(root_object_dir);
fidl::BindingSet<fuchsia::inspect::Inspect> inspect_bindings_;
context->outgoing()->GetOrCreateDirectory("objects")->AddEntry(
fuchsia::inspect::Inspect::Name_,
std::make_unique<vfs::Service>(
inspect_bindings_.GetHandler(root_object_dir.object().get())));
// Create global metrics and globally publish pointers to them.
auto employee_count = root_object.CreateUIntMetric("employee_count", 0);
auto task_count = root_object.CreateUIntMetric("task_count", 0);
auto cleanup = SetGlobals(&employee_count, &task_count);
// Create a CEO |Employee| nested underneath the |root_object|.
// The name "reporting_tree" will appear as a child of the root object.
Employee ceo("CEO", "ceo@example.com",
root_object.CreateChild("reporting_tree"));
// Create some reports for the CEO, named Bob, Prakash, and Svetlana.
auto* bob = ceo.AddReport("Bob", "bob@example.com");
auto* prakash = ceo.AddReport("Prakash", "prakash@example.com");
auto* svetlana = ceo.AddReport("Svetlana", "svetlana@example.com");
// Bob has 3 reports: Julie, James, and Jun.
bob->AddReport("Julie", "julie@example.com");
bob->AddReport("James", "james@example.com");
bob->AddReport("Jun", "jun@example.com");
// Prakash has two reports: Gerald and Nathan.
prakash->AddReport("Gerald", "gerald@example.com");
// Nathan is an intern, so assign him a task to complete his training.
prakash->AddReport("Nathan", "nathan@example.com")
->AddTask("ABC-12", "Complete intern code training")
->SetCompletion(1);
// Bob has a lot of corporate tasks to complete, he will give these to people
// in his reporting tree.
bob->AddTask("CORP-100", "Promote extra synergy")->SetCompletion(.5);
bob->AddTask("CORP-101", "Circle back and re-sync")->SetCompletion(.75);
bob->AddTask("CORP-102", "Look into issue with facilities")
->SetCompletion(.8);
bob->AddTask("CORP-103", "Issue new badges")->SetCompletion(.2);
// Prakash has a lot of engineering tasks to complete, he will give these to
// people in his reporting tree.
prakash->AddTask("ENG-10", "Document key structures")->SetCompletion(1);
prakash->AddTask("ENG-11", "Write login page")->SetCompletion(.1);
prakash->AddTask("ENG-12", "Create design for v2")->SetCompletion(.33);
// Svetlana has a lot of infrastructure tasks to complete, she doesn't
// currently have reports so she takes these herself.
svetlana->AddTask("INFRA-100", "Implement new infrastructure")
->SetCompletion(1);
svetlana->AddTask("INFRA-101", "Onboard new users")->SetCompletion(.8);
// Svetlana hired new people to help with infrastructure.
svetlana->AddReport("Hector", "hector@example.com");
svetlana->AddReport("Dianne", "dianne@example.com");
svetlana->AddReport("Andre", "andre@example.com");
// Good thing Svetlana just hired, here are a bunch of tasks.
svetlana->AddTask("INFRA-102", "Bring up new datacenter")->SetCompletion(.75);
svetlana->AddTask("INFRA-103", "Cleanup old file structure")
->SetCompletion(.25);
svetlana->AddTask("INFRA-104", "Rewire the datacenter again")
->SetCompletion(.9);
svetlana->AddTask("INFRA-105", "Upgrade the cooling system")
->SetCompletion(.8);
svetlana->AddTask("INFRA-106", "Investigate opening a datacenter on Mars")
->SetCompletion(1);
svetlana->AddTask("INFRA-107", "Interface with the cloud")
->SetCompletion(.05);
loop.Run();
return 0;
}