| // 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. |
| |
| #ifndef SRC_DEVELOPER_FORENSICS_TESTING_FAKES_COBALT_H_ |
| #define SRC_DEVELOPER_FORENSICS_TESTING_FAKES_COBALT_H_ |
| |
| #include <fuchsia/metrics/cpp/fidl.h> |
| #include <fuchsia/metrics/test/cpp/fidl.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include <iostream> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/developer/forensics/utils/cobalt/metrics.h" |
| |
| namespace forensics { |
| namespace fakes { |
| |
| using fuchsia::metrics::test::LogMethod; |
| |
| // A wrapper for getting events from a mock_cobalt component in integration tests. |
| // This class is stateful because the connection to the fake component is stateful. |
| class Cobalt { |
| public: |
| using EventDimensions = std::vector<uint32_t>; |
| // key:<metric_id, dimensions> |
| using EventKey = std::pair<uint32_t, EventDimensions>; |
| |
| Cobalt(std::shared_ptr<sys::ServiceDirectory> environment_services); |
| ~Cobalt(); |
| |
| template <typename DimensionType> |
| void RegisterExpectedEvent(DimensionType dimension, uint32_t count); |
| |
| void RegisterExpectedEvent(int metric_id, EventDimensions dimensions, uint32_t count); |
| |
| // Verify that we receive the expected events in no particular order. If the ignore_extra_events |
| // flag is set, the test won't fail even if we receive duplicated or unexpected events. This |
| // method is stateful because the underlying FIDL call to the fake component is stateful. |
| bool MeetsExpectedEvents(LogMethod log_method, bool ignore_extra_events); |
| |
| private: |
| // Display events for debugging. |
| void DisplayEvents(); |
| |
| fuchsia::metrics::test::MetricEventLoggerQuerierSyncPtr logger_querier_; |
| |
| // <EventKey, Count> |
| std::map<EventKey, uint32_t> expected_events_; |
| std::map<EventKey, uint32_t> received_events_; |
| int remaining_event_count_ = 0; |
| }; |
| |
| std::ostream& operator<<(std::ostream& out, Cobalt::EventDimensions dimensions) { |
| out << "{"; |
| for (uint32_t idx = 0; idx < dimensions.size(); idx++) { |
| out << dimensions[idx]; |
| |
| if (idx < dimensions.size() - 1) { |
| out << ", "; |
| } |
| } |
| out << "}"; |
| return out; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, Cobalt::EventKey key) { |
| out << "{metric_id:" << key.first << ", dimensions:" << key.second << "}"; |
| return out; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, std::map<Cobalt::EventKey, uint32_t> events) { |
| out << "{\n"; |
| for (auto& [key, value] : events) { |
| out << "{key:" << key << ", value:" << value << "}\n"; |
| } |
| out << "}"; |
| return out; |
| } |
| |
| Cobalt::Cobalt(std::shared_ptr<sys::ServiceDirectory> environment_services) { |
| environment_services->Connect(logger_querier_.NewRequest()); |
| } |
| |
| Cobalt::~Cobalt() { |
| using fuchsia::metrics::test::LogMethod; |
| FX_CHECK(logger_querier_) << "logger_querier_ disconnected. Cannot reset mock_cobalt, aborting"; |
| |
| // Reset the logger so tests can be run repeatedly. |
| FX_CHECK(logger_querier_->ResetLogger(cobalt::kProjectId, LogMethod::LOG_OCCURRENCE) == ZX_OK) |
| << "Failed to reset OCCURRENCE events, aborting"; |
| FX_CHECK(logger_querier_->ResetLogger(cobalt::kProjectId, LogMethod::LOG_INTEGER) == ZX_OK) |
| << "Failed to reset LOG_INTEGER events, aborting"; |
| FX_CHECK(logger_querier_->ResetLogger(cobalt::kProjectId, LogMethod::LOG_INTEGER_HISTOGRAM) == |
| ZX_OK) |
| << "Failed to reset LOG_INTEGER_HISTOGRAM events, aborting"; |
| FX_CHECK(logger_querier_->ResetLogger(cobalt::kProjectId, LogMethod::LOG_STRING) == ZX_OK) |
| << "Failed to reset LOG_STRING events, aborting"; |
| FX_CHECK(logger_querier_->ResetLogger(cobalt::kProjectId, LogMethod::LOG_METRIC_EVENTS) == ZX_OK) |
| << "Failed to reset LOG_METRIC_EVENTS events, aborting"; |
| } |
| |
| void Cobalt::DisplayEvents() { |
| FX_LOGS(INFO) << "Received Events:" << received_events_; |
| FX_LOGS(INFO) << "Expected Events:" << expected_events_; |
| } |
| |
| template <typename DimensionType> |
| void Cobalt::RegisterExpectedEvent(DimensionType dimension, uint32_t count) { |
| RegisterExpectedEvent(MetricIDForEventCode(dimension), {static_cast<uint32_t>(dimension)}, count); |
| } |
| |
| void Cobalt::RegisterExpectedEvent(int metric_id, EventDimensions dimensions, uint32_t count) { |
| auto key = EventKey{metric_id, dimensions}; |
| FX_CHECK(expected_events_.count(key) == 0) << "Duplicated key:" << key; |
| |
| expected_events_[key] = count; |
| remaining_event_count_ += static_cast<int>(count); |
| } |
| |
| bool Cobalt::MeetsExpectedEvents(LogMethod log_method, bool ignore_extra_events) { |
| switch (log_method) { |
| case LogMethod::LOG_OCCURRENCE: |
| case LogMethod::LOG_INTEGER: |
| break; |
| default: |
| FX_NOTIMPLEMENTED() << "this log method testing is not yet implemented!"; |
| } |
| |
| // Get all events of type <log_method> |
| // |
| // We may need to run WatchLogs() multiple times to collect all of the events generated by |
| // our component. This is due to the fact that we are communicating with both the |
| // fuchsia.metrics/MetricEventLogger and fuchsia.metrics.test/MetricEventLoggerQuerier APIs and |
| // are provided no guarantees regarding the order in which messages are received. Thus it's |
| // conceivable (and will actually happen quite often) that the call to WatchLogs() (and maybe |
| // even ResetLogger()) will get to the component serving both APIs before either of the calls to |
| // LogEvent() arrive and a response containing zero or one Cobalt events is received. So, if you |
| // wish to remove this for loop it is a prerequisite that you have figured out a way to guarantee |
| // the ordering of independent, asynchronous messages, made it so that you component only ever |
| // logs to Cobalt, or don't care about flakes in your tests. |
| // |
| // We can set an upper bound on the number of calls to the LoggerQuerier since calls to |
| // WatchLogs() after the first block if until new events are received. If we assume we send N |
| // cobalt events, in the worst case WatchLogs() is called before any events are received, |
| // returning nothing. Then to get the rest of the N sent events we must make at most N calls to |
| // WatchLogs() since we're guaranteed that each call will return with at least one new event. |
| while (remaining_event_count_ > 0) { |
| std::vector<fuchsia::metrics::MetricEvent> event_results; |
| bool more; |
| |
| FX_CHECK(logger_querier_->WatchLogs(cobalt::kProjectId, log_method, &event_results, &more) == |
| ZX_OK); |
| FX_CHECK(!more); |
| |
| for (const auto& event : event_results) { |
| auto key = EventKey{event.metric_id, event.event_codes}; |
| |
| received_events_[key]++; |
| |
| if (expected_events_.count(key) && received_events_[key] <= expected_events_[key]) { |
| remaining_event_count_--; |
| } else if (ignore_extra_events == false) { |
| FX_LOGS(ERROR) << "Found unexpected event:" << key; |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace fakes |
| } // namespace forensics |
| |
| #endif // SRC_DEVELOPER_FORENSICS_TESTING_FAKES_COBALT_H_ |