| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <base/at_exit.h> | |
| #include <base/logging.h> | |
| #include <base/functional/bind.h> | |
| #include <base/functional/callback_forward.h> | |
| #include <base/functional/callback_helpers.h> | |
| #include <base/run_loop.h> | |
| #include <base/strings/strcat.h> | |
| #include <base/task/single_thread_task_executor.h> | |
| #include <base/task/sequenced_task_runner.h> | |
| #include <base/task/thread_pool.h> | |
| #include <base/task/thread_pool/thread_pool_instance.h> | |
| #include <base/task/task_traits.h> | |
| #include <base/threading/sequence_bound.h> | |
| #include <base/types/expected.h> | |
| #include <brillo/dbus/dbus_connection.h> | |
| #include <brillo/flag_helper.h> | |
| #include <brillo/syslog_logging.h> | |
| #include <dbus/bus.h> | |
| #include "missive/client/missive_client.h" | |
| #include "missive/proto/record_constants.pb.h" | |
| #include "missive/util/status.h" | |
| #include "missive/util/status_macros.h" | |
| #include "missive/util/statusor.h" | |
| // The tool for manual Enqueue and Flush operations. | |
| // Built as part of missive when running tests. | |
| // Detailed instruction - see in README.md | |
| namespace reporting { | |
| namespace { | |
| class DBusSender { | |
| public: | |
| DBusSender() { | |
| dbus::Bus::Options options; | |
| bus_ = dbus_connection_.Connect(); | |
| CHECK(bus_); | |
| MissiveClient::Initialize(bus_.get()); | |
| } | |
| DBusSender(const DBusSender&) = delete; | |
| DBusSender& operator=(const DBusSender&) = delete; | |
| ~DBusSender() { MissiveClient::Shutdown(); } | |
| void EnqueueEvent(std::string_view priority_string, | |
| std::string_view destination_string, | |
| std::string_view event_string, | |
| base::OnceCallback<void(Status)> cb) { | |
| ASSIGN_OR_ONCE_CALLBACK_AND_RETURN(Priority priority, cb, | |
| DecodePriority(priority_string)); | |
| ASSIGN_OR_ONCE_CALLBACK_AND_RETURN(Destination destination, cb, | |
| DecodeDestination(destination_string)); | |
| Record record; | |
| record.set_timestamp_us(base::Time::Now().InMillisecondsSinceUnixEpoch() * | |
| base::Time::kMicrosecondsPerMillisecond); | |
| record.set_destination(destination); | |
| record.mutable_data()->assign(event_string); | |
| (new RunAndRetryIfNeeded( | |
| base::BindRepeating(&MissiveClient::EnqueueRecord, | |
| base::Unretained(MissiveClient::Get()), priority, | |
| std::move(record)), | |
| std::move(cb))) | |
| ->Run(); | |
| } | |
| void Flush(std::string_view priority_string, | |
| base::OnceCallback<void(Status)> cb) { | |
| ASSIGN_OR_ONCE_CALLBACK_AND_RETURN(Priority priority, cb, | |
| DecodePriority(priority_string)); | |
| (new RunAndRetryIfNeeded( | |
| base::BindRepeating(&MissiveClient::Flush, | |
| base::Unretained(MissiveClient::Get()), priority), | |
| std::move(cb))) | |
| ->Run(); | |
| } | |
| private: | |
| // Helper class runs `action` and repeats it if the returned `status` is | |
| // UNAVAILABLE. If the `status` is OK or any other error, exits. | |
| class RunAndRetryIfNeeded { | |
| public: | |
| RunAndRetryIfNeeded( | |
| base::RepeatingCallback<void(base::OnceCallback<void(Status)>)> action, | |
| base::OnceCallback<void(Status)> cb) | |
| : action_(action), cb_(std::move(cb)) {} | |
| void Run() { | |
| action_.Run(base::BindOnce(&RunAndRetryIfNeeded::CalledBack, | |
| base::Unretained(this))); | |
| } | |
| private: | |
| void CalledBack(Status status) { | |
| if (status.ok() || status.error_code() != error::UNAVAILABLE) { | |
| std::move(cb_).Run(status); | |
| delete this; | |
| return; | |
| } | |
| // Back off and retry. | |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( | |
| FROM_HERE, | |
| base::BindOnce(&RunAndRetryIfNeeded::Run, base::Unretained(this)), | |
| /*delay=*/base::Seconds(1)); | |
| } | |
| const base::RepeatingCallback<void(base::OnceCallback<void(Status)>)> | |
| action_; | |
| base::OnceCallback<void(Status)> cb_; | |
| }; | |
| StatusOr<Priority> DecodePriority(std::string_view priority_string) { | |
| Priority priority; | |
| if (!Priority_Parse(std::string(priority_string), &priority) || | |
| priority == Priority::UNDEFINED_PRIORITY) { | |
| return base::unexpected(Status( | |
| error::INVALID_ARGUMENT, | |
| base::StrCat({"Wrong priority: ", std::string(priority_string)}))); | |
| } | |
| return priority; | |
| } | |
| StatusOr<Destination> DecodeDestination(std::string_view destination_string) { | |
| Destination destination; | |
| if (!Destination_Parse(std::string(destination_string), &destination) || | |
| destination == Destination::UNDEFINED_DESTINATION) { | |
| return base::unexpected( | |
| Status(error::INVALID_ARGUMENT, | |
| base::StrCat({"Wrong destination: ", | |
| std::string(destination_string)}))); | |
| } | |
| return destination; | |
| } | |
| brillo::DBusConnection dbus_connection_; | |
| scoped_refptr<::dbus::Bus> bus_; | |
| }; | |
| } // namespace | |
| } // namespace reporting | |
| int main(int argc, char* argv[]) { | |
| base::AtExitManager exit_manager; | |
| DEFINE_string(priority, "UNDEFINED", "Priority of the queue"); | |
| DEFINE_string(destination, "UNDEFINED", "Destination of the event"); | |
| DEFINE_string(enqueue, "", "Event to be enqueued"); | |
| DEFINE_bool(flush, false, "Flag to flush the event"); | |
| brillo::FlagHelper::Init(argc, argv, R"( | |
| missive_testing_tool: | |
| To enqueue event: | |
| --priority=... priority queue to enqueue to | |
| --destination=... destination of the enqueued event | |
| --enqueue="..." requesting to enqueue event | |
| (record data passed as a string) | |
| To flush queue: | |
| --priority=... priority queue to flush | |
| --flush requesting to flush the queue | |
| )"); | |
| // Always log to syslog and log to stderr if we are connected to a tty. | |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty); | |
| // Override the log items set by brillo::InitLog. | |
| logging::SetLogItems(/*enable_process_id=*/true, /*enable_thread_id=*/true, | |
| /*enable_timestamp=*/true, /*enable_tickcount=*/true); | |
| base::ThreadPoolInstance::CreateAndStartWithDefaultParams( | |
| "missive_test_thread_pool"); | |
| base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO); | |
| base::RunLoop run_loop; | |
| base::SequenceBound<::reporting::DBusSender> sender( | |
| base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})); | |
| auto cb = base::BindOnce( | |
| [](base::ScopedClosureRunner done, ::reporting::Status status) { | |
| LOG_IF(WARNING, status.ok()) << "Success!"; | |
| LOG_IF(ERROR, !status.ok()) << "Failed: " << status; | |
| }, | |
| base::ScopedClosureRunner(run_loop.QuitClosure())); | |
| if (FLAGS_flush) { | |
| LOG(INFO) << "Flush priority=" << FLAGS_priority; | |
| sender.AsyncCall(&::reporting::DBusSender::Flush) | |
| .WithArgs(FLAGS_priority, std::move(cb)); | |
| } else if (!FLAGS_enqueue.empty()) { | |
| LOG(INFO) << "Enqueue priority=" << FLAGS_priority << " " | |
| << " destination=" << FLAGS_destination | |
| << " size_of_data=" << FLAGS_enqueue.size(); | |
| sender.AsyncCall(&::reporting::DBusSender::EnqueueEvent) | |
| .WithArgs(FLAGS_priority, FLAGS_destination, FLAGS_enqueue, | |
| std::move(cb)); | |
| } else { | |
| std::move(cb).Run(::reporting::Status( | |
| ::reporting::error::FAILED_PRECONDITION, "No request specified.")); | |
| } | |
| run_loop.Run(); | |
| exit(0); | |
| } |