-
Notifications
You must be signed in to change notification settings - Fork 89
gRPC service logging bugfix #363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7ee8abf
afebe8c
619c05e
da26a80
a63396b
593fe3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,8 +92,11 @@ class ClusterManagerFactory : public Envoy::Upstream::ProdClusterManagerFactory | |
| bool prefetch_connections_{}; | ||
| }; | ||
|
|
||
| ProcessImpl::ProcessImpl(const Options& options, Envoy::Event::TimeSystem& time_system) | ||
| : time_system_(time_system), stats_allocator_(symbol_table_), store_root_(stats_allocator_), | ||
| ProcessImpl::ProcessImpl(const Options& options, Envoy::Event::TimeSystem& time_system, | ||
| const std::shared_ptr<Envoy::ProcessWide>& process_wide) | ||
| : process_wide_(process_wide == nullptr ? std::make_shared<Envoy::ProcessWide>() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsure about the context - do we need to worry about thread safety here or is ProcessImpl guaranteed to be constructed from a single thread?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All |
||
| : process_wide), | ||
| time_system_(time_system), stats_allocator_(symbol_table_), store_root_(stats_allocator_), | ||
| api_(std::make_unique<Envoy::Api::Impl>(platform_impl_.threadFactory(), store_root_, | ||
| time_system_, platform_impl_.fileSystem())), | ||
| dispatcher_(api_->allocateDispatcher("main_thread")), benchmark_client_factory_(options), | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,7 +46,18 @@ class ClusterManagerFactory; | |
| */ | ||
| class ProcessImpl : public Process, public Envoy::Logger::Loggable<Envoy::Logger::Id::main> { | ||
| public: | ||
| ProcessImpl(const Options& options, Envoy::Event::TimeSystem& time_system); | ||
| /** | ||
| * Instantiates a ProcessImpl | ||
| * @param options provides the options configuration to be used. | ||
| * @param time_system provides the Envoy::Event::TimeSystem implementation that will be used. | ||
| * @param process_wide optional parameter which can be used to pass a pre-setup reference to | ||
| * an active Envoy::ProcessWide instance. ProcessImpl will add a reference to this when passed, | ||
| * and hold on that that throughout its lifetime. | ||
| * If this parameter is not supplied, ProcessImpl will contruct its own Envoy::ProcessWide | ||
| * instance. | ||
| */ | ||
| ProcessImpl(const Options& options, Envoy::Event::TimeSystem& time_system, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a doc comment? We could also explain the optional process_wide argument and say what happens if it isn't provided. |
||
| const std::shared_ptr<Envoy::ProcessWide>& process_wide = nullptr); | ||
| ~ProcessImpl() override; | ||
|
|
||
| /** | ||
|
|
@@ -96,7 +107,7 @@ class ProcessImpl : public Process, public Envoy::Logger::Loggable<Envoy::Logger | |
| bool runInternal(OutputCollector& collector, const std::vector<UriPtr>& uris, | ||
| const UriPtr& request_source_uri, const UriPtr& tracing_uri); | ||
|
|
||
| Envoy::ProcessWide process_wide_; | ||
| std::shared_ptr<Envoy::ProcessWide> process_wide_; | ||
| Envoy::PlatformImpl platform_impl_; | ||
| Envoy::Event::TimeSystem& time_system_; | ||
| Envoy::Stats::SymbolTableImpl symbol_table_; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,11 +33,7 @@ void ServiceImpl::handleExecutionRequest(const nighthawk::client::ExecutionReque | |
| return; | ||
| } | ||
|
|
||
| ProcessImpl process(*options, time_system_); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to reviewers: the fix is to not do this here. We do this once now at construction time, and hold on to the logging context throughout the lifetime of the |
||
| auto logging_context = std::make_unique<Envoy::Logger::Context>( | ||
| spdlog::level::from_str( | ||
| nighthawk::client::Verbosity::VerbosityOptions_Name(options->verbosity())), | ||
| "[%T.%f][%t][%L] %v", log_lock_, false); | ||
| ProcessImpl process(*options, time_system_, process_wide_); | ||
| OutputCollectorImpl output_collector(time_system_, *options); | ||
| const bool ok = process.run(output_collector); | ||
| if (!ok) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,21 +11,34 @@ | |
| #endif | ||
|
|
||
| #include <future> | ||
| #include <memory> | ||
|
|
||
| #include "external/envoy/source/common/common/logger.h" | ||
| #include "external/envoy/source/common/common/thread.h" | ||
| #include "external/envoy/source/common/event/real_time_system.h" | ||
| #include "external/envoy/source/exe/process_wide.h" | ||
|
|
||
| #include "nighthawk/client/process.h" | ||
| #include "nighthawk/common/request_source.h" | ||
|
|
||
| namespace Nighthawk { | ||
| namespace Client { | ||
|
|
||
| /** | ||
| * Implements Nighthawk's gRPC service. This service allows load generation to be | ||
| * controlled by gRPC clients. | ||
| */ | ||
| class ServiceImpl final : public nighthawk::client::NighthawkService::Service, | ||
| public Envoy::Logger::Loggable<Envoy::Logger::Id::main> { | ||
|
|
||
| public: | ||
| /** | ||
| * Constructs a new ServiceImpl instance | ||
| */ | ||
| ServiceImpl() : process_wide_(std::make_shared<Envoy::ProcessWide>()) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add a doc comment for the public constructor? |
||
| logging_context_ = std::make_unique<Envoy::Logger::Context>( | ||
| spdlog::level::from_str("info"), "[%T.%f][%t][%L] %v", log_lock_, false); | ||
| } | ||
| ::grpc::Status ExecutionStream( | ||
| ::grpc::ServerContext* context, | ||
| ::grpc::ServerReaderWriter<::nighthawk::client::ExecutionResponse, | ||
|
|
@@ -36,6 +49,8 @@ class ServiceImpl final : public nighthawk::client::NighthawkService::Service, | |
| void writeResponse(const nighthawk::client::ExecutionResponse& response); | ||
| ::grpc::Status finishGrpcStream(const bool success, absl::string_view description = ""); | ||
|
|
||
| std::unique_ptr<Envoy::Logger::Context> logging_context_; | ||
| std::shared_ptr<Envoy::ProcessWide> process_wide_; | ||
| Envoy::Event::RealTimeSystem time_system_; // NO_CHECK_FORMAT(real_time) | ||
| Envoy::Thread::MutexBasicLockable log_lock_; | ||
| ::grpc::ServerReaderWriter<::nighthawk::client::ExecutionResponse, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ class NighthawkGrpcService(object): | |
| Attributes: | ||
| server_ip: IP address used by the gRPC service to listen. | ||
| server_port: An integer, indicates the port used by the gRPC service to listen. 0 means that the server is not listening. | ||
| log_lines: An array of log lines emitted by the service. Available after stop() is called, reset to None on start(). | ||
| """ | ||
|
|
||
| def __init__(self, | ||
|
|
@@ -39,6 +40,7 @@ def __init__(self, | |
| assert ip_version != IpVersion.UNKNOWN | ||
| self.server_port = 0 | ||
| self.server_ip = server_ip | ||
| self.log_lines = None | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (nit) Does this need to be public or would self._log_lines do? If public, can we document it above in the Attributes: section of the class docstring?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it gets consumed over at https://github.com/envoyproxy/nighthawk/pull/363/files#diff-9620177d1b84e578a841abdc925dcdf4R33. So it needs to be public, and documented. I'll add that.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 593fe3c (also moved the line that resets the log to |
||
| self._server_process = None | ||
| self._ip_version = ip_version | ||
| self._server_binary_path = server_binary_path | ||
|
|
@@ -55,8 +57,9 @@ def _serverThreadRunner(self): | |
| "%s:0" % str(self.server_ip), "--service", self._service_name | ||
| ] | ||
| logging.info("Nighthawk grpc service popen() args: [%s]" % args) | ||
| self._server_process = subprocess.Popen(args) | ||
| self._server_process.communicate() | ||
| self._server_process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
| _, stderr = self._server_process.communicate() | ||
| self.log_lines = stderr.decode("utf-8").splitlines() | ||
| self._address_file = None | ||
|
|
||
| def _waitUntilServerListening(self): | ||
|
|
@@ -87,6 +90,7 @@ def start(self): | |
| can be queried to get the listening port. | ||
| """ | ||
|
|
||
| self.log_lines = None | ||
| self._server_thread.daemon = True | ||
| self._server_thread.start() | ||
| return self._waitUntilServerListening() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way how we can add test coverage for this change to keep the fixed functionality working?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, yes. Lacking C++ mocks for the logging, we can't easily unit-test this issue.
But I could add an end-to-end integration test for this, see 619c05e