HEBench
hebench_config.cpp
Go to the documentation of this file.
1 // Copyright (C) 2021 Intel Corporation
2 // SPDX-License-Identifier: Apache-2.0
3 
4 #include <algorithm>
5 #include <cassert>
6 #include <cctype>
7 #include <chrono>
8 #include <cstdlib>
9 #include <filesystem>
10 #include <fstream>
11 #include <limits>
12 #include <numeric>
13 #include <sstream>
14 #include <stdexcept>
15 #include <string>
16 #include <string_view>
17 #include <unordered_map>
18 
19 #include <yaml-cpp/yaml.h>
20 
21 #include "hebench/modules/general/include/hebench_math_utils.h"
22 #include "hebench/modules/general/include/hebench_utilities.h"
25 #include "include/hebench_config.h"
26 #include "include/hebench_engine.h"
29 
30 namespace hebench {
31 namespace Utilities {
32 
34 {
35 public:
41  static void exportBenchmarkRequest2YAML(YAML::Emitter &out,
42  const BenchmarkRequest &bench_req,
44 
45 private:
46  static YAML::Node createWorkloadParamYAMLNode(const hebench::APIBridge::WorkloadParam &w_param);
47 
48  template <typename T>
49  static YAML::Node createWorkloadParamValueYAMLNode(const T &value)
50  {
51  YAML::Node retval;
52  retval["from"] = value;
53  retval["to"] = value;
54  retval["step"] = static_cast<T>(0); // step is always 0 for exports
55 
56  return retval;
57  }
58 };
59 
61 {
62 public:
63  static std::vector<BenchmarkRequest> importYAML2BenchmarkRequest(const std::filesystem::path &working_path,
64  std::size_t benchmark_index,
65  const YAML::Node &yaml_bench,
66  const hebench::TestHarness::Engine &engine,
67  std::uint64_t fallback_min_test_time,
68  std::uint64_t fallback_sample_size);
69  template <typename T>
70  static T retrieveValue(const std::string &s_value);
71  template <typename T>
72  static T retrieveValue(const YAML::Node &node)
73  {
74  return retrieveValue<T>(node.as<std::string>());
75  }
76 
77 private:
78  static std::string getCleanEnvVariableName(std::string s_var_name)
79  {
80  s_var_name.erase(0, s_var_name.find_first_not_of(" \t\n\r\f\v"));
81  s_var_name.erase(s_var_name.find_last_not_of(" \t\n\r\f\v") + 1);
82  return !s_var_name.empty() && s_var_name.front() == '$' ? s_var_name.substr(1) : std::string();
83  }
84  static std::string evaluateEnvVariable(const std::string &s_var_name)
85  {
86  std::string retval;
87  if (!s_var_name.empty())
88  {
89  const char *c_value = std::getenv(s_var_name.c_str());
90  if (c_value)
91  retval = c_value;
92  } // end if
93  return retval;
94  }
95 
96  template <typename T>
97  static std::size_t computeComponentSize(const YAML::Node &node_from,
98  const YAML::Node &node_to,
99  const YAML::Node &node_step)
100  {
101  T param_step = retrieveValue<T>(node_step);
102  if (param_step == static_cast<T>(0))
103  param_step = std::numeric_limits<T>::max();
104  T tmp_to = retrieveValue<T>(node_to);
105  T tmp_from = retrieveValue<T>(node_from);
106  if (tmp_to < tmp_from)
107  tmp_to = tmp_from;
108  return static_cast<std::size_t>((tmp_to - tmp_from) / param_step) + 1;
109  }
110 
111  template <typename T>
112  static T computeParamValue(std::size_t count,
113  const YAML::Node &node_from,
114  const YAML::Node &node_step)
115  {
116  return static_cast<T>(count * retrieveValue<T>(node_step) + retrieveValue<T>(node_from));
117  }
118 };
119 
120 //--------------------------
121 // class ConfigImporterImpl
122 //--------------------------
123 
124 template <>
125 inline std::string ConfigImporterImpl::retrieveValue<std::string>(const std::string &s_value)
126 {
127  std::string s_tmp = getCleanEnvVariableName(s_value);
128  return s_tmp.empty() ? s_value : evaluateEnvVariable(s_tmp);
129 }
130 
131 template <typename T>
132 inline T ConfigImporterImpl::retrieveValue(const std::string &s_value)
133 {
134  T retval;
135  std::string s_tmp = getCleanEnvVariableName(s_value);
136  std::stringstream ss(s_tmp.empty() ? s_value : evaluateEnvVariable(s_tmp));
137  ss >> retval;
138  if (!ss)
139  {
140  std::stringstream ss_err;
141  ss_err << "ConfigImporterImpl::" << __func__ << "():" << __LINE__ << ": "
142  << "Invalid conversion for value \"" << s_value << "\"";
143  throw std::runtime_error(ss_err.str());
144  } // end if
145 
146  return retval;
147 }
148 
149 std::vector<BenchmarkRequest> ConfigImporterImpl::importYAML2BenchmarkRequest(const std::filesystem::path &working_path,
150  std::size_t benchmark_index,
151  const YAML::Node &yaml_bench,
152  const TestHarness::Engine &engine,
153  std::uint64_t fallback_min_test_time,
154  std::uint64_t fallback_sample_size)
155 {
156  assert(retrieveValue<decltype(benchmark_index)>(yaml_bench["ID"]) == benchmark_index);
157 
158  std::vector<BenchmarkRequest> retval;
159 
160  std::filesystem::path dataset_filename;
161  std::uint64_t default_min_test_time_ms = 0;
162  std::vector<std::uint64_t> default_sample_sizes;
163 
164  if (yaml_bench["dataset"].IsDefined() && !yaml_bench["dataset"].IsNull())
165  {
166  dataset_filename = std::filesystem::path(retrieveValue<std::string>(yaml_bench["dataset"]));
167  if (dataset_filename.is_relative())
168  dataset_filename = working_path / dataset_filename;
169  try
170  {
171  // may throw if file does not exist
172  dataset_filename = std::filesystem::canonical(dataset_filename);
173  }
174  catch (const std::exception &ex)
175  {
176  YAML::Mark yaml_mark = yaml_bench["dataset"].Mark();
177  std::stringstream ss;
178  ss << "Error in ";
179  if (yaml_mark.is_null())
180  ss << "benchmark with ID " << benchmark_index;
181  else
182  ss << "line " << yaml_mark.line + 1;
183  ss << "." << std::endl
184  << " dataset: \"" << yaml_bench["dataset"] << "\"" << std::endl
185  << ex.what();
186  throw std::runtime_error(ss.str());
187  }
188  } // end if
189 
190  if (yaml_bench["default_min_test_time"].IsDefined())
191  default_min_test_time_ms = retrieveValue<decltype(default_min_test_time_ms)>(yaml_bench["default_min_test_time"]);
192 
193  if (yaml_bench["default_sample_sizes"].IsDefined())
194  {
195  if (!yaml_bench["default_sample_sizes"].IsMap())
196  {
197  YAML::Mark yaml_mark = yaml_bench["default_sample_sizes"].Mark();
198  std::stringstream ss;
199  ss << "Value of field `default_sample_sizes` in ";
200  if (yaml_mark.is_null())
201  ss << "benchmark with ID " << benchmark_index;
202  else
203  ss << "line " << yaml_mark.line + 1;
204  ss << " must be a map matching operation parameter index to the corresponding sample size.";
205  throw std::runtime_error(ss.str());
206  } // end if
207  for (auto it = yaml_bench["default_sample_sizes"].begin(); it != yaml_bench["default_sample_sizes"].end(); ++it)
208  {
209  // retrieve the sample size in format `index: size`
210  // this way, users can specify sample sizes for some, but not necessarily all operation parameters.
211  std::size_t key = it->first.as<std::size_t>();
212  if (default_sample_sizes.size() <= key)
213  default_sample_sizes.resize(key + 1, 0);
214  default_sample_sizes[key] = retrieveValue<std::uint64_t>(it->second);
215  } // end for
216  } // end if
217 
218  if (yaml_bench["params"].IsDefined() && yaml_bench["params"].size() > 0)
219  {
220  // create a component counter to iterate over the parameters using from-to-step model
221  std::vector<std::size_t> component_sizes(yaml_bench["params"].size());
222  for (std::size_t param_i = 0; param_i < component_sizes.size(); ++param_i)
223  {
224  // validate node
225 
226  if (!yaml_bench["params"][param_i].IsDefined())
227  {
228  std::stringstream ss;
229  ss << "workload parameter index " << param_i << " not found in benchmark with ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
230  throw std::runtime_error(ss.str());
231  } // end if
232  if (!yaml_bench["params"][param_i]["name"].IsDefined())
233  {
234  std::stringstream ss;
235  ss << "Field \"name\" not found for workload parameter index " << param_i << ", benchmark ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
236  throw std::runtime_error(ss.str());
237  } // end if
238  if (!yaml_bench["params"][param_i]["type"].IsDefined())
239  {
240  std::stringstream ss;
241  ss << "Field \"type\" not found for workload parameter index " << param_i << ", benchmark ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
242  throw std::runtime_error(ss.str());
243  } // end if
244  if (!yaml_bench["params"][param_i]["value"].IsDefined())
245  {
246  std::stringstream ss;
247  ss << "Field \"value\" not found for workload parameter index " << param_i << ", benchmark ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
248  throw std::runtime_error(ss.str());
249  } // end if
250 
251  YAML::Node yaml_param_value = yaml_bench["params"][param_i]["value"];
252  if (!yaml_param_value["from"].IsDefined())
253  {
254  std::stringstream ss;
255  ss << "Field \"from\" not found for \"value\" in workload parameter index " << param_i << ", benchmark ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
256  throw std::runtime_error(ss.str());
257  } // end if
258  if (!yaml_param_value["to"].IsDefined())
259  {
260  std::stringstream ss;
261  ss << "Field \"to\" not found for \"value\" in workload parameter index " << param_i << ", benchmark ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
262  throw std::runtime_error(ss.str());
263  } // end if
264  if (!yaml_param_value["step"].IsDefined())
265  {
266  std::stringstream ss;
267  ss << "Field \"step\" not found for \"value\" in workload parameter index " << param_i << ", benchmark ID " << benchmark_index << ", defined at line " << yaml_bench.Mark().line + 1 << ".";
268  throw std::runtime_error(ss.str());
269  } // end if
270 
271  // convert param from-to-step into counter
272  std::string param_type = hebench::Utilities::ToLowerCase(retrieveValue<std::string>(yaml_bench["params"][param_i]["type"]));
273  if (param_type == "uint64")
274  {
275  component_sizes[param_i] =
276  computeComponentSize<std::uint64_t>(yaml_param_value["from"],
277  yaml_param_value["to"],
278  yaml_param_value["step"]);
279  } // end if
280  else if (param_type == "int64")
281  {
282  component_sizes[param_i] =
283  computeComponentSize<std::int64_t>(yaml_param_value["from"],
284  yaml_param_value["to"],
285  yaml_param_value["step"]);
286  } // end if
287  else if (param_type == "float64")
288  {
289  component_sizes[param_i] =
290  computeComponentSize<double>(yaml_param_value["from"],
291  yaml_param_value["to"],
292  yaml_param_value["step"]);
293  } // end if
294  else
295  {
296  std::stringstream ss;
297  ss << "Invalid \"type\" found in parameter index " << param_i
298  << ", benchmark ID " << benchmark_index << ": "
299  << yaml_bench["params"][param_i]["type"].as<std::string>() << ".";
300  throw std::runtime_error(ss.str());
301  } // end if
302  } // end for
303 
304  hebench::Utilities::Math::ComponentCounter c_counter(component_sizes);
305 
306  do
307  {
308  const std::vector<std::size_t> &count = c_counter.getCount();
310  config.dataset_filename = dataset_filename.string();
311  config.default_min_test_time_ms = default_min_test_time_ms <= 0 ? fallback_min_test_time : default_min_test_time_ms;
312  config.fallback_default_sample_size = fallback_sample_size;
313  config.default_sample_sizes = default_sample_sizes;
314  for (std::size_t param_i = 0; param_i < count.size(); ++param_i)
315  {
316  // fill out WorkloadParam struct
318  YAML::Node yaml_param = yaml_bench["params"][param_i];
319  YAML::Node yaml_param_value = yaml_param["value"];
320  std::string param_type = hebench::Utilities::ToLowerCase(retrieveValue<std::string>(yaml_param["type"]));
321 
322  if (param_type == "uint64")
323  {
325  w_param.u_param = computeParamValue<decltype(w_param.u_param)>(count[param_i],
326  yaml_param_value["from"],
327  yaml_param_value["step"]);
328  } // end if
329  else if (param_type == "int64")
330  {
332  w_param.i_param = computeParamValue<decltype(w_param.i_param)>(count[param_i],
333  yaml_param_value["from"],
334  yaml_param_value["step"]);
335  } // end if
336  else if (param_type == "float64")
337  {
339  w_param.f_param = computeParamValue<decltype(w_param.f_param)>(count[param_i],
340  yaml_param_value["from"],
341  yaml_param_value["step"]);
342  } // end if
343 
344  hebench::Utilities::copyString(w_param.name, HEBENCH_MAX_BUFFER_SIZE, retrieveValue<std::string>(yaml_param["name"]));
345  config.w_params.push_back(w_param);
346  } // end for
347 
348  // check if benchmark is supported with specified parameters
349  try
350  {
351  if (!retval.empty() && !config.dataset_filename.empty())
352  throw std::runtime_error("External dataset is only supported on benchmark configuration blocks that define a single benchmark "
353  "(for a workload parameter, fields `from` and `to` must match).");
355  engine.describeBenchmark(benchmark_index, config);
356  if (!p_token)
357  throw std::runtime_error("Unable to match benchmark: no match found able to support selected benchmark with specified workload parameters.");
358  retval.emplace_back();
359  retval.back().index = benchmark_index;
360  retval.back().configuration = p_token->getBenchmarkConfiguration();
361  }
362  catch (std::runtime_error &ex)
363  {
364  std::stringstream ss;
365  ss << "Benchmark ID: " << benchmark_index << std::endl
366  << ex.what();
367  throw std::runtime_error(ss.str());
368  }
369  } while (!c_counter.inc()); // repeat until we have done all possible parameters
370  } // end if
371  else
372  {
373  // add an empty parameter list for workload
374  try
375  {
376  retval.emplace_back();
377  retval.back().configuration.default_min_test_time_ms = default_min_test_time_ms;
378  retval.back().configuration.default_sample_sizes = default_sample_sizes;
380  engine.describeBenchmark(benchmark_index, retval.back().configuration);
381  if (!p_token)
382  throw std::runtime_error("Unable to match benchmark.");
383  retval.back().index = benchmark_index;
384  retval.back().configuration = p_token->getBenchmarkConfiguration();
385  }
386  catch (std::runtime_error &ex)
387  {
388  std::stringstream ss;
389  ss << "Benchmark ID: " << benchmark_index << std::endl
390  << ex.what();
391  throw std::runtime_error(ss.str());
392  }
393  } // end else
394 
395  return retval;
396 }
397 
398 //--------------------------
399 // class ConfigExporterImpl
400 //--------------------------
401 
402 YAML::Node ConfigExporterImpl::createWorkloadParamYAMLNode(const hebench::APIBridge::WorkloadParam &w_param)
403 {
404  YAML::Node retval;
405  retval["name"] = w_param.name;
406  switch (w_param.data_type)
407  {
409  retval["type"] = "Int64";
410  retval["value"] = createWorkloadParamValueYAMLNode<decltype(w_param.i_param)>(w_param.i_param);
411  break;
412 
414  retval["type"] = "UInt64";
415  retval["value"] = createWorkloadParamValueYAMLNode<decltype(w_param.u_param)>(w_param.u_param);
416  break;
417 
419  retval["type"] = "Float64";
420  retval["value"] = createWorkloadParamValueYAMLNode<decltype(w_param.f_param)>(w_param.f_param);
421  break;
422 
423  default:
424  throw std::invalid_argument("Invalid data type detected in 'w_param'.");
425  break;
426  } // end switch
427 
428  return retval;
429 }
430 
432  const BenchmarkRequest &bench_req,
434 {
436  std::stringstream ss;
437 
438  ss << "Section \"description\" is for informational purposes only. It shows the" << std::endl
439  << "benchmark descriptor matching the benchmark ID. Changing contents of" << std::endl
440  << "\"description\" has no effect.";
441  out << YAML::Newline;
442  out << YAML::Comment(ss.str());
443 
444  YAML::Node node_benchmark;
445  YAML::Node node_bench_description;
446  node_bench_description["workload"] = description.workload;
447  node_bench_description["workload_name"] = description.workload_name;
448  node_bench_description["data_type"] = description.data_type;
449  node_bench_description["category"] = description.category;
450  node_bench_description["scheme"] = description.scheme;
451  node_bench_description["security"] = description.security;
452  node_bench_description["cipher_flags"] = description.cipher_flags;
453  node_bench_description["other"] = description.other;
454  node_bench_description["notes"] = YAML::Node(YAML::NodeType::Null);
455  node_benchmark["ID"] = bench_req.index;
456  node_benchmark["description"] = node_bench_description;
457  node_benchmark["dataset"] = YAML::Node(YAML::NodeType::Null);
458  node_benchmark["default_min_test_time"] = config.default_min_test_time_ms;
459  if (!config.default_sample_sizes.empty())
460  {
461  for (std::size_t op_param_i = 0; op_param_i < config.default_sample_sizes.size(); ++op_param_i)
462  node_benchmark["default_sample_sizes"].push_back(config.default_sample_sizes[op_param_i]);
463  node_benchmark["default_sample_sizes"]["to_map"] = "yes"; // convert sequence to map to have the indices as keys
464  node_benchmark["default_sample_sizes"].remove("to_map");
465  } // end if
466  if (!config.w_params.empty())
467  {
468  for (std::size_t param_i = 0; param_i < config.w_params.size(); ++param_i)
469  node_benchmark["params"].push_back(createWorkloadParamYAMLNode(config.w_params[param_i]));
470  node_benchmark["params"]["to_map"] = "yes"; // convert sequence to map to have the indices as keys
471  node_benchmark["params"].remove("to_map");
472  } // end if
473  out << node_benchmark << YAML::Newline;
474 }
475 
476 //-----------------------------
477 // class BenchmarkConfigLoader
478 //-----------------------------
479 
480 std::shared_ptr<BenchmarkConfigLoader> BenchmarkConfigLoader::create(const std::string &yaml_filename,
481  std::uint64_t fallback_random_seed)
482 {
483  std::shared_ptr<BenchmarkConfigLoader> p_retval =
484  std::shared_ptr<BenchmarkConfigLoader>(new BenchmarkConfigLoader(yaml_filename, fallback_random_seed));
485  return p_retval;
486 }
487 
488 BenchmarkConfigLoader::BenchmarkConfigLoader(const std::string &yaml_filename,
489  std::uint64_t fallback_random_seed) :
490  m_random_seed(fallback_random_seed),
491  m_default_min_test_time_ms(0),
492  m_default_sample_size(0)
493 {
494  // get absolute path and directory of yaml file to load
495  m_filename = std::filesystem::canonical(yaml_filename).string();
496  std::filesystem::path file_dir = std::filesystem::path(m_filename).remove_filename();
497  YAML::Node root = YAML::LoadFile(m_filename);
498 
499  if (!root["benchmark"].IsDefined())
500  throw std::runtime_error("Map \"benchmark\" not found at root of configuration file.");
501  if (!root["benchmark"].IsSequence())
502  throw std::runtime_error("Value for map \"benchmark\" is not a valid YAML sequence.");
503 
504  // parse benchmark defaults
505  if (root["default_min_test_time"].IsDefined())
506  m_default_min_test_time_ms =
507  ConfigImporterImpl::retrieveValue<decltype(m_default_min_test_time_ms)>(root["default_min_test_time"]);
508  if (root["default_sample_size"].IsDefined())
509  m_default_sample_size =
510  ConfigImporterImpl::retrieveValue<decltype(m_default_sample_size)>(root["default_sample_size"]);
511  if (root["random_seed"].IsDefined())
512  m_random_seed = ConfigImporterImpl::retrieveValue<std::uint64_t>(root["random_seed"]);
513 
514  if (root["initialization_data"].IsDefined() && !root["initialization_data"].IsNull())
515  {
516  // process initialization data
517  std::string init_data = ConfigImporterImpl::retrieveValue<std::string>(root["initialization_data"]);
518  if (!init_data.empty())
519  {
520  std::filesystem::path init_data_filepath = init_data;
521  // check if this is a relative file name
522  if (init_data_filepath.is_relative())
523  // filenames are relative to the yaml file
524  init_data_filepath = file_dir / init_data_filepath;
525  if (std::filesystem::exists(init_data_filepath) && std::filesystem::is_regular_file(init_data_filepath))
526  {
527  std::ifstream fnum;
528  fnum.open(init_data_filepath, std::ifstream::in | std::ifstream::binary);
529  if (!fnum.is_open())
530  {
531  std::stringstream ss;
532  ss << "Could not open file " << init_data_filepath
533  << " for reading as indicated by field `initialization_data`.";
534  throw std::runtime_error(ss.str());
535  } // end if
536  // read the whole file
537  fnum.seekg(0, fnum.end);
538  m_data.resize(fnum.tellg());
539  fnum.seekg(0, fnum.beg);
540  fnum.read(reinterpret_cast<std::ifstream::char_type *>(m_data.data()), m_data.size());
541  } // end if
542  else
543  // make the actual yaml string into the initialization data
544  m_data.assign(init_data.begin(), init_data.end());
545  } // end if
546  } // end if
547 
548  m_yaml_content = std::shared_ptr<YAML::Node>(new YAML::Node(root));
549 }
550 
551 //-----------------------------
552 // class BenchmarkConfigBroker
553 //-----------------------------
554 
555 BenchmarkConfigBroker::BenchmarkConfigBroker(std::weak_ptr<hebench::TestHarness::Engine> wp_engine,
556  std::uint64_t random_seed,
557  const std::string s_backend) :
558  m_wp_engine(wp_engine),
559  m_s_backend(s_backend)
560 {
561  std::shared_ptr<hebench::TestHarness::Engine> p_engine = wp_engine.lock();
562  if (!p_engine)
563  throw std::invalid_argument(IL_LOG_MSG_CLASS("Invalid empty 'wp_engine'."));
564 
565  std::size_t count_benchmarks = p_engine->countBenchmarks();
566  m_default_benchmarks.random_seed = random_seed;
567  std::vector<BenchmarkRequest> &benchmark_requests = m_default_benchmarks.benchmark_requests;
568  benchmark_requests.reserve(count_benchmarks);
569  for (std::size_t bench_i = 0; bench_i < count_benchmarks; ++bench_i)
570  {
571  std::vector<std::vector<hebench::APIBridge::WorkloadParam>> w_params_set =
572  p_engine->getDefaultWorkloadParams(bench_i);
573  for (std::size_t w_params_i = 0; w_params_i < w_params_set.size(); ++w_params_i)
574  {
575  benchmark_requests.emplace_back();
576  BenchmarkRequest &bench_description = benchmark_requests.back();
577  bench_description.configuration.w_params = std::move(w_params_set[w_params_i]);
578  // obtain full description for the benchmark
580  p_engine->describeBenchmark(bench_i, bench_description.configuration);
581  if (!p_token)
582  throw std::runtime_error(IL_LOG_MSG_CLASS("Unexpected error: no matching benchmark found for backend request."));
583  // store the data needed to retrieve the benchmark
584  bench_description.index = bench_i;
585  bench_description.configuration = p_token->getBenchmarkConfiguration();
586  } // end for
587  } // end for
588 }
589 
590 void BenchmarkConfigBroker::exportConfiguration(const std::string &yaml_filename,
591  const BenchmarkSession &bench_configs) const
592 {
593  std::shared_ptr<hebench::TestHarness::Engine> p_engine = m_wp_engine.lock();
594  if (!p_engine)
595  throw std::logic_error(IL_LOG_MSG_CLASS("Invalid internal state: engine object has been released."));
596 
597  std::filesystem::path path_backend = m_s_backend;
598 
599  YAML::Emitter out;
600 
601  std::stringstream ss;
602  ss << "HEBench auto-generated benchmark selection and configuration file." << std::endl
603  << std::endl;
604  if (path_backend.has_filename())
605  ss << "Generated from backend: " << std::endl
606  << " " << path_backend.filename() << std::endl
607  << std::endl;
608  ss << "Only benchmarks with their workload parameters specified here will run when" << std::endl
609  << "this configuration file is used with Test Harness. This configuration only" << std::endl
610  << "works for the backend used to generate this file (the backend)." << std::endl
611  << std::endl
612  << "Benchmark \"ID\" represents workload and benchmark descriptor options for the" << std::endl
613  << "backend. Benchmark \"params\" are the configurable workload parameters." << std::endl
614  << std::endl
615  << "To use this file do any of the following:" << std::endl
616  << " - Add or remove benchmarks based on pre-existing configurations." << std::endl
617  << " - Modify the configuration and workload parameters for existing or added" << std::endl
618  << " benchmarks." << std::endl
619  << std::endl
620  << "Global configuration values, if present, are used when local values are not" << std::endl
621  << "specified per benchmark." << std::endl
622  << std::endl
623  << "When adding new benchmarks:" << std::endl
624  << " The only benchmark IDs supported are those already existing in this" << std::endl
625  << " auto-generated file as reported by the backend. Any new benchmark added" << std::endl
626  << " must have the same number of workload parameters as those already existing" << std::endl
627  << " in this auto-generated file with the same benchmark ID." << std::endl
628  << std::endl
629  << "When modifying workload parameters:" << std::endl
630  << " Number of workload parameters, their type and name must match those of any" << std::endl
631  << " auto-generated benchmark with the same ID." << std::endl
632  << " Refer to workload and backend specifications for supported range of values" << std::endl
633  << " for each workload parameter. Invalid values will cause the benchmark to" << std::endl
634  << " fail during execution." << std::endl
635  << std::endl
636  << "If non-null \"dataset\" is specified for a benchmark, the framework will" << std::endl
637  << "attempt to load the specified file and use its contents as values for inputs" << std::endl
638  << "and ground truths instead of using synthetic data. For a benchmark" << std::endl
639  << "description specifying a dataset file, all workload parameter ranges must" << std::endl
640  << "resolve to single values." << std::endl;
641 
642  out << YAML::Comment(ss.str()) << YAML::Newline;
643 
644  // output benchmark default configuration
645  out << YAML::Newline;
646 
647  out << YAML::BeginMap;
648 
649  ss = std::stringstream();
650  ss << "Default minimum test time in milliseconds. Latency tests specifying" << std::endl
651  << "default test time and Offline tests will be repeated until this time" << std::endl
652  << "has elapsed. Defaults to 0 if not present (Latency executes twice," << std::endl
653  << "Offline executes once).";
654  out << YAML::Comment(ss.str())
655  << YAML::Key << "default_min_test_time" << YAML::Value << 0;
656  out << YAML::Newline << YAML::Newline;
657 
658  ss = std::stringstream();
659  ss << "Default sample size to use for operation parameters that support" << std::endl
660  << "variable sample size in Offline category. Defaults to benchmark" << std::endl
661  << "specific if not present.";
662  out << YAML::Comment(ss.str())
663  << YAML::Key << "default_sample_size" << YAML::Value << 0;
664  out << YAML::Newline << YAML::Newline;
665 
666  ss = std::stringstream();
667  ss << "Random seed to use when generating synthetic data for these benchmarks." << std::endl
668  << "Type: unsigned int. Defaults to system time when not present.";
669  out << YAML::Comment(ss.str())
670  << YAML::Key << "random_seed" << YAML::Value << bench_configs.random_seed;
671  out << YAML::Newline << YAML::Newline;
672 
673  ss = std::stringstream();
674  ss << "Optional backend initialization data." << std::endl
675  << "Type: string (can be null)." << std::endl
676  << "When present, if this is the name of an existing file (relative to this" << std::endl
677  << "file, or absolute), the file binary contents will be forwarded to the" << std::endl
678  << "backend engine during initialization. Otherwise, the specified string is" << std::endl
679  << "forwarded as is (without null terminator)";
680  out << YAML::Comment(ss.str())
681  << YAML::Key << "initialization_data" << YAML::Value << YAML::Null;
682  out << YAML::Newline << YAML::Newline;
683 
684  out << YAML::EndMap;
685 
686  // output benchmark list
687 
688  out << YAML::BeginMap;
689  out << YAML::Key << "benchmark" << YAML::Value
690  << YAML::BeginSeq;
691 
692  auto &bench_requests = bench_configs.benchmark_requests;
693 
694  for (std::size_t i = 0; i < bench_requests.size(); ++i)
695  {
697  p_engine->describeBenchmark(bench_requests[i].index, bench_requests[i].configuration);
698  if (!p_token)
699  {
700  ss = std::stringstream();
701  ss << "No matching benchmark found for backend request " << i
702  << " for backend benchmark index " << bench_requests[i].index << ".";
703  throw std::invalid_argument(IL_LOG_MSG_CLASS(ss.str()));
704  } // end if
705  BenchmarkRequest bench_request_corrected; // finalize the benchmark request to output default values
706  bench_request_corrected.index = bench_requests[i].index;
707  bench_request_corrected.configuration = p_token->getBenchmarkConfiguration();
708  bench_request_corrected.configuration.default_min_test_time_ms = p_token->getBackendDescription().descriptor.cat_params.min_test_time_ms;
709  hebench::APIBridge::Category category = p_token->getBackendDescription().descriptor.category;
710  auto &sample_sizes = bench_request_corrected.configuration.default_sample_sizes;
711  assert(sample_sizes.size() < HEBENCH_MAX_OP_PARAMS);
712  switch (category)
713  {
715  // no sample sizes for latency test
716  sample_sizes.clear();
717  break;
719  for (std::size_t i = 0; i < sample_sizes.size(); ++i)
720  sample_sizes[i] = p_token->getBackendDescription().descriptor.cat_params.offline.data_count[i];
721  break;
722  default:
723  break;
724  } // end switch
725  ConfigExporterImpl::exportBenchmarkRequest2YAML(out, bench_request_corrected, p_token->getDescription());
726  } // end for
727 
728  out << YAML::EndSeq
729  << YAML::EndMap;
730 
731  hebench::Utilities::writeToFile(
732  yaml_filename,
733  [&out](std::ostream &os) {
734  os << out.c_str();
735  },
736  false, false);
737 }
738 
740 {
741  BenchmarkSession retval;
742  std::uint64_t default_min_test_time_ms = 0;
743  std::uint64_t default_sample_size = 0;
744 
745  const void *p_yaml_content = loader.getYAMLContent({});
746  if (!p_yaml_content)
747  throw std::invalid_argument(IL_LOG_MSG_CLASS("Invalid loader: internal YAML content is not initialized."));
748  std::shared_ptr<hebench::TestHarness::Engine> p_engine = m_wp_engine.lock();
749  if (!p_engine)
750  throw std::logic_error(IL_LOG_MSG_CLASS("Invalid internal state: engine object has been released."));
751 
752  std::filesystem::path file_dir = std::filesystem::path(loader.getFilename()).remove_filename();
753  YAML::Node root = *reinterpret_cast<const YAML::Node *>(p_yaml_content);
754 
755  // Root is already validated by loader to be a valid benchmark sequence
756  // (individual benchmarks are not validated by loader).
757  root = root["benchmark"];
758  std::vector<BenchmarkRequest> &benchmark_requests = retval.benchmark_requests;
759  benchmark_requests.reserve(root.size());
760  for (std::size_t i = 0; i < root.size(); ++i)
761  {
762  if (!root[i]["ID"].IsDefined())
763  throw std::runtime_error("Field \"ID\" not found in benchmark.");
764  std::size_t benchmark_index = ConfigImporterImpl::retrieveValue<std::size_t>(root[i]["ID"]);
765  std::vector<BenchmarkRequest> node_requests =
767  benchmark_index,
768  root[i],
769  *p_engine,
770  default_min_test_time_ms,
771  default_sample_size);
772  benchmark_requests.insert(benchmark_requests.end(), node_requests.begin(), node_requests.end());
773  } // end for
774 
775  retval.random_seed = loader.getRandomSeed();
776  retval.data = loader.getInitData();
777 
778  return retval;
779 }
780 
781 } // namespace Utilities
782 } // namespace hebench
std::uint64_t fallback_default_sample_size
Default sample size to be used if a specific size is not specified in the default_sample_sizes collec...
std::vector< std::uint64_t > default_sample_sizes
Default sample size for each operation parameter.
std::vector< hebench::APIBridge::WorkloadParam > w_params
Set of arguments for workload parameters.
std::string dataset_filename
File containing data for the benchmark. If empty string, benchmarks that can auto generate the datase...
std::uint64_t default_min_test_time_ms
Default minimum test time in milliseconds.
IBenchmarkDescriptor::DescriptionToken::Ptr describeBenchmark(std::size_t index, const BenchmarkDescription::Configuration &config) const
Describes a benchmark workload that matches the specified description from the benchmarks registered ...
BenchmarkSession importConfiguration(const BenchmarkConfigLoader &loader) const
Imports a benchmark configuration from a configurator loader.
BenchmarkConfigBroker(std::weak_ptr< hebench::TestHarness::Engine > wp_engine, std::uint64_t random_seed=0, const std::string s_backend=std::string())
void exportConfiguration(const std::string &yaml_filename, const BenchmarkSession &bench_configs) const
const std::string & getFilename() const
Retrieves the file name used to load the yaml data.
static std::shared_ptr< BenchmarkConfigLoader > create(const std::string &yaml_filename, std::uint64_t fallback_random_seed)
Loads a benchmark configuration from yaml data contained in a file.
BenchmarkConfigLoader(const BenchmarkConfigLoader &)=delete
const void * getYAMLContent(const FriendKey &) const
std::uint64_t getRandomSeed() const
Retrieves the configuration random seed.
const std::vector< std::int8_t > & getInitData() const
static void exportBenchmarkRequest2YAML(YAML::Emitter &out, const BenchmarkRequest &bench_req, const hebench::TestHarness::BenchmarkDescription::Description &description)
exportBenchmarkDescription2YAML
static T retrieveValue(const YAML::Node &node)
static T retrieveValue(const std::string &s_value)
static std::vector< BenchmarkRequest > importYAML2BenchmarkRequest(const std::filesystem::path &working_path, std::size_t benchmark_index, const YAML::Node &yaml_bench, const hebench::TestHarness::Engine &engine, std::uint64_t fallback_min_test_time, std::uint64_t fallback_sample_size)
@ Float64
64 bits IEEE 754 standard floating point real numbers.
Definition: types.h:306
@ Int64
64 bits signed integers.
Definition: types.h:304
@ UInt64
64 bits unsigned integers.
Definition: types.h:305
char name[HEBENCH_MAX_BUFFER_SIZE]
Null-terminated string containing the name for the parameter.
Definition: types.h:328
WorkloadParamType::WorkloadParamType data_type
Type of the parameter data.
Definition: types.h:322
Category
Defines all possible categories for each workload.
Definition: types.h:391
Defines a single workload parameter.
Definition: types.h:318
std::string other
Other value used to uniquely identify benchmark implementations.
std::string scheme
Human-readable friendly name of the benchmark scheme.
std::string cipher_flags
Human-readable friendly name of the benchmark cipher parameters.
std::int64_t workload
Workload ID as given by hebench::APIBridge::Workload.
std::string workload_name
Human-readable friendly name of the benchmark workload.
std::string category
Human-readable friendly name of the benchmark category.
std::string data_type
Human-readable friendly name of the benchmark input/output data type.
std::string security
Human-readable friendly name of the benchmark security.
std::vector< BenchmarkRequest > benchmark_requests
hebench::TestHarness::BenchmarkDescription::Configuration configuration
std::vector< std::int8_t > data
std::uint64_t copyString(char *dst, std::uint64_t size, const std::string &src)
Copies a C++ string object into a C-style string.
Definition: utilities.cpp:13
#define HEBENCH_MAX_BUFFER_SIZE
Definition: types.h:56
#define HEBENCH_MAX_OP_PARAMS
Maximum number of parameters supported by an operation.
Definition: types.h:63