1 /*******************************************************************************
3 * Copyright (c) 2020 Intel.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 *******************************************************************************/
20 /* This is the new utility file for all tests, all new common functionality has to go here.
21 When contributing to the common.hpp please focus on readability and maintainability rather than
23 #ifndef XRANLIB_COMMON_HPP
24 #define XRANLIB_COMMON_HPP
26 /* Disable warnings generated by JSON parser */
27 #pragma warning(disable : 191)
28 #pragma warning(disable : 186)
29 #pragma warning(disable : 192)
37 #include <immintrin.h>
43 #include <rte_config.h>
44 #include <rte_malloc.h>
47 #include "gtest/gtest.h"
49 #include "common_typedef_xran.h"
53 using json = nlohmann::json;
55 #define ASSERT_ARRAY_NEAR(reference, actual, size, precision) \
56 assert_array_near(reference, actual, size, precision)
58 #define ASSERT_ARRAY_EQ(reference, actual, size) \
59 assert_array_eq(reference, actual, size)
61 #define ASSERT_AVG_GREATER_COMPLEX(reference, actual, size, precision) \
62 assert_avg_greater_complex(reference, actual, size, precision)
64 struct BenchmarkParameters
66 static long repetition;
68 static unsigned cpu_id;
71 struct missing_config_file_exception : public std::exception
73 const char * what () const throw () override {
74 return "JSON file cannot be opened!";
78 struct reading_input_file_exception : public std::exception
80 const char * what () const throw () override {
81 return "Input file cannot be read!";
86 \brief Attach current process to the selected core.
87 \param [in] cpu Core number.
88 \return 0 on success, -1 otherwise.
90 int bind_to_cpu(const unsigned cpu);
93 \brief Calculate the mean and variance from the result of the run_benchmark.
94 \param [in] values Vector with result values.
95 \return std::pair where the first element is mean and the second one is standard deviation.
96 \note It's not a general mean/stddev function it only works properly when feed with data from
97 the benchmark function.
99 std::pair<double, double> calculate_statistics(const std::vector<long> values);
102 \brief For a given number return sequence of number from 0 to number - 1.
103 \param [in] number Positive integer value.
104 \return Vector with the sorted integer numbers between 0 and number - 1.
106 std::vector<unsigned> get_sequence(const unsigned number);
109 \brief Read JSON from the given file.
110 \param [in] filename name of the .json file.
111 \return JSON object with data.
112 \throws missing_config_file_exception when file cannot be opened.
114 json read_json_from_file(const std::string &filename);
117 \brief Read binary data from the file.
118 \param [in] filename name of the binary file.
119 \return Pointer to the allocated memory with data from the file.
120 \throws std::runtime_error when memory cannot be allocated.
122 char* read_data_to_aligned_array(const std::string &filename);
125 \brief Measure the TSC on the machine
126 \return Number of ticks per us
128 unsigned long tsc_recovery();
131 \brief Return the current value of the TSC
132 \return Current TSC value
134 unsigned long tsc_tick();
139 Each test class has to inherit from KernelTests class as it provides GTest support and does a lot
140 of setup (including JSON) an provides useful methods to operate on loaded JSON file.
141 Unfortunately GTest is limited in the way that all TEST_P within the class are called for all
142 cases/parameters, but we usually want two different data sets for functional and performance
143 tests (or maybe other types of tests). Because of that to use different data sets we need to
144 create separate classes, hence performance and functional test are in separate classes. it adds
145 an extra overhead, but adds much more flexibility. init_test(...) is used to select data set from
148 Important note on the JSON file structure. Top JSON object can have as many section (JSON
149 objects) as needed, but each have to have a distinct name that is used by init_test. Then
150 each section must contain an array of objects (test cases) where each object has a name,
151 parameters and references. Everything inside parameters and references can be completely custom
152 as it's loaded by get_input/reference_parameter function. JSON values can be either literal
153 values, e.g. 1, 0.001, 5e-05, etc. or filename. Depends on the get type test framework can either
154 read the value or load data from the file - and it happens automatically (*pff* MAGIC!).
156 class KernelTests : public testing::TestWithParam<unsigned>
160 static std::string test_type;
162 static void SetUpTestCase()
168 conf = read_json_from_file("conf.json");
170 catch(missing_config_file_exception &e)
172 std::cout << "[----------] SetUpTestCase failed: " << e.what() << std::endl;
176 tsc = tsc_recovery();
180 std::cout << "[----------] SetUpTestCase failed: TSC recovery failed" << std::endl;
185 static void TearDownTestCase()
187 /* Free resources - nothing to free at the moment */
190 static unsigned get_number_of_cases(const std::string &type)
194 json json_data = read_json_from_file("conf.json");
196 return json_data[type].size();
198 catch(missing_config_file_exception &e)
200 std::cout << "[----------] get_number_of_cases failed: " << e.what() << std::endl;
204 catch(std::domain_error &e)
206 std::cout << "[----------] get_number_of_cases failed: " << e.what() << std::endl;
207 std::cout << "[----------] Use a default value: 0" << std::endl;
214 double division_factor = 1.0;
215 std::string result_units = "None";
216 int parallelization_factor = 1;
219 \brief Set division factor
220 \param [in] factor Division factor that divides mean and standard deviation.
222 void set_division_factor(const double factor)
224 division_factor = factor;
228 \brief Set reults units
229 \param [in] units Units that are displayed in the report.
231 void set_results_units(const std::string &units)
233 result_units = units;
237 \brief Set size of processed data
238 \param [in] size Size of processed data used to calculate module throughput.
240 void set_parallelization_factor(const int factor)
242 parallelization_factor = factor;
246 \brief Run performance test case for a given function.
247 \param [in] isa Used Instruction Set.
248 \param [in] module_name name of the tested kernel.
249 \param [in] function function to be tested.
250 \param [in] args function's arguments.
252 template <typename F, typename ... Args>
253 void performance(const std::string &isa, const std::string &module_name, F function,
255 ASSERT_EQ(0, bind_to_cpu(BenchmarkParameters::cpu_id)) << "Failed to bind to cpu!";
257 const auto result = run_benchmark(function, args ...);
258 const auto scaled_mean = result.first / division_factor / tsc;
259 const auto scaled_stddev = result.second / division_factor / tsc;
261 print_and_store_results(isa, get_case_name(), module_name, get_case_name(), result_units,
262 parallelization_factor, scaled_mean, scaled_stddev);
266 \brief Print unique test description to the results xml file
267 \param [in] isa Used Instruction Set.
268 \param [in] module_name name of the tested kernel.
269 \param [in] function function to be tested.
271 void print_test_description(const std::string &isa, const std::string &module_name) {
272 print_and_store_results(isa, get_case_name(), module_name, get_case_name(), result_units,
273 parallelization_factor, 0, 0);
278 \brief Load selected data from a JSON object.
279 get_input_parameter loads data from parameters section of the test case in JSON file and
280 get_reference_parameter does the same thing for references section.
282 Get parameter function uses template type to figure out how to load parameters. If type
283 is NOT a pointer it'll load value directly from the JSON. Otherwise path to the test
284 vector is expected and function will allocate memory, load data from the binary file to
285 this memory location and return pointer to it. For example in here we request to load
286 pointer to float so llrs filed is expected to be
287 a path to the binary file.
289 template <typename T>
290 T get_input_parameter(const std::string ¶meter_name)
294 return get_parameter<T>("parameters", parameter_name);
296 catch (std::domain_error &e)
298 std::cout << "[----------] get_input_parameter (" << parameter_name
299 << ") failed: " << e.what()
300 << ". Did you mispell the parameter name?" << std::endl;
303 catch(reading_input_file_exception &e)
305 std::cout << "[----------] get_input_parameter (" << parameter_name
306 << ") failed: " << e.what() << std::endl;
311 template <typename T>
312 T get_input_parameter(const std::string &subsection_name, const std::string ¶meter_name)
316 return get_parameter<T>("parameters", subsection_name, parameter_name);
318 catch (std::domain_error &e)
320 std::cout << "[----------] get_input_parameter (" << subsection_name << "." << parameter_name
321 << ") failed: " << e.what()
322 << ". Did you mispell the parameter name?" << std::endl;
325 catch(reading_input_file_exception &e)
327 std::cout << "[----------] get_input_parameter (" << subsection_name << "." << parameter_name
328 << ") failed: " << e.what() << std::endl;
333 template <typename T>
334 T get_input_parameter(const std::string &subsection_name, const int index, const std::string ¶meter_name)
338 return get_parameter<T>("parameters", subsection_name, index, parameter_name);
340 catch (std::domain_error &e)
342 std::cout << "[----------] get_input_parameter (" << subsection_name << "[" << index << "]." << parameter_name
343 << ") failed: " << e.what()
344 << ". Did you mispell the parameter name?" << std::endl;
347 catch(reading_input_file_exception &e)
349 std::cout << "[----------] get_input_parameter (" << subsection_name << "[" << index << "]." << parameter_name
350 << ") failed: " << e.what() << std::endl;
354 int get_input_parameter_size(const std::string &subsection_name, const std::string ¶meter_name)
358 auto array_size = conf[test_type][GetParam()]["parameters"][subsection_name][parameter_name].size();
361 catch (std::domain_error &e)
363 std::cout << "[----------] get_input_parameter_size (" << subsection_name << "." << parameter_name
364 << ") failed: " << e.what()
365 << ". Did you mispell the parameter name?" << std::endl;
368 catch(reading_input_file_exception &e)
370 std::cout << "[----------] get_input_parameter_size (" << subsection_name << "." << parameter_name
371 << ") failed: " << e.what() << std::endl;
375 int get_input_subsection_size(const std::string &subsection_name)
379 auto array_size = conf[test_type][GetParam()]["parameters"][subsection_name].size();
382 catch (std::domain_error &e)
384 std::cout << "[----------] get_input_subsection_size (" << subsection_name
385 << ") failed: " << e.what()
386 << ". Did you mispell the subsection name?" << std::endl;
389 catch(reading_input_file_exception &e)
391 std::cout << "[----------] get_input_subsection_size (" << subsection_name
392 << ") failed: " << e.what() << std::endl;
397 template <typename T>
398 T get_reference_parameter(const std::string ¶meter_name)
402 return get_parameter<T>("references", parameter_name);
404 catch (std::domain_error &e)
406 std::cout << "[----------] get_reference_parameter (" << parameter_name
407 << ") failed: " << e.what()
408 << ". Did you mispell the parameter name?" << std::endl;
411 catch(reading_input_file_exception &e)
413 std::cout << "[----------] get_reference_parameter (" << parameter_name
414 << ") failed: " << e.what() << std::endl;
421 \brief Get name of the test case from JSON file.
422 \return Test'ss case name or a default name if name field is missing.
424 const std::string get_case_name()
428 return conf[test_type][GetParam()]["name"];
430 catch (std::domain_error &e)
432 std::cout << "[----------] get_case_name failed: " << e.what()
433 << ". Did you specify a test name in JSON?" << std::endl;
434 std::cout << "[----------] Using a default name instead" << std::endl;
436 return "Default test name";
441 \brief Defines section in the conf.json that is used to load parameters from.
442 \param [in] type Name of the section in the JSON file.
444 void init_test(const std::string &type)
447 const std::string name = get_case_name();
448 std::cout << "[----------] Test case: " << name << std::endl;
452 static unsigned long tsc;
456 static T read_parameter(const int index, const std::string &type,
457 const std::string ¶meter_name)
459 return conf[test_type][index][type][parameter_name];
464 struct data_reader<std::vector<T>> {
465 static std::vector<T> read_parameter(const int index, const std::string &type,
466 const std::string ¶meter_name)
468 auto array_size = conf[test_type][index][type][parameter_name].size();
470 std::vector<T> result(array_size);
472 for(unsigned number = 0; number < array_size; number++)
473 result.at(number) = conf[test_type][index][type][parameter_name][number];
480 struct data_reader<T*> {
481 static T* read_parameter(const int index, const std::string &type,
482 const std::string ¶meter_name)
484 return (T*) read_data_to_aligned_array(conf[test_type][index][type][parameter_name]);
488 template <typename T>
489 T get_parameter(const std::string &type, const std::string ¶meter_name)
491 return data_reader<T>::read_parameter(GetParam(), type, parameter_name);
495 struct data_reader2 {
496 static T read_parameter(const int index, const std::string &type,
497 const std::string &subsection_name,
498 const std::string ¶meter_name)
500 return conf[test_type][index][type][subsection_name][parameter_name];
505 struct data_reader2<std::vector<T>> {
506 static std::vector<T> read_parameter(const int index, const std::string &type,
507 const std::string &subsection_name,
508 const std::string ¶meter_name)
510 auto array_size = conf[test_type][index][type][subsection_name][parameter_name].size();
512 std::vector<T> result(array_size);
514 for(unsigned number = 0; number < array_size; number++)
515 result.at(number) = conf[test_type][index][type][subsection_name][parameter_name][number];
522 struct data_reader2<T*> {
523 static T* read_parameter(const int index, const std::string &type,
524 const std::string &subsection_name,
525 const std::string ¶meter_name)
527 return (T*) read_data_to_aligned_array(conf[test_type][index][type][subsection_name][parameter_name]);
530 template <typename T>
531 T get_parameter(const std::string &type, const std::string &subsection_name, const std::string ¶meter_name)
533 return data_reader2<T>::read_parameter(GetParam(), type, subsection_name, parameter_name);
537 struct data_reader3 {
538 static T read_parameter(const int index, const std::string &type,
539 const std::string &subsection_name,
541 const std::string ¶meter_name)
543 return conf[test_type][index][type][subsection_name][subindex][parameter_name];
548 struct data_reader3<std::vector<T>> {
549 static std::vector<T> read_parameter(const int index, const std::string &type,
550 const std::string &subsection_name,
552 const std::string ¶meter_name)
554 auto array_size = conf[test_type][index][type][subsection_name][subindex][parameter_name].size();
556 std::vector<T> result(array_size);
558 for(unsigned number = 0; number < array_size; number++)
559 result.at(number) = conf[test_type][index][type][subsection_name][subindex][parameter_name][number];
566 struct data_reader3<T*> {
567 static T* read_parameter(const int index, const std::string &type,
568 const std::string &subsection_name,
570 const std::string ¶meter_name)
572 return (T*) read_data_to_aligned_array(conf[test_type][index][type][subsection_name][subindex][parameter_name]);
575 template <typename T>
576 T get_parameter(const std::string &type, const std::string &subsection_name, const int subindex, const std::string ¶meter_name)
578 return data_reader3<T>::read_parameter(GetParam(), type, subsection_name, subindex, parameter_name);
581 void print_and_store_results(const std::string &isa,
582 const std::string ¶meters,
583 const std::string &module_name,
584 const std::string &test_name,
585 const std::string &unit,
586 const int para_factor,
588 const double stddev);
592 \brief Run the given function and return the mean run time and stddev.
593 \param [in] function Function to benchmark.
594 \param [in] args Function's arguments.
595 \return std::pair where the first element is mean and the second one is standard deviation.
597 template <typename F, typename ... Args>
598 std::pair<double, double> run_benchmark(F function, Args ... args)
600 std::vector<long> results((unsigned long) BenchmarkParameters::repetition);
602 for(unsigned int outer_loop = 0; outer_loop < BenchmarkParameters::repetition; outer_loop++) {
603 const auto start_time = __rdtsc();
604 for (unsigned int inner_loop = 0; inner_loop < BenchmarkParameters::loop; inner_loop++) {
607 const auto end_time = __rdtsc();
608 results.push_back(end_time - start_time);
611 return calculate_statistics(results);
615 \brief Assert elements of two arrays. It calls ASSERT_EQ for each element of the array.
616 \param [in] reference Array with reference values.
617 \param [in] actual Array with the actual output.
618 \param [in] size Size of the array.
620 template <typename T>
621 void assert_array_eq(const T* reference, const T* actual, const int size)
623 for(int index = 0; index < size ; index++)
625 ASSERT_EQ(reference[index], actual[index])
626 <<"The wrong number is index: "<< index;
631 \brief Assert elements of two arrays. It calls ASSERT_NEAR for each element of the array.
632 \param [in] reference Array with reference values.
633 \param [in] actual Array with the actual output.
634 \param [in] size Size of the array.
635 \param [in] precision Precision fo the comparision used by ASSERT_NEAR.
637 template <typename T>
638 void assert_array_near(const T* reference, const T* actual, const int size, const double precision)
640 for(int index = 0; index < size ; index++)
642 ASSERT_NEAR(reference[index], actual[index], precision)
643 <<"The wrong number is index: "<< index;
648 void assert_array_near<complex_float>(const complex_float* reference, const complex_float* actual, const int size, const double precision)
650 for(int index = 0; index < size ; index++)
652 ASSERT_NEAR(reference[index].re, actual[index].re, precision)
653 <<"The wrong number is RE, index: "<< index;
654 ASSERT_NEAR(reference[index].im, actual[index].im, precision)
655 <<"The wrong number is IM, index: "<< index;
660 \brief Assert average diff of two arrays. It calls ASSERT_GT to check the average.
661 \param [in] reference Array with reference values, interleaved IQ inputs.
662 \param [in] actual Array with the actual output, interleaved IQ inputs.
663 \param [in] size Size of the array, based on complex inputs.
664 \param [in] precision Precision for the comparison used by ASSERT_GT.
667 void assert_avg_greater_complex(const T* reference, const T* actual, const int size, const double precision)
670 double avgMSEDB = 0.0;
671 for (int index = 0; index < size; index++) {
672 T refReal = reference[2*index];
673 T refImag = reference[(2*index)+1];
674 T resReal = actual[2*index];
675 T resImag = actual[(2*index)+1];
677 T errReal = resReal - refReal;
678 T errIm = resImag - refImag;
680 /* For some unit tests, e.g. PUCCH deomdulation, the expected output is 0. To avoid a
681 divide by zero error, check the reference results to determine if the expected result
682 is 0 and, if so, add a 1 to the division. */
683 if (refReal == 0 && refImag == 0)
684 MSE = (float)(errReal*errReal + errIm*errIm)/(float)(refReal*refReal + refImag*refImag + 1);
686 MSE = (float)(errReal*errReal + errIm*errIm)/(float)(refReal*refReal + refImag*refImag);
689 mseDB = (float)(-100.0);
691 mseDB = (float)(10.0) * (float)log10(MSE);
693 avgMSEDB += (double)mseDB;
698 ASSERT_GT(precision, avgMSEDB);
702 \brief Allocates memory of the given size.
704 aligned_malloc is wrapper to functions that allocate memory:
705 'rte_malloc' from DPDK if hugepages are defined, 'memalign' otherwise.
706 Size is defined as a number of variables of given type e.g. floats, rather than bytes.
707 It hides sizeof(T) multiplication and cast hence makes things cleaner.
709 \param [in] size Size of the memory to allocate.
710 \param [in] alignment Bytes alignment of the allocated memory. If 0, the return is a pointer
711 that is suitably aligned for any kind of variable (in the same manner as malloc()).
712 Otherwise, the return is a pointer that is a multiple of align. In this case,
713 it must be a power of two. (Minimum alignment is the cacheline size, i.e. 64-bytes)
714 \return Pointer to the allocated memory.
716 template <typename T>
717 T* aligned_malloc(const int size, const unsigned alignment)
720 return (T*) rte_malloc(NULL, sizeof(T) * size, alignment);
723 return (T*) memalign(alignment, sizeof(T) * size);
725 return (T*)_aligned_malloc(sizeof(T)*size, alignment);
731 \brief Frees memory pointed by the given pointer.
733 aligned_free is a wrapper for functions that free memory allocated by
734 aligned_malloc: 'rte_free' from DPDK if hugepages are defined and 'free' otherwise.
736 \param [in] ptr Pointer to the allocated memory.
738 template <typename T>
739 void aligned_free(T* ptr)
742 rte_free((void*)ptr);
748 _aligned_free((void *)ptr);
754 \brief generate random numbers.
756 It allocates memory and populate it with random numbers using C++11 default engine and
757 uniform real / int distribution (where lo_range <= x <up_range). Don't forget to free
760 \param [in] size Size of the memory to be filled with random data.
761 \param [in] alignment Bytes alignment of the memory.
762 \param [in] distribution Distribuiton for random generator.
763 \return Pointer to the allocated memory with random data.
765 template <typename T, typename U>
766 T* generate_random_numbers(const long size, const unsigned alignment, U& distribution)
768 auto array = (T*) aligned_malloc<char>(size * sizeof(T), alignment);
770 std::random_device random_device;
771 std::default_random_engine generator(random_device());
773 for(long i = 0; i < size; i++)
774 array[i] = (T)distribution(generator);
780 \brief generate random data.
782 It allocates memory and populate it with random data using C++11 default engine and
783 uniform integer distribution (bytes not floats are uniformly distributed). Don't forget
784 to free allocated memory!
786 \param [in] size Size of the memory to be filled with random data.
787 \param [in] alignment Bytes alignment of the memory.
788 \return Pointer to the allocated memory with random data.
790 template <typename T>
791 T* generate_random_data(const long size, const unsigned alignment)
793 std::uniform_int_distribution<> random(0, 255);
795 return (T*)generate_random_numbers<char, std::uniform_int_distribution<>>(size * sizeof(T), alignment, random);
799 \brief generate integer random numbers.
801 It allocates memory and populate it with random numbers using C++11 default engine and
802 uniform integer distribution (where lo_range <= x < up_range). Don't forget
803 to free allocated memory! The result type generated by the generator should be one of
806 \param [in] size Size of the memory to be filled with random data.
807 \param [in] alignment Bytes alignment of the memory.
808 \param [in] lo_range Lower bound of range of values returned by random generator.
809 \param [in] up_range Upper bound of range of values returned by random generator.
810 \return Pointer to the allocated memory with random data.
812 template <typename T>
813 T* generate_random_int_numbers(const long size, const unsigned alignment, const T lo_range,
816 std::uniform_int_distribution<T> random(lo_range, up_range);
818 return generate_random_numbers<T, std::uniform_int_distribution<T>>(size, alignment, random);
822 \brief generate real random numbers.
824 It allocates memory and populate it with random numbers using C++11 default engine and
825 uniform real distribution (where lo_range <= x <up_range). Don't forget to free
826 allocated memory! The result type generated by the generator should be one of
827 real types: float, double or long double.
829 \param [in] size Size of the memory to be filled with random data.
830 \param [in] alignment Bytes alignment of the memory.
831 \param [in] lo_range Lower bound of range of values returned by random generator.
832 \param [in] up_range Upper bound of range of values returned by random generator.
833 \return Pointer to the allocated memory with random data.
835 template <typename T>
836 T* generate_random_real_numbers(const long size, const unsigned alignment, const T lo_range,
839 std::uniform_real_distribution<T> distribution(lo_range, up_range);
841 return generate_random_numbers<T, std::uniform_real_distribution<T>>(size, alignment, distribution);
844 #endif //XRANLIB_COMMON_HPP