| // 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 "src/developer/system_monitor/bin/dockyard_host/dockyard_host.h" | |
| #include <time.h> | |
| #include <chrono> | |
| #include <fstream> | |
| #include <future> | |
| #include <thread> | |
| #include "src/developer/system_monitor/lib/gt_log.h" | |
| namespace { | |
| // Generic function to extract the values (without the keys) from a map. | |
| template <typename K, typename V> | |
| std::vector<V> GetMapValues(std::map<K, V> input) { | |
| std::vector<V> result; | |
| result.reserve(input.size()); | |
| for (const auto& entry : input) { | |
| result.push_back(entry.second); | |
| } | |
| return result; | |
| } | |
| // Test a multi-step query. First the set of named kernel objects is collected, | |
| // then the IDs are determined, the IDs are translated into strings, and final | |
| // results are printed to the log. | |
| void TestFetchKoidNames(DockyardHost* dockyard_host, | |
| const dockyard::Dockyard& dockyard) { | |
| dockyard::DockyardPathToIdMap paths = dockyard.MatchPaths("koid:", ":name"); | |
| auto names = dockyard_host->GetSampleStringsForIds(GetMapValues(paths)); | |
| if (!names) { | |
| return; | |
| } | |
| if (names->size() != paths.size()) { | |
| GT_LOG(WARNING) << "names and paths size mismatch." | |
| << " names " << names->size() << ", paths " << paths.size(); | |
| return; | |
| } | |
| auto name = names->begin(); | |
| auto path = paths.begin(); | |
| for (; name < names->end(); ++name, ++path) { | |
| GT_LOG(INFO) << path->second << "=" << path->first << ": '" << *name << "'"; | |
| } | |
| } | |
| } // namespace | |
| DockyardHost::DockyardHost() : is_connected_(false) { | |
| // Set up callback handlers. | |
| dockyard_.SetDockyardPathsHandler(std::bind(&DockyardHost::OnPaths, this, | |
| std::placeholders::_1, | |
| std::placeholders::_2)); | |
| } | |
| void DockyardHost::StartCollectingFrom(const std::string& device_name) { | |
| dockyard::ConnectionRequest request; | |
| request.SetDeviceName(device_name); | |
| bool started = dockyard_.StartCollectingFrom( | |
| std::move(request), | |
| [this, device_name](const dockyard::ConnectionRequest& /*request*/, | |
| const dockyard::ConnectionResponse& response) { | |
| if (!response.Ok()) { | |
| GT_LOG(FATAL) << "StartCollectingFrom failed"; | |
| return; | |
| } | |
| OnConnection(device_name); | |
| }); | |
| if (!started) { | |
| GT_LOG(FATAL) << "Call StopCollectingFromDevice before calling" | |
| " StartCollectingFrom again"; | |
| } | |
| } | |
| std::optional<std::future<std::unique_ptr<AsyncQuery>>> | |
| DockyardHost::GetSamples(const std::vector<dockyard::DockyardId>& path_ids) { | |
| // Create a query instance. | |
| auto emplacement = std::make_unique<AsyncQuery>(); | |
| auto iter = request_id_to_async_query_.emplace( | |
| emplacement->request.RequestId(), std::move(emplacement)); | |
| if (!iter.second) { | |
| GT_LOG(ERROR) << "Failed to emplace query (might be out of" | |
| " memory)."; | |
| return {}; | |
| } | |
| auto query = iter.first->second.get(); | |
| // Fill in the request. | |
| query->request.start_time_ns = 0; | |
| query->request.end_time_ns = dockyard_.LatestSampleTimeNs(); | |
| query->request.sample_count = 1; | |
| query->request.render_style = dockyard::StreamSetsRequest::RECENT; | |
| query->request.dockyard_ids.insert(query->request.dockyard_ids.begin(), | |
| path_ids.begin(), path_ids.end()); | |
| // Issue the request for the data. | |
| dockyard_.GetStreamSets(std::move(iter.first->second->request), | |
| [this](const dockyard::StreamSetsRequest& /*request*/, | |
| const dockyard::StreamSetsResponse& response) { | |
| OnStreamSets(response); | |
| }); | |
| return std::optional<std::future<std::unique_ptr<AsyncQuery>>>{ | |
| iter.first->second->promise.get_future()}; | |
| } | |
| std::optional<dockyard::SampleValue> DockyardHost::GetSampleValue( | |
| const std::string& path) { | |
| std::vector<dockyard::DockyardId> dockyard_ids; | |
| dockyard_ids.emplace_back(dockyard_.GetDockyardId(path)); | |
| auto future = GetSamples(dockyard_ids); | |
| if (!future) { | |
| return {}; | |
| } | |
| return future->get()->response.highest_value; | |
| } | |
| std::optional<std::string> DockyardHost::GetSampleString( | |
| const std::string& path) { | |
| std::vector<dockyard::DockyardId> dockyard_ids; | |
| dockyard_ids.emplace_back(dockyard_.GetDockyardId(path)); | |
| auto future = GetSamples(dockyard_ids); | |
| if (!future) { | |
| return {}; | |
| } | |
| dockyard::DockyardId dockyard_id = future->get()->response.highest_value; | |
| std::string string_as_path; | |
| if (dockyard_.GetDockyardPath(dockyard_id, &string_as_path)) { | |
| return string_as_path; | |
| } | |
| return {}; | |
| } | |
| std::optional<std::vector<const std::string>> | |
| DockyardHost::GetSampleStringsForIds( | |
| const std::vector<dockyard::DockyardId>& path_ids) { | |
| auto future = GetSamples(path_ids); | |
| if (!future) { | |
| return {}; | |
| } | |
| std::unique_ptr<AsyncQuery> query = future->get(); | |
| GT_LOG(DEBUG) << "GetSampleStringsForIds query " | |
| << dockyard::DebugPrintQuery(dockyard_, query->request, | |
| query->response) | |
| .str(); | |
| std::vector<const std::string> result; | |
| for (const auto& sample_values : query->response.data_sets) { | |
| std::string string_as_path; | |
| if (dockyard_.GetDockyardPath(sample_values[0], &string_as_path)) { | |
| result.emplace_back(string_as_path); | |
| } else { | |
| result.emplace_back("<not found>"); | |
| } | |
| } | |
| return result; | |
| } | |
| void DockyardHost::OnConnection(const std::string& device_name) { | |
| GT_LOG(DEBUG) << "Connection from " << device_name; | |
| is_connected_ = true; | |
| device_name_ = device_name; | |
| // This might not be the right choice for all clients. For this application | |
| // starting fresh with the new connection is a reasonable approach. | |
| dockyard_.ResetHarvesterData(); | |
| // Run some tests. | |
| run_tests_ = std::async([this]() { | |
| // Give time for the dockyard to populate some samples. | |
| std::this_thread::sleep_for(std::chrono::seconds(4)); | |
| // How much RAM does the Fuchsia device have. | |
| GT_LOG(INFO) << "memory:device_total_bytes " | |
| << *GetSampleValue("memory:device_total_bytes"); | |
| // How much RAM does the Fuchsia device have. | |
| GT_LOG(INFO) << "cpu:0:busy_time " << *GetSampleValue("cpu:0:busy_time"); | |
| // Dump the dockyard state to a file. | |
| if (dump_state_) { | |
| std::ofstream out_file; | |
| out_file.open("dockyard_dump"); | |
| out_file << dockyard_.DebugDump().str(); | |
| out_file.close(); | |
| } | |
| // Print a list of all the named kernel objects. | |
| TestFetchKoidNames(this, dockyard_); | |
| }); | |
| } | |
| void DockyardHost::OnPaths(const std::vector<dockyard::PathInfo>& add, | |
| const std::vector<dockyard::DockyardId>& remove) { | |
| GT_LOG(DEBUG) << "OnPaths"; | |
| for (const auto& path_info : add) { | |
| GT_LOG(DEBUG) << " add " << path_info.id << ": " << path_info.path; | |
| path_to_id_.emplace(path_info.path, path_info.id); | |
| id_to_path_.emplace(path_info.id, path_info.path); | |
| } | |
| for (const auto& dockyard_id : remove) { | |
| GT_LOG(DEBUG) << " remove " << dockyard_id; | |
| auto search = id_to_path_.find(dockyard_id); | |
| if (search != id_to_path_.end()) { | |
| path_to_id_.erase(search->second); | |
| id_to_path_.erase(search); | |
| } | |
| } | |
| } | |
| void DockyardHost::OnStreamSets(const dockyard::StreamSetsResponse& response) { | |
| auto search = request_id_to_async_query_.find(response.RequestId()); | |
| if (search != request_id_to_async_query_.end()) { | |
| search->second->response = response; | |
| search->second->promise.set_value(std::move(search->second)); | |
| request_id_to_async_query_.erase(search); | |
| } else { | |
| GT_LOG(INFO) << "Did not find RequestId " << response.RequestId(); | |
| } | |
| } |