diff --git a/README.md b/README.md index 9f67244..767f179 100644 --- a/README.md +++ b/README.md @@ -524,7 +524,7 @@ Also, `st/repeat.sh` script repeats a previous execution (last by default) in he So you may start the process, again, natively or using docker: ```bash -$> OPTS=(--verbose --traffic-server-worker-threads 5 --prometheus-response-delay-seconds-histogram-boundaries "100e-6 200e-6 300e-6 400e-6 500e-6 1e-3 5e-3 10e-3 20e-3") +$> OPTS=(--verbose --traffic-server-worker-threads 5 --prometheus-response-delay-seconds-histogram-boundaries "100e-6,200e-6,300e-6,400e-6,1e-3,5e-3,10e-3,20e-3") $> build/Release/bin/h2agent "${OPTS[@]}" # native executable - or - $> docker run --rm -it --network=host -v $(pwd -P):$(pwd -P) ghcr.io/testillano/h2agent:latest "${OPTS[@]}" # docker @@ -792,14 +792,13 @@ Options: [--prometheus-port ] Prometheus local ; defaults to 8080. -[--prometheus-response-delay-seconds-histogram-boundaries ] +[--prometheus-response-delay-seconds-histogram-boundaries ] Bucket boundaries for response delay seconds histogram; no boundaries are defined by default. - Scientific notation is allowed, so in terms of microseconds (e-6) and milliseconds (e-3) we - could provide, for example: "100e-6 200e-6 300e-6 400e-6 500e-6 1e-3 5e-3 10e-3 20e-3". + Scientific notation is allowed, i.e.: "100e-6,200e-6,300e-6,400e-6,1e-3,5e-3,10e-3,20e-3". This affects to both mock server-data and client-data processing time values, but normally both flows will not be used together in the same process instance. -[--prometheus-message-size-bytes-histogram-boundaries ] +[--prometheus-message-size-bytes-histogram-boundaries ] Bucket boundaries for Rx/Tx message size bytes histogram; no boundaries are defined by default. This affects to both mock 'server internal/client external' message size values, but normally both flows will not be used together in the same process instance. @@ -1131,12 +1130,11 @@ To print accumulated statistics you can send UDP message 'STATS' or stop/interru [--prometheus-port ] Prometheus local ; defaults to 8081. Value of -1 disables metrics. -[--prometheus-response-delay-seconds-histogram-boundaries ] +[--prometheus-response-delay-seconds-histogram-boundaries ] Bucket boundaries for response delay seconds histogram; no boundaries are defined by default. - Scientific notation is allowed, so in terms of microseconds (e-6) and milliseconds (e-3) we - could provide, for example: "100e-6 200e-6 300e-6 400e-6 500e-6 1e-3 5e-3 10e-3 20e-3". + Scientific notation is allowed, i.e.: "100e-6,200e-6,300e-6,400e-6,1e-3,5e-3,10e-3,20e-3". -[--prometheus-message-size-bytes-histogram-boundaries ] +[--prometheus-message-size-bytes-histogram-boundaries ] Bucket boundaries for Tx/Rx message size bytes histogram; no boundaries are defined by default. [-h|--help] @@ -1199,9 +1197,8 @@ status codes: 3 2xx, 0 3xx, 0 4xx, 0 5xx, 0 timeouts, 0 connection errors ## Execution of udp-client utility This utility could be useful to test `udp-server`, and specially, `udp-server-h2client` tool. -You can also use netcat in bash, to generate messages easily, but this tool provide high load. -This tool manages a monotonically increasing sequence within a given range, and allow to parse -it over a pattern to build the datagram generated. +You can also use netcat in bash, to generate messages easily, but this tool provide high load. This tool manages a monotonically increasing sequence within a given range, and allow to parse it over a pattern to build the datagram generated. Even, we could provide a list of patterns which will be randomized. +Although we could launch multiple UDP clients towards the UDP server (such server must be unique due to non-oriented connection nature of UDP protocol), it is probably unnecessary: this client is fast enough to generate the required load. ### Command line @@ -1227,8 +1224,11 @@ Options: [-f|--final ] Final value for datagram. Defaults to unlimited. -[-p|--pattern ] +[--pattern ] Pattern to be parsed by sequence (@{seq} is replaced by sequence). Defaults to '@{seq}'. + This parameter can occur multiple times to create a random set. For example, passing + '--pattern foo --pattern foo --pattern bar', there is a probability of 2/3 to select + 'foo' and 1/3 to select 'bar'. [-e|--print-each ] Print messages each specific amount (must be positive). Defaults to 1. diff --git a/build-native.sh b/build-native.sh index 184351c..d5e1747 100755 --- a/build-native.sh +++ b/build-native.sh @@ -20,7 +20,7 @@ ert_queuedispatcher_ver=v1.0.3 jupp0r_prometheuscpp_ver=v0.13.0 civetweb_civetweb_ver=v1.14 ert_metrics_ver=v1.0.3 -ert_http2comm_ver=v2.1.2 +ert_http2comm_ver=v2.1.3 nlohmann_json_ver=$(grep ^nlohmann_json_ver__dflt= ${REPO_DIR}/build.sh | cut -d= -f2) pboettch_jsonschemavalidator_ver=$(grep ^pboettch_jsonschemavalidator_ver__dflt= ${REPO_DIR}/build.sh | cut -d= -f2) google_test_ver=$(grep ^google_test_ver__dflt= ${REPO_DIR}/build.sh | cut -d= -f2) diff --git a/src/main.cpp b/src/main.cpp index 608d59c..29e5fef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -95,27 +95,40 @@ const char* AdminApiVersion = "v1"; // Auxiliary functions // ///////////////////////// -// Transform input in the form " .. " to bucket boundaries vector -// Cientific notation is allowed, for example boundaries for 150us would be 150e-6 +// Transform input in the form ",,..," to bucket boundaries vector +// Cientific notation is allowed, for example boundary for 150us would be 150e-6 // Returns the final string ignoring non-double values scanned. Also sort is applied. std::string loadHistogramBoundaries(const std::string &input, ert::metrics::bucket_boundaries_t &boundaries) { std::string result; - std::stringstream ss(input); - double value = 0; - while (ss >> value) { - boundaries.push_back(value); + std::istringstream ss(input); + std::string item; + + while (std::getline(ss, item, ',')) { + try { + double value = std::stod(item); + if (value >= 0) { + boundaries.push_back(value); + result += (std::to_string(value) + ","); + } + else { + std::cerr << "Ignoring negative double: " << item << "\n"; + } + } catch (const std::invalid_argument& e) { + std::cerr << "Ignoring invalid double: " << item << "\n"; + } catch (const std::out_of_range& e) { + std::cerr << "Ignoring out-of-range double: " << item << "\n"; + } } + // Sort surviving numbers: std::sort(boundaries.begin(), boundaries.end()); - for (const auto &i: boundaries) { - result += (std::to_string(i) + " "); - } + + result.pop_back(); // remove last comma return result; } - /* int getThreadCount() { char buf[512]; @@ -359,14 +372,13 @@ void usage(int rc, const std::string &errorMessage = "") << "[--prometheus-port ]\n" << " Prometheus local ; defaults to 8080.\n\n" - << "[--prometheus-response-delay-seconds-histogram-boundaries ]\n" + << "[--prometheus-response-delay-seconds-histogram-boundaries ]\n" << " Bucket boundaries for response delay seconds histogram; no boundaries are defined by default.\n" - << " Scientific notation is allowed, so in terms of microseconds (e-6) and milliseconds (e-3) we\n" - << " could provide, for example: \"100e-6 200e-6 300e-6 400e-6 500e-6 1e-3 5e-3 10e-3 20e-3\".\n" + << " Scientific notation is allowed, i.e.: \"100e-6,200e-6,300e-6,400e-6,1e-3,5e-3,10e-3,20e-3\".\n" << " This affects to both mock server-data and client-data processing time values,\n" << " but normally both flows will not be used together in the same process instance.\n\n" - << "[--prometheus-message-size-bytes-histogram-boundaries ]\n" + << "[--prometheus-message-size-bytes-histogram-boundaries ]\n" << " Bucket boundaries for Rx/Tx message size bytes histogram; no boundaries are defined by default.\n" << " This affects to both mock 'server internal/client external' message size values,\n" << " but normally both flows will not be used together in the same process instance.\n\n" @@ -717,6 +729,8 @@ int main(int argc, char* argv[]) bool hasPEMpasswordPrompt = (admin_secured && traffic_secured && traffic_server_key_password.empty()); ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + std::cout << '\n'; + std::cout << currentDateTime() << ": Starting " << progname << " " << (gitVersion.empty() ? "":gitVersion) << '\n'; std::cout << "Log level: " << ert::tracing::Logger::levelAsString(ert::tracing::Logger::getLevel()) << '\n'; std::cout << "Verbose (stdout): " << (verbose ? "true":"false") << '\n'; diff --git a/src/model/Transformation.cpp b/src/model/Transformation.cpp index 9e9d37f..ba68b45 100644 --- a/src/model/Transformation.cpp +++ b/src/model/Transformation.cpp @@ -623,9 +623,7 @@ std::string Transformation::asString() const { else if (source_type_ == SourceType::RandomSet || source_type_ == SourceType::ServerEvent) { ss << " | source_tokenized_:"; for(auto it: source_tokenized_) { - ss<< " '" ; - ss << it; - ss << "'"; + ss << " '" << it << "'"; } } else if (source_type_ == SourceType::STxtFile || source_type_ == SourceType::SBinFile) { diff --git a/tools/udp-client/main.cpp b/tools/udp-client/main.cpp index 0c0c9bf..d642b67 100644 --- a/tools/udp-client/main.cpp +++ b/tools/udp-client/main.cpp @@ -87,8 +87,11 @@ void usage(int rc, const std::string &errorMessage = "") << "[-f|--final ]\n" << " Final value for datagram. Defaults to unlimited.\n\n" - << "[-p|--pattern ]\n" - << " Pattern to be parsed by sequence (@{seq} is replaced by sequence). Defaults to '@{seq}'.\n\n" + << "[--pattern ]\n" + << " Pattern to be parsed by sequence (@{seq} is replaced by sequence). Defaults to '@{seq}'.\n" + << " This parameter can occur multiple times to create a random set. For example, passing\n" + << " '--pattern foo --pattern foo --pattern bar', there is a probability of 2/3 to select\n" + << " 'foo' and 1/3 to select 'bar'.\n\n" << "[-e|--print-each ]\n" << " Print messages each specific amount (must be positive). Defaults to 1.\n\n" @@ -144,18 +147,22 @@ double toDouble(const std::string& value) return result; } -bool cmdOptionExists(char** begin, char** end, const std::string& option, - std::string& value) +char **cmdOptionExists(char** begin, char** end, const std::string& option, std::string& value) { - char** itr = std::find(begin, end, option); - bool exists = (itr != end); + char** result = std::find(begin, end, option); + bool exists = (result != end); - if (exists && ++itr != end) - { - value = *itr; + if (exists) { + if (++result != end) + { + value = *result; + } + } + else { + result = nullptr; } - return exists; + return result; } void sighndl(int signal) @@ -173,6 +180,7 @@ void sighndl(int signal) int main(int argc, char* argv[]) { + srand(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()); progname = basename(argv[0]); // Parse command-line /////////////////////////////////////////////////////////////////////////////////////// @@ -180,7 +188,7 @@ int main(int argc, char* argv[]) std::string udpSocketPath{}; unsigned long long int initialValue{}; unsigned long long int finalValue = std::numeric_limits::max(); - std::string pattern = "@{seq}"; + std::vector patterns{}; double eps = 1.0; std::string value; @@ -215,10 +223,10 @@ int main(int argc, char* argv[]) finalValue = toLong(value); } - if (cmdOptionExists(argv, argv + argc, "-p", value) - || cmdOptionExists(argv, argv + argc, "--pattern", value)) + char **next = argv; + while ((next = cmdOptionExists(next, argv + argc, "--pattern", value))) { - pattern = value; + patterns.push_back(value); } if (cmdOptionExists(argv, argv + argc, "-e", value) @@ -232,11 +240,21 @@ int main(int argc, char* argv[]) std::cout << '\n'; if (udpSocketPath.empty()) usage(EXIT_FAILURE); + if (patterns.empty()) patterns.push_back("@{seq}"); // default if no --pattern parameter is provided std::cout << "Path: " << udpSocketPath << '\n'; std::cout << "Print each: " << i_printEach << " message(s)\n"; std::cout << "Range: [" << initialValue << ", " << finalValue << "]\n"; - std::cout << "Pattern: " << pattern << "\n"; + if (patterns.size() == 1) { + std::cout << "Pattern: " << patterns[0] << "\n"; + } + else { + std::cout << "Patterns (random subset): "; + for(auto it: patterns) { + std::cout << " '" << it << "'"; + } + std::cout << '\n'; + } std::cout << "Events per second: "; if (eps > 0) std::cout << eps; else std::cout << "unlimited"; @@ -276,11 +294,19 @@ int main(int argc, char* argv[]) unsigned long long int sequence{}; auto startTimeNS = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + int patternsSize = patterns.size(); while (true) { udpDataSeq = std::to_string(initialValue + sequence); - udpData = pattern; + + if (patternsSize == 1) { + udpData = udpData = patterns[0]; + } + else { // random subset + udpData = patterns[rand () % patternsSize]; + } + // search/replace @{seq} by 'udpDataSeq': pos = 0u; while((pos = udpData.find(from, pos)) != std::string::npos) { diff --git a/tools/udp-server-h2client/main.cpp b/tools/udp-server-h2client/main.cpp index d8bbebf..82645e2 100644 --- a/tools/udp-server-h2client/main.cpp +++ b/tools/udp-server-h2client/main.cpp @@ -160,12 +160,11 @@ void usage(int rc, const std::string &errorMessage = "") << "[--prometheus-port ]\n" << " Prometheus local ; defaults to 8081. Value of -1 disables metrics.\n\n" - << "[--prometheus-response-delay-seconds-histogram-boundaries ]\n" + << "[--prometheus-response-delay-seconds-histogram-boundaries ]\n" << " Bucket boundaries for response delay seconds histogram; no boundaries are defined by default.\n" - << " Scientific notation is allowed, so in terms of microseconds (e-6) and milliseconds (e-3) we\n" - << " could provide, for example: \"100e-6 200e-6 300e-6 400e-6 500e-6 1e-3 5e-3 10e-3 20e-3\".\n\n" + << " Scientific notation is allowed, i.e.: \"100e-6,200e-6,300e-6,400e-6,1e-3,5e-3,10e-3,20e-3\".\n\n" - << "[--prometheus-message-size-bytes-histogram-boundaries ]\n" + << "[--prometheus-message-size-bytes-histogram-boundaries ]\n" << " Bucket boundaries for Tx/Rx message size bytes histogram; no boundaries are defined by default.\n\n" << "[-h|--help]\n" @@ -309,22 +308,36 @@ void replaceVariables(std::string &str, const std::map } } -// Transform input in the form " .. " to bucket boundaries vector -// Cientific notation is allowed, for example boundaries for 150us would be 150e-6 +// Transform input in the form ",,..," to bucket boundaries vector +// Cientific notation is allowed, for example boundary for 150us would be 150e-6 // Returns the final string ignoring non-double values scanned. Also sort is applied. std::string loadHistogramBoundaries(const std::string &input, ert::metrics::bucket_boundaries_t &boundaries) { std::string result; - std::stringstream ss(input); - double value = 0; - while (ss >> value) { - boundaries.push_back(value); + std::istringstream ss(input); + std::string item; + + while (std::getline(ss, item, ',')) { + try { + double value = std::stod(item); + if (value >= 0) { + boundaries.push_back(value); + result += (std::to_string(value) + ","); + } + else { + std::cerr << "Ignoring negative double: " << item << "\n"; + } + } catch (const std::invalid_argument& e) { + std::cerr << "Ignoring invalid double: " << item << "\n"; + } catch (const std::out_of_range& e) { + std::cerr << "Ignoring out-of-range double: " << item << "\n"; + } } + // Sort surviving numbers: std::sort(boundaries.begin(), boundaries.end()); - for (const auto &i: boundaries) { - result += (std::to_string(i) + " "); - } + + result.pop_back(); // remove last comma return result; } @@ -824,7 +837,7 @@ int main(int argc, char* argv[]) } /* - // WITHOUT DELAY FEATURE: + // WITHOUT DELAY FEATURE (also this could cause submit error due to method/path/body not protected in this thread): boost::asio::post(io_ctx, [&]() { auto stream = std::make_shared(udpData); stream->setRequest(client, method, path, body, headers, millisecondsTimeout);