| // 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 <memory> |
| #include <utility> |
| |
| #include <fuchsia/modular/cpp/fidl.h> |
| #include <fuchsia/sys/cpp/fidl.h> |
| #include <lib/fidl/cpp/binding.h> |
| #include <lib/fsl/vmo/strings.h> |
| #include <lib/modular_test_harness/cpp/fake_component.h> |
| #include <lib/modular_test_harness/cpp/fake_module.h> |
| #include <lib/modular_test_harness/cpp/test_harness_fixture.h> |
| #include <src/lib/fxl/logging.h> |
| #include <src/lib/fxl/macros.h> |
| #include <src/lib/fxl/strings/string_number_conversions.h> |
| #include <trace/event.h> |
| |
| #include "peridot/lib/testing/session_shell_impl.h" |
| #include "src/modular/benchmarks/tracing_waiter.h" |
| |
| namespace { |
| |
| // Number of stories to create in the Loop test. |
| const int kStoryCount = 5; |
| |
| // Number of times each module sets its link value. |
| const int kLinkSetCount = 100; |
| |
| class TestStoryWatcher : fuchsia::modular::StoryWatcher { |
| public: |
| TestStoryWatcher() : binding_(this) {} |
| ~TestStoryWatcher() override = default; |
| |
| // Registers itself as a watcher on the given story. Only one story at a time |
| // can be watched. |
| void Watch(fuchsia::modular::StoryController* const story_controller) { |
| story_controller->Watch(binding_.NewBinding()); |
| } |
| |
| // Deregisters itself from the watched story. |
| void Reset() { binding_.Unbind(); } |
| |
| // Sets a callback that will be called once the story is running. |
| void OnStoryRunning(fit::function<void()> on_running) { on_running_ = std::move(on_running); } |
| |
| private: |
| // |fuchsia::modular::StoryWatcher| |
| void OnStateChange(fuchsia::modular::StoryState state) override { |
| FXL_LOG(INFO) << "TestStoryWatcher.OnStateChange(): " << fidl::ToUnderlying(state); |
| if (state != fuchsia::modular::StoryState::RUNNING) { |
| return; |
| } |
| |
| on_running_(); |
| } |
| |
| // |fuchsia::modular::StoryWatcher| |
| void OnModuleAdded(fuchsia::modular::ModuleData /*module_data*/) override {} |
| |
| // |fuchsia::modular::StoryWatcher| |
| void OnModuleFocused(std::vector<std::string> /*module_path*/) override {} |
| |
| fit::function<void()> on_running_; |
| fidl::Binding<fuchsia::modular::StoryWatcher> binding_; |
| }; |
| |
| // A simple link watcher implementation that invokes a callback when |
| // it sees the watched link change. |
| class TestLinkWatcher : fuchsia::modular::LinkWatcher { |
| public: |
| TestLinkWatcher() : binding_(this) {} |
| ~TestLinkWatcher() override = default; |
| |
| // Registers itself as a watcher on the given link. Only one story at a time |
| // can be watched. |
| void Watch(fuchsia::modular::Link* const link) { link->WatchAll(binding_.NewBinding()); } |
| |
| // Deregisters itself from the watched story. |
| void Reset() { binding_.Unbind(); } |
| |
| // Sets the function that is called when the link changes. |
| void OnNotify(fit::function<void(fidl::StringPtr)> callback) { on_notify_ = std::move(callback); } |
| |
| private: |
| // |fuchsia::modular::LinkWatcher| |
| void Notify(fuchsia::mem::Buffer value) override { |
| std::string json; |
| FXL_CHECK(fsl::StringFromVmo(value, &json)); |
| on_notify_(std::move(json)); |
| } |
| |
| fidl::Binding<fuchsia::modular::LinkWatcher> binding_; |
| |
| fit::function<void(fidl::StringPtr)> on_notify_{[](fidl::StringPtr) {}}; |
| |
| FXL_DISALLOW_COPY_AND_ASSIGN(TestLinkWatcher); |
| }; |
| |
| // A basic fake session shell component: gives access to services |
| // available to session shells in their environment, as well as an |
| // implementation of fuchsia::modular::SessionShell built for tests. |
| class TestSessionShell : public modular::testing::FakeComponent { |
| public: |
| fuchsia::modular::StoryProvider* story_provider() { return story_provider_.get(); } |
| |
| fuchsia::modular::SessionShellContext* session_shell_context() { |
| return session_shell_context_.get(); |
| } |
| |
| private: |
| // |modular::testing::FakeComponent| |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) override { |
| component_context()->svc()->Connect(session_shell_context_.NewRequest()); |
| session_shell_context_->GetStoryProvider(story_provider_.NewRequest()); |
| |
| component_context()->outgoing()->AddPublicService(session_shell_impl_.GetHandler()); |
| } |
| |
| modular::testing::SessionShellImpl session_shell_impl_; |
| fuchsia::modular::SessionShellContextPtr session_shell_context_; |
| fuchsia::modular::StoryProviderPtr story_provider_; |
| }; |
| |
| // This module repeatedly updates its root link a number of times and then |
| // just sits there until it's terminated. |
| class TestModule : public modular::testing::FakeModule, fuchsia::modular::LinkWatcher { |
| public: |
| TestModule() : link_watcher_binding_(this) {} |
| |
| private: |
| // |modular::testing::FakeComponent| |
| void OnCreate(fuchsia::sys::StartupInfo startup_info) override { |
| modular::testing::FakeModule::OnCreate(std::move(startup_info)); |
| |
| FXL_LOG(INFO) << "TestModule.OnCreate()"; |
| |
| module_context()->GetLink(nullptr, link_.NewRequest()); |
| |
| // Will call Notify() with current value. |
| link_->WatchAll(link_watcher_binding_.NewBinding()); |
| } |
| |
| // |fuchsia::modular::LinkWatcher| |
| void Notify(fuchsia::mem::Buffer content) override { |
| std::string json; |
| FXL_CHECK(fsl::StringFromVmo(content, &json)); |
| FXL_LOG(INFO) << "TestModule.Notify(): " << json; |
| |
| // First invocation is from WatchAll(); next from Set(). |
| if (count_ == -1) { |
| count_ = 0; |
| Set(); |
| return; |
| } |
| |
| // Corresponding TRACE_ASYNC_BEGIN() is in Set(). |
| TRACE_ASYNC_END("benchmark", "link/set", count_); |
| |
| ++count_; |
| if (count_ <= kLinkSetCount) { |
| Set(); |
| } |
| } |
| |
| void Set() { |
| FXL_LOG(INFO) << "TestModule.Set(): " << count_; |
| |
| // Corresponding TRACE_ASYNC_END() is in Notify(). |
| TRACE_ASYNC_BEGIN("benchmark", "link/set", count_); |
| |
| // Corresponding TRACE_FLOW_END() is in the session shell. |
| TRACE_FLOW_BEGIN("benchmark", "link/trans", count_); |
| |
| fsl::SizedVmo vmo; |
| FXL_CHECK(fsl::VmoFromString(std::to_string(count_), &vmo)); |
| link_->Set(nullptr, std::move(vmo).ToTransport()); |
| } |
| |
| // The number of times the root link has been set. |
| int count_{-1}; |
| |
| fuchsia::modular::LinkPtr link_; |
| fidl::Binding<fuchsia::modular::LinkWatcher> link_watcher_binding_; |
| }; |
| |
| // Measures timing the machinery available to a session shell implementation. |
| class StoryBenchmarkTest : public modular::testing::TestHarnessFixture { |
| public: |
| // Name of the module created in CreateStory(). |
| const std::string kModName = "mod"; |
| |
| // Prefix of the name of each story created. |
| const std::string kStoryNamePrefix = "story-"; |
| |
| // Initializes and starts the modular test harness. |
| void InitSession() { |
| modular_testing::TestHarnessBuilder builder; |
| |
| link_watcher_ = std::make_unique<TestLinkWatcher>(); |
| story_watcher_ = std::make_unique<TestStoryWatcher>(); |
| |
| session_shell_ = std::make_unique<TestSessionShell>(); |
| builder.InterceptSessionShell(session_shell_->GetOnCreateHandler(), |
| {.sandbox_services = {"fuchsia.modular.SessionShellContext", |
| "fuchsia.modular.PuppetMaster"}}); |
| |
| // Listen for the module that is created in CreateStory(). |
| module_ = std::make_unique<TestModule>(); |
| module_url_ = modular_testing::TestHarnessBuilder::GenerateFakeUrl(); |
| builder.InterceptComponent( |
| module_->GetOnCreateHandler(), |
| {.url = module_url_, .sandbox_services = module_->GetSandboxServices()}); |
| |
| TRACE_ASYNC_BEGIN("benchmark", "session/start", 0); |
| builder.BuildAndRun(test_harness()); |
| |
| // Wait for our session shell to start. |
| RunLoopUntil([&] { |
| bool is_running = session_shell_->is_running(); |
| if (is_running) { |
| TRACE_ASYNC_END("benchmark", "session/start", 0); |
| } |
| return is_running; |
| }); |
| |
| // Connect to the PuppetMaster service also provided to the session shell. |
| fuchsia::modular::testing::ModularService modular_service; |
| modular_service.set_puppet_master(puppet_master_.NewRequest()); |
| test_harness()->ConnectToModularService(std::move(modular_service)); |
| } |
| |
| void CreateStory(std::string story_name) { |
| FXL_LOG(INFO) << "CreateStory()"; |
| TRACE_ASYNC_BEGIN("benchmark", "story/create", 0); |
| |
| story_name_ = story_name; |
| |
| puppet_master_->ControlStory(story_name_, story_puppet_master_.NewRequest()); |
| |
| fuchsia::modular::AddMod add_mod; |
| add_mod.mod_name_transitional = kModName; |
| add_mod.intent.handler = module_url_; |
| add_mod.intent.action = "action"; |
| |
| std::vector<fuchsia::modular::StoryCommand> commands(1); |
| commands.at(0).set_add_mod(std::move(add_mod)); |
| |
| story_puppet_master_->Enqueue(std::move(commands)); |
| |
| bool is_created{false}; |
| story_puppet_master_->Execute([&](fuchsia::modular::ExecuteResult result) { |
| TRACE_ASYNC_END("benchmark", "story/create", 0); |
| is_created = true; |
| }); |
| |
| // Wait for the story to be created. |
| RunLoopUntil([&] { return is_created; }); |
| |
| session_shell_->story_provider()->GetController(story_name_, story_controller_.NewRequest()); |
| } |
| |
| void StoryInfo() { |
| FXL_LOG(INFO) << "StoryInfo()"; |
| TRACE_ASYNC_BEGIN("benchmark", "story/info", 0); |
| |
| bool got_story_info{false}; |
| story_controller_->GetInfo( |
| [&](fuchsia::modular::StoryInfo story_info, fuchsia::modular::StoryState state) { |
| TRACE_ASYNC_END("benchmark", "story/info", 0); |
| got_story_info = true; |
| }); |
| |
| // Wait for the story info to be returned. |
| RunLoopUntil([&] { return got_story_info; }); |
| } |
| |
| void StartStory() { |
| FXL_LOG(INFO) << "StartStory()"; |
| TRACE_ASYNC_BEGIN("benchmark", "story/start", 0); |
| |
| bool is_started{false}; |
| story_watcher_->OnStoryRunning([&] { |
| TRACE_ASYNC_END("benchmark", "story/start", 0); |
| is_started = true; |
| }); |
| |
| story_watcher_->Watch(story_controller_.get()); |
| story_controller_->RequestStart(); |
| |
| // Wait for the story to start. |
| RunLoopUntil([&] { return is_started; }); |
| } |
| |
| void WatchLink() { |
| FXL_LOG(INFO) << "WatchLink()"; |
| |
| std::vector<std::string> module_path = {kModName}; |
| fuchsia::modular::LinkPath link_path{.module_path = std::move(module_path), |
| .link_name = nullptr}; |
| story_controller_->GetLink(std::move(link_path), link_.NewRequest()); |
| |
| link_watcher_->Watch(link_.get()); |
| |
| link_watcher_->OnNotify([&](fidl::StringPtr json) { |
| FXL_LOG(INFO) << "WatchLink(): " << json; |
| // Ignore empty links which have the JSON string value "null". |
| if (json == "null") { |
| return; |
| } |
| |
| link_value_ = fxl::StringToNumber<int>(json.value_or("")); |
| |
| // Corresponding TRACE_FLOW_BEGIN() is in the module. |
| TRACE_FLOW_END("benchmark", "link/trans", link_value_); |
| }); |
| } |
| |
| void StopStory() { |
| FXL_LOG(INFO) << "StopStory()"; |
| TRACE_ASYNC_BEGIN("benchmark", "story/stop", 0); |
| |
| bool is_stopped{false}; |
| story_controller_->Stop([&] { |
| TRACE_ASYNC_END("benchmark", "story/stop", 0); |
| is_stopped = true; |
| }); |
| |
| // Wait for the story to stop. |
| RunLoopUntil([&] { return is_stopped; }); |
| } |
| |
| void Reset() { |
| FXL_LOG(INFO) << "Reset()"; |
| link_watcher_->Reset(); |
| story_watcher_->Reset(); |
| story_controller_.Unbind(); |
| story_puppet_master_.Unbind(); |
| story_name_.clear(); |
| } |
| |
| void Logout() { |
| FXL_LOG(INFO) << "Logout()"; |
| TRACE_ASYNC_BEGIN("benchmark", "user/logout", 0); |
| session_shell_->session_shell_context()->Logout(); |
| TRACE_ASYNC_END("benchmark", "user/logout", 0); |
| } |
| |
| // The name of the story created by CreateStory(). |
| std::string story_name_; |
| |
| // Component URL of the |module_| intercepted in InitSession(). |
| std::string module_url_; |
| |
| // The last link value that |link_watcher_| has observed. |
| int link_value_{0}; |
| |
| std::unique_ptr<TestStoryWatcher> story_watcher_; |
| std::unique_ptr<TestSessionShell> session_shell_; |
| std::unique_ptr<TestModule> module_; |
| std::unique_ptr<TestLinkWatcher> link_watcher_; |
| |
| fuchsia::modular::StoryControllerPtr story_controller_; |
| fuchsia::modular::PuppetMasterPtr puppet_master_; |
| fuchsia::modular::StoryPuppetMasterPtr story_puppet_master_; |
| fuchsia::modular::LinkPtr link_; |
| |
| modular::TracingWaiter tracing_waiter_; |
| }; |
| |
| TEST_F(StoryBenchmarkTest, Loop) { |
| // Wait for the tracing service to be ready to use. |
| bool is_tracing_started{false}; |
| tracing_waiter_.WaitForTracing([&] { is_tracing_started = true; }); |
| RunLoopUntil([&] { return is_tracing_started; }); |
| |
| InitSession(); |
| |
| for (int i = 1; i <= kStoryCount; i++) { |
| auto story_name = std::string(kStoryNamePrefix) + std::to_string(i); |
| |
| FXL_LOG(INFO) << "Creating story \"" << story_name << "\" (" << i << " of " << kStoryCount |
| << ")"; |
| |
| CreateStory(story_name); |
| StoryInfo(); |
| WatchLink(); |
| StartStory(); |
| |
| // Wait for the module to set the link value |kLinkSetCount| times. |
| RunLoopUntil([&] { return link_value_ == kLinkSetCount; }); |
| |
| StopStory(); |
| |
| Reset(); |
| } |
| |
| Logout(); |
| } |
| |
| } // namespace |