-
Notifications
You must be signed in to change notification settings - Fork 89
Adaptive Load library for calling Nighthawk Service #493
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
8ea442d
5ac755a
b8c25a5
1c19c68
7050686
0776563
16fd8f6
c383010
6e1a483
4ef1140
4111bf4
871a959
1fd77c1
edc36b2
4d0364e
aed6d94
d9ae87d
a05a6f5
8cd4d21
d814a96
5f5a885
7e20a78
9048267
306c0ec
d33f543
442cca9
677b783
cefb366
f3684df
5463051
46e0e25
f634642
3c39faa
b9c8f2b
5fc4db4
64e7852
12807f1
e8e960f
94fbcbe
6142700
afbc049
48e7121
de747eb
0b76542
fd8d03d
7adbabb
6306b4e
1ece783
4616e6f
70705e9
e576bc1
1fca528
cd6f6da
8d642bb
b1284f5
5cf83e4
24cf230
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 |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| #pragma once | ||
| #include "envoy/common/pure.h" | ||
|
|
||
| #include "external/envoy/source/common/common/statusor.h" | ||
| #include "external/envoy/source/common/protobuf/protobuf.h" | ||
|
|
||
| #include "api/client/options.pb.h" | ||
| #include "api/client/service.grpc.pb.h" | ||
|
|
||
| namespace Nighthawk { | ||
|
|
||
| /** | ||
| * An interface for interacting with a Nighthawk Service gRPC stub. | ||
| */ | ||
| class NighthawkServiceClient { | ||
| public: | ||
| virtual ~NighthawkServiceClient() = default; | ||
|
|
||
| /** | ||
| * Runs a single benchmark using a Nighthawk Service. | ||
| * | ||
| * @param nighthawk_service_stub Nighthawk Service gRPC stub. | ||
| * @param command_line_options Nighthawk Service benchmark request proto generated by the | ||
| * StepController, without the duration set. | ||
| * | ||
| * @return StatusOr<ExecutionResponse> If we reached the Nighthawk Service, this is the raw | ||
| * ExecutionResponse proto, containing the benchmark data or possibly an error message from | ||
| * Nighthawk Service; if we had trouble communicating with the Nighthawk Service, we return an | ||
| * error status. | ||
| */ | ||
| virtual absl::StatusOr<nighthawk::client::ExecutionResponse> PerformNighthawkBenchmark( | ||
| nighthawk::client::NighthawkService::StubInterface* nighthawk_service_stub, | ||
| const nighthawk::client::CommandLineOptions& command_line_options) PURE; | ||
| }; | ||
|
|
||
| } // namespace Nighthawk |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| #include "common/nighthawk_service_client_impl.h" | ||
|
|
||
| #include "external/envoy/source/common/common/assert.h" | ||
|
|
||
| namespace Nighthawk { | ||
|
|
||
| absl::StatusOr<nighthawk::client::ExecutionResponse> | ||
| NighthawkServiceClientImpl::PerformNighthawkBenchmark( | ||
| nighthawk::client::NighthawkService::StubInterface* nighthawk_service_stub, | ||
| const nighthawk::client::CommandLineOptions& command_line_options) { | ||
| nighthawk::client::ExecutionRequest request; | ||
| nighthawk::client::ExecutionResponse response; | ||
| *request.mutable_start_request()->mutable_options() = command_line_options; | ||
|
|
||
| ::grpc::ClientContext context; | ||
| std::shared_ptr<::grpc::ClientReaderWriterInterface<nighthawk::client::ExecutionRequest, | ||
| nighthawk::client::ExecutionResponse>> | ||
| stream(nighthawk_service_stub->ExecutionStream(&context)); | ||
|
|
||
| if (!stream->Write(request)) { | ||
| return absl::UnavailableError("Failed to write request to the Nighthawk Service gRPC channel."); | ||
| } else if (!stream->WritesDone()) { | ||
| return absl::InternalError("WritesDone() failed on the Nighthawk Service gRPC channel."); | ||
| } | ||
|
|
||
| bool got_response = false; | ||
| while (stream->Read(&response)) { | ||
| RELEASE_ASSERT(!got_response, | ||
| "Nighthawk Service has started responding with more than one message."); | ||
| got_response = true; | ||
| } | ||
| if (!got_response) { | ||
| return absl::InternalError("Nighthawk Service did not send a gRPC response."); | ||
| } | ||
| ::grpc::Status status = stream->Finish(); | ||
| if (!status.ok()) { | ||
| return absl::Status(static_cast<absl::StatusCode>(status.error_code()), status.error_message()); | ||
| } | ||
| return response; | ||
| } | ||
|
|
||
| } // namespace Nighthawk |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| #include "nighthawk/common/nighthawk_service_client.h" | ||
|
|
||
| #include "external/envoy/source/common/common/statusor.h" | ||
| #include "external/envoy/source/common/protobuf/protobuf.h" | ||
|
|
||
| #include "api/client/options.pb.h" | ||
| #include "api/client/service.grpc.pb.h" | ||
|
|
||
| namespace Nighthawk { | ||
|
|
||
| /** | ||
| * Real implementation of a helper that opens a channel with the gRPC stub, sends the input, and | ||
| * translates the output or errors into a StatusOr. | ||
| * | ||
| * This class is stateless and may be called from multiple threads. Furthermore, the same gRPC stub | ||
| * is safe to use from multiple threads simultaneously. | ||
| */ | ||
| class NighthawkServiceClientImpl : public NighthawkServiceClient { | ||
| public: | ||
| absl::StatusOr<nighthawk::client::ExecutionResponse> PerformNighthawkBenchmark( | ||
| nighthawk::client::NighthawkService::StubInterface* nighthawk_service_stub, | ||
| const nighthawk::client::CommandLineOptions& command_line_options) override; | ||
| }; | ||
|
|
||
| } // namespace Nighthawk |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| #include "external/envoy/source/common/protobuf/protobuf.h" | ||
|
|
||
| #include "api/client/options.pb.h" | ||
| #include "api/client/service.grpc.pb.h" | ||
| #include "api/client/service_mock.grpc.pb.h" | ||
|
|
||
| #include "common/nighthawk_service_client_impl.h" | ||
|
|
||
| #include "grpcpp/test/mock_stream.h" | ||
|
|
||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace Nighthawk { | ||
|
|
||
| namespace { | ||
|
|
||
| using ::Envoy::Protobuf::util::MessageDifferencer; | ||
| using ::nighthawk::client::CommandLineOptions; | ||
| using ::nighthawk::client::ExecutionRequest; | ||
| using ::nighthawk::client::ExecutionResponse; | ||
| using ::testing::_; | ||
| using ::testing::DoAll; | ||
| using ::testing::HasSubstr; | ||
| using ::testing::Return; | ||
| using ::testing::SaveArg; | ||
| using ::testing::SetArgPointee; | ||
|
|
||
| TEST(PerformNighthawkBenchmark, UsesSpecifiedCommandLineOptions) { | ||
| const int kExpectedRps = 456; | ||
| ExecutionRequest request; | ||
| nighthawk::client::MockNighthawkServiceStub mock_nighthawk_service_stub; | ||
| // Configure the mock Nighthawk Service stub to return an inner mock channel when the code under | ||
| // test requests a channel. Set call expectations on the inner mock channel. | ||
| EXPECT_CALL(mock_nighthawk_service_stub, ExecutionStreamRaw) | ||
| .WillOnce([&request](grpc_impl::ClientContext*) { | ||
| auto* mock_reader_writer = | ||
| new grpc::testing::MockClientReaderWriter<ExecutionRequest, ExecutionResponse>(); | ||
| // PerformNighthawkBenchmark currently expects Read to return true exactly once. | ||
| EXPECT_CALL(*mock_reader_writer, Read(_)).WillOnce(Return(true)).WillOnce(Return(false)); | ||
| // Capture the Nighthawk request PerformNighthawkBenchmark sends on the channel. | ||
| EXPECT_CALL(*mock_reader_writer, Write(_, _)) | ||
| .WillOnce(::testing::DoAll(::testing::SaveArg<0>(&request), Return(true))); | ||
| EXPECT_CALL(*mock_reader_writer, WritesDone()).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, Finish()).WillOnce(Return(::grpc::Status::OK)); | ||
| return mock_reader_writer; | ||
| }); | ||
|
|
||
| CommandLineOptions command_line_options; | ||
| command_line_options.mutable_requests_per_second()->set_value(kExpectedRps); | ||
| NighthawkServiceClientImpl client; | ||
| absl::StatusOr<ExecutionResponse> response_or = | ||
| client.PerformNighthawkBenchmark(&mock_nighthawk_service_stub, command_line_options); | ||
| EXPECT_TRUE(response_or.ok()); | ||
| EXPECT_EQ(request.start_request().options().requests_per_second().value(), kExpectedRps); | ||
| } | ||
|
|
||
| TEST(PerformNighthawkBenchmark, ReturnsNighthawkResponseSuccessfully) { | ||
| ExecutionResponse expected_response; | ||
| nighthawk::client::MockNighthawkServiceStub mock_nighthawk_service_stub; | ||
| // Configure the mock Nighthawk Service stub to return an inner mock channel when the code under | ||
| // test requests a channel. Set call expectations on the inner mock channel. | ||
| EXPECT_CALL(mock_nighthawk_service_stub, ExecutionStreamRaw) | ||
| .WillOnce([&expected_response](grpc_impl::ClientContext*) { | ||
| auto* mock_reader_writer = | ||
| new grpc::testing::MockClientReaderWriter<ExecutionRequest, ExecutionResponse>(); | ||
| // PerformNighthawkBenchmark currently expects Read to return true exactly once. | ||
| // Capture the gRPC response proto as it is written to the output parameter. | ||
| EXPECT_CALL(*mock_reader_writer, Read(_)) | ||
| .WillOnce(DoAll(SetArgPointee<0>(expected_response), Return(true))) | ||
| .WillOnce(Return(false)); | ||
| EXPECT_CALL(*mock_reader_writer, Write(_, _)).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, WritesDone()).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, Finish()).WillOnce(Return(::grpc::Status::OK)); | ||
| return mock_reader_writer; | ||
| }); | ||
|
|
||
| NighthawkServiceClientImpl client; | ||
| absl::StatusOr<ExecutionResponse> response_or = | ||
| client.PerformNighthawkBenchmark(&mock_nighthawk_service_stub, CommandLineOptions()); | ||
| EXPECT_TRUE(response_or.ok()); | ||
| ExecutionResponse actual_response = response_or.value(); | ||
| EXPECT_TRUE(MessageDifferencer::Equivalent(actual_response, expected_response)); | ||
| EXPECT_EQ(actual_response.DebugString(), expected_response.DebugString()); | ||
| } | ||
|
|
||
| TEST(PerformNighthawkBenchmark, ReturnsErrorIfNighthawkServiceDoesNotSendResponse) { | ||
| nighthawk::client::MockNighthawkServiceStub mock_nighthawk_service_stub; | ||
| // Configure the mock Nighthawk Service stub to return an inner mock channel when the code under | ||
| // test requests a channel. Set call expectations on the inner mock channel. | ||
| EXPECT_CALL(mock_nighthawk_service_stub, ExecutionStreamRaw) | ||
| .WillOnce([](grpc_impl::ClientContext*) { | ||
| auto* mock_reader_writer = | ||
| new grpc::testing::MockClientReaderWriter<ExecutionRequest, ExecutionResponse>(); | ||
| EXPECT_CALL(*mock_reader_writer, Read(_)).WillOnce(Return(false)); | ||
| EXPECT_CALL(*mock_reader_writer, Write(_, _)).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, WritesDone()).WillOnce(Return(true)); | ||
| return mock_reader_writer; | ||
| }); | ||
|
|
||
| NighthawkServiceClientImpl client; | ||
| absl::StatusOr<ExecutionResponse> response_or = | ||
| client.PerformNighthawkBenchmark(&mock_nighthawk_service_stub, CommandLineOptions()); | ||
| ASSERT_FALSE(response_or.ok()); | ||
| EXPECT_EQ(response_or.status().code(), absl::StatusCode::kInternal); | ||
| EXPECT_THAT(response_or.status().message(), | ||
| HasSubstr("Nighthawk Service did not send a gRPC response.")); | ||
| } | ||
|
|
||
| TEST(PerformNighthawkBenchmark, ReturnsErrorIfNighthawkServiceWriteFails) { | ||
| nighthawk::client::MockNighthawkServiceStub mock_nighthawk_service_stub; | ||
| // Configure the mock Nighthawk Service stub to return an inner mock channel when the code under | ||
| // test requests a channel. Set call expectations on the inner mock channel. | ||
| EXPECT_CALL(mock_nighthawk_service_stub, ExecutionStreamRaw) | ||
| .WillOnce([](grpc_impl::ClientContext*) { | ||
| auto* mock_reader_writer = | ||
| new grpc::testing::MockClientReaderWriter<ExecutionRequest, ExecutionResponse>(); | ||
| EXPECT_CALL(*mock_reader_writer, Write(_, _)).WillOnce(Return(false)); | ||
| return mock_reader_writer; | ||
| }); | ||
|
|
||
| NighthawkServiceClientImpl client; | ||
| absl::StatusOr<ExecutionResponse> response_or = | ||
| client.PerformNighthawkBenchmark(&mock_nighthawk_service_stub, CommandLineOptions()); | ||
| ASSERT_FALSE(response_or.ok()); | ||
| EXPECT_EQ(response_or.status().code(), absl::StatusCode::kUnavailable); | ||
| EXPECT_THAT(response_or.status().message(), HasSubstr("Failed to write")); | ||
| } | ||
|
|
||
| TEST(PerformNighthawkBenchmark, ReturnsErrorIfNighthawkServiceWritesDoneFails) { | ||
| nighthawk::client::MockNighthawkServiceStub mock_nighthawk_service_stub; | ||
| // Configure the mock Nighthawk Service stub to return an inner mock channel when the code under | ||
| // test requests a channel. Set call expectations on the inner mock channel. | ||
| EXPECT_CALL(mock_nighthawk_service_stub, ExecutionStreamRaw) | ||
| .WillOnce([](grpc_impl::ClientContext*) { | ||
| auto* mock_reader_writer = | ||
| new grpc::testing::MockClientReaderWriter<ExecutionRequest, ExecutionResponse>(); | ||
| EXPECT_CALL(*mock_reader_writer, Write(_, _)).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, WritesDone()).WillOnce(Return(false)); | ||
| return mock_reader_writer; | ||
| }); | ||
|
|
||
| NighthawkServiceClientImpl client; | ||
| absl::StatusOr<ExecutionResponse> response_or = | ||
| client.PerformNighthawkBenchmark(&mock_nighthawk_service_stub, CommandLineOptions()); | ||
| ASSERT_FALSE(response_or.ok()); | ||
| EXPECT_EQ(response_or.status().code(), absl::StatusCode::kInternal); | ||
| EXPECT_THAT(response_or.status().message(), HasSubstr("WritesDone() failed")); | ||
| } | ||
|
|
||
| TEST(PerformNighthawkBenchmark, PropagatesErrorIfNighthawkServiceGrpcStreamClosesAbnormally) { | ||
| nighthawk::client::MockNighthawkServiceStub mock_nighthawk_service_stub; | ||
| // Configure the mock Nighthawk Service stub to return an inner mock channel when the code under | ||
| // test requests a channel. Set call expectations on the inner mock channel. | ||
| EXPECT_CALL(mock_nighthawk_service_stub, ExecutionStreamRaw) | ||
| .WillOnce([](grpc_impl::ClientContext*) { | ||
| auto* mock_reader_writer = | ||
| new grpc::testing::MockClientReaderWriter<ExecutionRequest, ExecutionResponse>(); | ||
| // PerformNighthawkBenchmark currently expects Read to return true exactly once. | ||
| EXPECT_CALL(*mock_reader_writer, Read(_)).WillOnce(Return(true)).WillOnce(Return(false)); | ||
| EXPECT_CALL(*mock_reader_writer, Write(_, _)).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, WritesDone()).WillOnce(Return(true)); | ||
| EXPECT_CALL(*mock_reader_writer, Finish()) | ||
| .WillOnce( | ||
| Return(::grpc::Status(::grpc::PERMISSION_DENIED, "Finish failure status message"))); | ||
| return mock_reader_writer; | ||
| }); | ||
|
|
||
| NighthawkServiceClientImpl client; | ||
| absl::StatusOr<ExecutionResponse> response_or = | ||
| client.PerformNighthawkBenchmark(&mock_nighthawk_service_stub, CommandLineOptions()); | ||
| ASSERT_FALSE(response_or.ok()); | ||
| EXPECT_EQ(response_or.status().code(), absl::StatusCode::kPermissionDenied); | ||
| EXPECT_THAT(response_or.status().message(), HasSubstr("Finish failure status message")); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace Nighthawk | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,7 @@ TO_CHECK="${2:-$PWD}" | |
| bazel run @envoy//tools:code_format/check_format.py -- \ | ||
| --skip_envoy_build_rule_check --namespace_check Nighthawk \ | ||
| --build_fixer_check_excluded_paths=$(realpath ".") \ | ||
| --include_dir_order envoy,nighthawk,external/source/envoy,external,api,common,source,exe,server,client,test_common,test \ | ||
| --include_dir_order envoy,nighthawk,external/source/envoy,external,api,common,source,exe,server,client,grpcpp,test_common,test \ | ||
|
Contributor
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. This
Member
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. Oh, that might have been a hassle to figure out, sorry about that. I wonder if we can improve there, ideally the "fix" doesn't produce something that the "check" objects to :-(
Contributor
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. This is definitely a quick fix. It seems to pass all the CI checks. I don't know how it's arriving at the order or how it's checking it. If we figure out how it works, this would no longer be necessary.
Member
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. I think it's the right tweak to address this. But I do find it's disappointing that check and fix don't line up when the order isn't explicitly specified. I'll file an issue to see if Envoy has progressed here, and if there are changes worth including in our version of this. |
||
| $1 $TO_CHECK | ||
|
|
||
| # The include checker doesn't support per-file checking, so we only | ||
|
|
||
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.
Two small asks, but on each of these tests.
Can you add a linebreak between this EXPECT_CALL and the CommandLineOptions below (or in other tests, whatever is following it)? Just helps the eyes parse this a little as one block.
Can we comment at the top of the EXPECT_CALL statement roughly what this block is mocking? Or if that seems like a bad way to document this, something else that helps clarify for future readers what is happening.
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.
Done