Skip to content

Commit

Permalink
Adaptive Load command line client (#616)
Browse files Browse the repository at this point in the history
An adaptive load command line executable that drives a Nighthawk Service with a series of benchmarks, varying the RPS or another input variable dynamically.

`bazel build -c opt :nighthawk`

`bazel-bin/nighthawk_adaptive_load_client`

- `--spec-file` _pathname_
  - Pathname of an `AdaptiveLoadSessionSpec` textproto defining the session
  - Required
  - Example:`test/adaptive_load/test_data/valid_session_spec.textproto`
- `--output-file` _pathname_ 
  - Pathname to write the `AdaptiveLoadSessionOutput` textproto session output
  - Required
- `--nighthawk-service-address` _host:port_ 
  - Address of the Nighthawk Service to be driven by the adaptive load controller over gRPC
  - Default `localhost:8443`
- `--use-tls`
  - Use TLS for the gRPC connection when sending commands to the Nighthawk Service
  - Default false (insecure connection)

Note that to run an adaptive load session, you must also have a Nighthawk Service running, e.g.:
`bazel-bin/nighthawk_service &`  (to run at localhost:8443)

Part of #416.

Signed-off-by: eric846 <[email protected]>
  • Loading branch information
eric846 authored Feb 11, 2021
1 parent 10b2a8e commit ff42695
Show file tree
Hide file tree
Showing 12 changed files with 799 additions and 1 deletion.
9 changes: 9 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ envoy_package()
filegroup(
name = "nighthawk",
srcs = [
":nighthawk_adaptive_load_client",
":nighthawk_client",
":nighthawk_output_transform",
":nighthawk_service",
":nighthawk_test_server",
],
)

envoy_cc_binary(
name = "nighthawk_adaptive_load_client",
repository = "@envoy",
deps = [
"//source/exe:adaptive_load_client_entry_lib",
],
)

envoy_cc_binary(
name = "nighthawk_client",
repository = "@envoy",
Expand Down
2 changes: 1 addition & 1 deletion api/adaptive_load/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ api_cc_py_proto_library(
],
visibility = ["//visibility:public"],
deps = [
"//api/client:base",
"@envoy_api//envoy/config/core/v3:pkg",
"@nighthawk//api/client:base",
],
)
29 changes: 29 additions & 0 deletions source/adaptive_load/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,35 @@ envoy_cc_library(
],
)

envoy_cc_library(
name = "adaptive_load_client_main",
srcs = [
"adaptive_load_client_main.cc",
],
hdrs = [
"adaptive_load_client_main.h",
],
repository = "@envoy",
visibility = ["//visibility:public"],
deps = [
":adaptive_load_controller_impl",
":config_validator_impl",
":input_variable_setter_impl",
":metrics_evaluator_impl",
":metrics_plugin_impl",
":scoring_function_impl",
":session_spec_proto_helper_impl",
":step_controller_impl",
"//api/client:base_cc_proto",
"//api/client:grpc_service_lib",
"//source/common:nighthawk_common_lib",
"@envoy//source/common/common:assert_lib_with_external_headers",
"@envoy//source/common/common:minimal_logger_lib_with_external_headers",
"@envoy//source/common/event:real_time_system_lib_with_external_headers",
"@envoy//source/common/grpc:google_grpc_utils_lib_with_external_headers",
],
)

envoy_cc_library(
name = "config_validator_impl",
srcs = [
Expand Down
137 changes: 137 additions & 0 deletions source/adaptive_load/adaptive_load_client_main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#include "adaptive_load/adaptive_load_client_main.h"

#include <cstring>
#include <memory>
#include <string>
#include <utility>

#include "envoy/common/exception.h"

#include "nighthawk/adaptive_load/adaptive_load_controller.h"
#include "nighthawk/common/exception.h"

#include "external/envoy/source/common/grpc/google_grpc_utils.h"
#include "external/envoy/source/common/protobuf/protobuf.h"

#include "api/adaptive_load/adaptive_load.pb.h"
#include "api/client/service.grpc.pb.h"
#include "api/client/service.pb.h"

#include "common/utility.h"
#include "common/version_info.h"

#include "fmt/ranges.h"
#include "google/rpc/status.pb.h"
#include "tclap/CmdLine.h"

namespace Nighthawk {

namespace {

/**
* Writes a string to a file.
*
* @param filesystem Envoy abstraction around filesystem functions, to facilitate unit testing.
* @param path Relative or absolute path to the file to write.
* @param contents String to write to the file.
*
* @throw Nighthawk::NighthawkException For any filesystem error.
*/
void WriteFileOrThrow(Envoy::Filesystem::Instance& filesystem, absl::string_view path,
absl::string_view contents) {
Envoy::Filesystem::FilePtr file = filesystem.createFile(std::string(path));
const Envoy::Api::IoCallBoolResult open_result =
file->open(((1 << Envoy::Filesystem::File::Operation::Write)) |
(1 << (Envoy::Filesystem::File::Operation::Create)));
if (!open_result.ok()) {
throw Nighthawk::NighthawkException(absl::StrCat("Unable to open output file \"", path,
"\": ", open_result.err_->getErrorDetails()));
}
const Envoy::Api::IoCallSizeResult write_result = file->write(contents);
if (!write_result.ok()) {
throw Nighthawk::NighthawkException(absl::StrCat("Unable to write to output file \"", path,
"\": ", write_result.err_->getErrorDetails()));
}
const Envoy::Api::IoCallBoolResult close_result = file->close();
if (!close_result.ok()) {
throw Nighthawk::NighthawkException(absl::StrCat("Unable to close output file \"", path,
"\": ", close_result.err_->getErrorDetails()));
}
}

} // namespace

AdaptiveLoadClientMain::AdaptiveLoadClientMain(int argc, const char* const* argv,
AdaptiveLoadController& controller,
Envoy::Filesystem::Instance& filesystem)
: controller_{controller}, filesystem_{filesystem} {
TCLAP::CmdLine cmd("Adaptive Load tool that finds the optimal load on the target "
"through a series of Nighthawk Service benchmarks.",
/*delimiter=*/' ', VersionInfo::version());

TCLAP::ValueArg<std::string> nighthawk_service_address(
/*flag_name=*/"", "nighthawk-service-address",
"host:port for Nighthawk Service. To enable TLS, set --use-tls.",
/*required=*/false, "localhost:8443", "string", cmd);
TCLAP::SwitchArg use_tls(
/*flag_name=*/"", "use-tls",
"Use TLS for the gRPC connection from this program to the Nighthawk Service. Set environment "
"variable GRPC_DEFAULT_SSL_ROOTS_FILE_PATH to override the default root certificates.",
cmd);
TCLAP::ValueArg<std::string> spec_filename(
/*flag_name=*/"", "spec-file",
"Path to a textproto file describing the adaptive load session "
"(nighthawk::adaptive_load::AdaptiveLoadSessionSpec).",
/*required=*/true, /*default_value=*/"", "string", cmd);
TCLAP::ValueArg<std::string> output_filename(
/*flag_name=*/"", "output-file",
"Path to write adaptive load session output textproto "
"(nighthawk::adaptive_load::AdaptiveLoadSessionOutput).",
/*required=*/true, /*default_value=*/"", "string", cmd);

Nighthawk::Utility::parseCommand(cmd, argc, argv);

nighthawk_service_address_ = nighthawk_service_address.getValue();
use_tls_ = use_tls.getValue();
spec_filename_ = spec_filename.getValue();
output_filename_ = output_filename.getValue();
}

uint32_t AdaptiveLoadClientMain::Run() {
ENVOY_LOG(info, "Attempting adaptive load session: {}", DescribeInputs());
std::string spec_textproto;
try {
spec_textproto = filesystem_.fileReadToEnd(spec_filename_);
} catch (const Envoy::EnvoyException& e) {
throw Nighthawk::NighthawkException("Failed to read spec textproto file \"" + spec_filename_ +
"\": " + e.what());
}
nighthawk::adaptive_load::AdaptiveLoadSessionSpec spec;
if (!Envoy::Protobuf::TextFormat::ParseFromString(spec_textproto, &spec)) {
throw Nighthawk::NighthawkException("Unable to parse file \"" + spec_filename_ +
"\" as a text protobuf (type " + spec.GetTypeName() + ")");
}
std::shared_ptr<::grpc::Channel> channel = grpc::CreateChannel(
nighthawk_service_address_, use_tls_ ? grpc::SslCredentials(grpc::SslCredentialsOptions())
: grpc::InsecureChannelCredentials());
std::unique_ptr<nighthawk::client::NighthawkService::StubInterface> stub(
nighthawk::client::NighthawkService::NewStub(channel));

absl::StatusOr<nighthawk::adaptive_load::AdaptiveLoadSessionOutput> output_or =
controller_.PerformAdaptiveLoadSession(stub.get(), spec);
if (!output_or.ok()) {
ENVOY_LOG(error, "Error in adaptive load session: {}", output_or.status().message());
return 1;
}
nighthawk::adaptive_load::AdaptiveLoadSessionOutput output = output_or.value();
WriteFileOrThrow(filesystem_, output_filename_, output.DebugString());
return 0;
}

std::string AdaptiveLoadClientMain::DescribeInputs() {
return "Nighthawk Service " + nighthawk_service_address_ + " using " +
(use_tls_ ? "TLS" : "insecure") + " connection, input file: " + spec_filename_ +
", output file: " + output_filename_;
}

} // namespace Nighthawk
55 changes: 55 additions & 0 deletions source/adaptive_load/adaptive_load_client_main.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#pragma once

#include "envoy/common/time.h"
#include "envoy/filesystem/filesystem.h"

#include "nighthawk/adaptive_load/adaptive_load_controller.h"

#include "external/envoy/source/common/common/logger.h"

namespace Nighthawk {

/**
* Main implementation of the CLI wrapper around the adaptive load controller library.
* Parses command line options, reads adaptive load session spec proto from a file,
* runs an adaptive load session, and writes the output proto to a file.
*/
class AdaptiveLoadClientMain : public Envoy::Logger::Loggable<Envoy::Logger::Id::main> {
public:
/**
* Parses the command line arguments to class members.
*
* @param argc Standard argc passed through from the exe entry point.
* @param argv Standard argv passed through from the exe entry point.
* @param controller Adaptive load controller, passed in to allow unit testing of this class.
* @param filesystem Abstraction of the filesystem, passed in to allow unit testing of this
* class.
*
* @throw Nighthawk::Client::MalformedArgvException If command line constraints are violated.
*/
AdaptiveLoadClientMain(int argc, const char* const* argv, AdaptiveLoadController& controller,
Envoy::Filesystem::Instance& filesystem);
/**
* Loads the adaptive load session spec proto from a file, runs an adaptive load session, and
* writes the output proto to a file. File paths are taken from class members initialized in the
* constructor.
*
* @return Exit code for this process.
* @throw Nighthawk::NighthawkException If a file read or write error occurs.
*/
uint32_t Run();
/**
* Describes the program inputs as parsed from the command line.
*/
std::string DescribeInputs();

private:
std::string nighthawk_service_address_;
bool use_tls_;
std::string spec_filename_;
std::string output_filename_;
AdaptiveLoadController& controller_;
Envoy::Filesystem::Instance& filesystem_;
};

} // namespace Nighthawk
17 changes: 17 additions & 0 deletions source/exe/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ licenses(["notice"]) # Apache 2

envoy_package()

envoy_cc_library(
name = "adaptive_load_client_entry_lib",
srcs = ["adaptive_load_client_main_entry.cc"],
external_deps = [
"abseil_symbolize",
],
repository = "@envoy",
visibility = ["//visibility:public"],
deps = [
"//source/adaptive_load:adaptive_load_client_main",
"//source/common:nighthawk_service_client_impl",
"//source/common:version_linkstamp",
"@envoy//source/exe:platform_header_lib_with_external_headers",
"@envoy//source/exe:platform_impl_lib",
],
)

envoy_cc_library(
name = "nighthawk_client_entry_lib",
srcs = ["client_main_entry.cc"],
Expand Down
45 changes: 45 additions & 0 deletions source/exe/adaptive_load_client_main_entry.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Command line adaptive load controller driving a Nighthawk Service.
#include <iostream>

#include "nighthawk/common/exception.h"

#include "external/envoy/source/common/event/real_time_system.h"
#include "external/envoy/source/exe/platform_impl.h"

#include "common/nighthawk_service_client_impl.h"

#include "absl/debugging/symbolize.h"
#include "adaptive_load/adaptive_load_client_main.h"
#include "adaptive_load/adaptive_load_controller_impl.h"
#include "adaptive_load/metrics_evaluator_impl.h"
#include "adaptive_load/session_spec_proto_helper_impl.h"

// NOLINT(namespace-nighthawk)

int main(int argc, char* argv[]) {
#ifndef __APPLE__
// absl::Symbolize mostly works without this, but this improves corner case
// handling, such as running in a chroot jail.
absl::InitializeSymbolizer(argv[0]);
#endif
Nighthawk::NighthawkServiceClientImpl nighthawk_service_client;
Nighthawk::MetricsEvaluatorImpl metrics_evaluator;
Nighthawk::AdaptiveLoadSessionSpecProtoHelperImpl spec_proto_helper;
Envoy::Event::RealTimeSystem time_system; // NO_CHECK_FORMAT(real_time)
Nighthawk::AdaptiveLoadControllerImpl controller(nighthawk_service_client, metrics_evaluator,
spec_proto_helper, time_system);
Envoy::PlatformImpl platform_impl;
try {
Nighthawk::AdaptiveLoadClientMain program(argc, argv, controller, platform_impl.fileSystem());
return program.Run();
} catch (const Nighthawk::Client::NoServingException& e) {
return EXIT_SUCCESS;
} catch (const Nighthawk::Client::MalformedArgvException& e) {
std::cerr << "Invalid args: " << e.what() << std::endl;
return EXIT_FAILURE;
} catch (const Nighthawk::NighthawkException& e) {
std::cerr << "Failure: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return 0;
}
22 changes: 22 additions & 0 deletions test/adaptive_load/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ envoy_cc_test_library(
],
)

envoy_cc_test(
name = "adaptive_load_client_main_test",
srcs = ["adaptive_load_client_main_test.cc"],
data = [
"test_data/golden_output.textproto",
"test_data/invalid_session_spec.textproto",
"test_data/valid_session_spec.textproto",
],
repository = "@envoy",
deps = [
":minimal_output",
"//include/nighthawk/adaptive_load:adaptive_load_controller",
"//source/adaptive_load:adaptive_load_client_main",
"//test/mocks/adaptive_load:mock_adaptive_load_controller",
"//test/test_common:environment_lib",
"@com_github_grpc_grpc//:grpc++_test",
"@envoy//source/common/protobuf:utility_lib_with_external_headers",
"@envoy//test/mocks/filesystem:filesystem_mocks",
"@envoy//test/test_common:utility_lib",
],
)

envoy_cc_test(
name = "adaptive_load_controller_test",
srcs = ["adaptive_load_controller_test.cc"],
Expand Down
Loading

0 comments on commit ff42695

Please sign in to comment.