qps_json_driver.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. /*
  2. *
  3. * Copyright 2015-2016 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. #include <fstream>
  19. #include <iostream>
  20. #include <memory>
  21. #include <set>
  22. #include "absl/flags/flag.h"
  23. #include <grpc/support/log.h>
  24. #include <grpcpp/impl/codegen/config_protobuf.h>
  25. #include "test/core/util/test_config.h"
  26. #include "test/cpp/qps/benchmark_config.h"
  27. #include "test/cpp/qps/driver.h"
  28. #include "test/cpp/qps/parse_json.h"
  29. #include "test/cpp/qps/report.h"
  30. #include "test/cpp/qps/server.h"
  31. #include "test/cpp/util/test_config.h"
  32. #include "test/cpp/util/test_credentials_provider.h"
  33. ABSL_FLAG(std::string, scenarios_file, "",
  34. "JSON file containing an array of Scenario objects");
  35. ABSL_FLAG(std::string, scenarios_json, "",
  36. "JSON string containing an array of Scenario objects");
  37. ABSL_FLAG(bool, quit, false, "Quit the workers");
  38. ABSL_FLAG(std::string, search_param, "",
  39. "The parameter, whose value is to be searched for to achieve "
  40. "targeted cpu load. For now, we have 'offered_load'. Later, "
  41. "'num_channels', 'num_outstanding_requests', etc. shall be "
  42. "added.");
  43. ABSL_FLAG(
  44. double, initial_search_value, 0.0,
  45. "initial parameter value to start the search with (i.e. lower bound)");
  46. ABSL_FLAG(double, targeted_cpu_load, 70.0,
  47. "Targeted cpu load (unit: %, range [0,100])");
  48. ABSL_FLAG(double, stride, 1,
  49. "Defines each stride of the search. The larger the stride is, "
  50. "the coarser the result will be, but will also be faster.");
  51. ABSL_FLAG(double, error_tolerance, 0.01,
  52. "Defines threshold for stopping the search. When current search "
  53. "range is narrower than the error_tolerance computed range, we "
  54. "stop the search.");
  55. ABSL_FLAG(std::string, qps_server_target_override, "",
  56. "Override QPS server target to configure in client configs."
  57. "Only applicable if there is a single benchmark server.");
  58. ABSL_FLAG(std::string, json_file_out, "", "File to write the JSON output to.");
  59. ABSL_FLAG(std::string, credential_type, grpc::testing::kInsecureCredentialsType,
  60. "Credential type for communication with workers");
  61. ABSL_FLAG(
  62. std::string, per_worker_credential_types, "",
  63. "A map of QPS worker addresses to credential types. When creating a "
  64. "channel to a QPS worker's driver port, the qps_json_driver first checks "
  65. "if the 'name:port' string is in the map, and it uses the corresponding "
  66. "credential type if so. If the QPS worker's 'name:port' string is not "
  67. "in the map, then the driver -> worker channel will be created with "
  68. "the credentials specified in --credential_type. The value of this flag "
  69. "is a semicolon-separated list of map entries, where each map entry is "
  70. "a comma-separated pair.");
  71. ABSL_FLAG(bool, run_inproc, false, "Perform an in-process transport test");
  72. ABSL_FLAG(
  73. int32_t, median_latency_collection_interval_millis, 0,
  74. "Specifies the period between gathering latency medians in "
  75. "milliseconds. The medians will be logged out on the client at the "
  76. "end of the benchmark run. If 0, this periodic collection is disabled.");
  77. namespace grpc {
  78. namespace testing {
  79. static std::map<std::string, std::string>
  80. ConstructPerWorkerCredentialTypesMap() {
  81. // Parse a list of the form: "addr1,cred_type1;addr2,cred_type2;..." into
  82. // a map.
  83. std::string remaining = absl::GetFlag(FLAGS_per_worker_credential_types);
  84. std::map<std::string, std::string> out;
  85. while (!remaining.empty()) {
  86. size_t next_semicolon = remaining.find(';');
  87. std::string next_entry = remaining.substr(0, next_semicolon);
  88. if (next_semicolon == std::string::npos) {
  89. remaining = "";
  90. } else {
  91. remaining = remaining.substr(next_semicolon + 1, std::string::npos);
  92. }
  93. size_t comma = next_entry.find(',');
  94. if (comma == std::string::npos) {
  95. gpr_log(GPR_ERROR,
  96. "Expectd --per_worker_credential_types to be a list "
  97. "of the form: 'addr1,cred_type1;addr2,cred_type2;...' "
  98. "into.");
  99. abort();
  100. }
  101. std::string addr = next_entry.substr(0, comma);
  102. std::string cred_type = next_entry.substr(comma + 1, std::string::npos);
  103. if (out.find(addr) != out.end()) {
  104. gpr_log(GPR_ERROR,
  105. "Found duplicate addr in per_worker_credential_types.");
  106. abort();
  107. }
  108. out[addr] = cred_type;
  109. }
  110. return out;
  111. }
  112. static std::unique_ptr<ScenarioResult> RunAndReport(
  113. const Scenario& scenario,
  114. const std::map<std::string, std::string>& per_worker_credential_types,
  115. bool* success) {
  116. std::cerr << "RUNNING SCENARIO: " << scenario.name() << "\n";
  117. auto result = RunScenario(
  118. scenario.client_config(), scenario.num_clients(),
  119. scenario.server_config(), scenario.num_servers(),
  120. scenario.warmup_seconds(), scenario.benchmark_seconds(),
  121. !absl::GetFlag(FLAGS_run_inproc) ? scenario.spawn_local_worker_count()
  122. : -2,
  123. absl::GetFlag(FLAGS_qps_server_target_override),
  124. absl::GetFlag(FLAGS_credential_type), per_worker_credential_types,
  125. absl::GetFlag(FLAGS_run_inproc),
  126. absl::GetFlag(FLAGS_median_latency_collection_interval_millis));
  127. // Amend the result with scenario config. Eventually we should adjust
  128. // RunScenario contract so we don't need to touch the result here.
  129. result->mutable_scenario()->CopyFrom(scenario);
  130. GetReporter()->ReportQPS(*result);
  131. GetReporter()->ReportQPSPerCore(*result);
  132. GetReporter()->ReportLatency(*result);
  133. GetReporter()->ReportTimes(*result);
  134. GetReporter()->ReportCpuUsage(*result);
  135. GetReporter()->ReportPollCount(*result);
  136. GetReporter()->ReportQueriesPerCpuSec(*result);
  137. for (int i = 0; *success && i < result->client_success_size(); i++) {
  138. *success = result->client_success(i);
  139. }
  140. for (int i = 0; *success && i < result->server_success_size(); i++) {
  141. *success = result->server_success(i);
  142. }
  143. if (!absl::GetFlag(FLAGS_json_file_out).empty()) {
  144. std::ofstream json_outfile;
  145. json_outfile.open(absl::GetFlag(FLAGS_json_file_out));
  146. json_outfile << "{\"qps\": " << result->summary().qps() << "}\n";
  147. json_outfile.close();
  148. }
  149. return result;
  150. }
  151. static double GetCpuLoad(
  152. Scenario* scenario, double offered_load,
  153. const std::map<std::string, std::string>& per_worker_credential_types,
  154. bool* success) {
  155. scenario->mutable_client_config()
  156. ->mutable_load_params()
  157. ->mutable_poisson()
  158. ->set_offered_load(offered_load);
  159. auto result = RunAndReport(*scenario, per_worker_credential_types, success);
  160. return result->summary().server_cpu_usage();
  161. }
  162. static double BinarySearch(
  163. Scenario* scenario, double targeted_cpu_load, double low, double high,
  164. const std::map<std::string, std::string>& per_worker_credential_types,
  165. bool* success) {
  166. while (low <= high * (1 - absl::GetFlag(FLAGS_error_tolerance))) {
  167. double mid = low + (high - low) / 2;
  168. double current_cpu_load =
  169. GetCpuLoad(scenario, mid, per_worker_credential_types, success);
  170. gpr_log(GPR_DEBUG, "Binary Search: current_offered_load %.0f", mid);
  171. if (!*success) {
  172. gpr_log(GPR_ERROR, "Client/Server Failure");
  173. break;
  174. }
  175. if (targeted_cpu_load <= current_cpu_load) {
  176. high = mid - absl::GetFlag(FLAGS_stride);
  177. } else {
  178. low = mid + absl::GetFlag(FLAGS_stride);
  179. }
  180. }
  181. return low;
  182. }
  183. static double SearchOfferedLoad(
  184. double initial_offered_load, double targeted_cpu_load, Scenario* scenario,
  185. const std::map<std::string, std::string>& per_worker_credential_types,
  186. bool* success) {
  187. std::cerr << "RUNNING SCENARIO: " << scenario->name() << "\n";
  188. double current_offered_load = initial_offered_load;
  189. double current_cpu_load = GetCpuLoad(scenario, current_offered_load,
  190. per_worker_credential_types, success);
  191. if (current_cpu_load > targeted_cpu_load) {
  192. gpr_log(GPR_ERROR, "Initial offered load too high");
  193. return -1;
  194. }
  195. while (*success && (current_cpu_load < targeted_cpu_load)) {
  196. current_offered_load *= 2;
  197. current_cpu_load = GetCpuLoad(scenario, current_offered_load,
  198. per_worker_credential_types, success);
  199. gpr_log(GPR_DEBUG, "Binary Search: current_offered_load %.0f",
  200. current_offered_load);
  201. }
  202. double targeted_offered_load =
  203. BinarySearch(scenario, targeted_cpu_load, current_offered_load / 2,
  204. current_offered_load, per_worker_credential_types, success);
  205. return targeted_offered_load;
  206. }
  207. static bool QpsDriver() {
  208. std::string json;
  209. bool scfile = (!absl::GetFlag(FLAGS_scenarios_file).empty());
  210. bool scjson = (!absl::GetFlag(FLAGS_scenarios_json).empty());
  211. if ((!scfile && !scjson && !absl::GetFlag(FLAGS_quit)) ||
  212. (scfile && (scjson || absl::GetFlag(FLAGS_quit))) ||
  213. (scjson && absl::GetFlag(FLAGS_quit))) {
  214. gpr_log(GPR_ERROR,
  215. "Exactly one of --scenarios_file, --scenarios_json, "
  216. "or --quit must be set");
  217. abort();
  218. }
  219. auto per_worker_credential_types = ConstructPerWorkerCredentialTypesMap();
  220. if (scfile) {
  221. // Read the json data from disk
  222. FILE* json_file = fopen(absl::GetFlag(FLAGS_scenarios_file).c_str(), "r");
  223. GPR_ASSERT(json_file != nullptr);
  224. fseek(json_file, 0, SEEK_END);
  225. long len = ftell(json_file);
  226. char* data = new char[len];
  227. fseek(json_file, 0, SEEK_SET);
  228. GPR_ASSERT(len == (long)fread(data, 1, len, json_file));
  229. fclose(json_file);
  230. json = std::string(data, data + len);
  231. delete[] data;
  232. } else if (scjson) {
  233. json = absl::GetFlag(FLAGS_scenarios_json).c_str();
  234. } else if (absl::GetFlag(FLAGS_quit)) {
  235. return RunQuit(absl::GetFlag(FLAGS_credential_type),
  236. per_worker_credential_types);
  237. }
  238. // Parse into an array of scenarios
  239. Scenarios scenarios;
  240. ParseJson(json.c_str(), "grpc.testing.Scenarios", &scenarios);
  241. bool success = true;
  242. // Make sure that there is at least some valid scenario here
  243. GPR_ASSERT(scenarios.scenarios_size() > 0);
  244. for (int i = 0; i < scenarios.scenarios_size(); i++) {
  245. if (absl::GetFlag(FLAGS_search_param).empty()) {
  246. const Scenario& scenario = scenarios.scenarios(i);
  247. RunAndReport(scenario, per_worker_credential_types, &success);
  248. } else {
  249. if (absl::GetFlag(FLAGS_search_param) == "offered_load") {
  250. Scenario* scenario = scenarios.mutable_scenarios(i);
  251. double targeted_offered_load =
  252. SearchOfferedLoad(absl::GetFlag(FLAGS_initial_search_value),
  253. absl::GetFlag(FLAGS_targeted_cpu_load), scenario,
  254. per_worker_credential_types, &success);
  255. gpr_log(GPR_INFO, "targeted_offered_load %f", targeted_offered_load);
  256. GetCpuLoad(scenario, targeted_offered_load, per_worker_credential_types,
  257. &success);
  258. } else {
  259. gpr_log(GPR_ERROR, "Unimplemented search param");
  260. }
  261. }
  262. }
  263. return success;
  264. }
  265. } // namespace testing
  266. } // namespace grpc
  267. int main(int argc, char** argv) {
  268. grpc::testing::TestEnvironment env(argc, argv);
  269. grpc::testing::InitTest(&argc, &argv, true);
  270. bool ok = grpc::testing::QpsDriver();
  271. return ok ? 0 : 1;
  272. }