1 // Copyright (C) 2021 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <ditto/result.h>
16 #include <ditto/statistics.h>
17 #include <ditto/timespec_utils.h>
18
19 #include <algorithm>
20 #include <fstream>
21 #include <iomanip>
22 #include <iostream>
23 #include <set>
24 #include <string>
25
26 const int kSampleDisplayWidth = 16; // this width is used displaying a sample value
27 const int kTableWidth = 164; // table width; can be adjusted in case of longer instruction paths
28 const char* kTableDivider = " | "; // table character divider
29 const int kMaxHistogramHeight = 20; // used for normalizing the histogram (represents the
30 // maximum height of the histogram)
31 const int kMaxHistogramWidth = 50; // used for normalizing the histogram (represents the
32 // maximum width of the histogram)
33 const char kCsvDelimiter = ','; // delimiter used for .csv files
34 static int bin_size; // bin size corresponding to the normalization
35 // of the Oy axis of the histograms
36
37 namespace dittosuite {
38
Result(const std::string & name,const int repeat)39 Result::Result(const std::string& name, const int repeat) : name_(name), repeat_(repeat) {}
40
AddMeasurement(const std::string & name,const std::vector<double> & samples)41 void Result::AddMeasurement(const std::string& name, const std::vector<double>& samples) {
42 samples_[name] = samples;
43 AnalyseMeasurement(name);
44 }
45
AddSubResult(std::unique_ptr<Result> result)46 void Result::AddSubResult(std::unique_ptr<Result> result) {
47 sub_results_.push_back(std::move(result));
48 }
49
GetSamples(const std::string & measurement_name) const50 std::vector<double> Result::GetSamples(const std::string& measurement_name) const {
51 return samples_.find(measurement_name)->second;
52 }
53
GetRepeat() const54 int Result::GetRepeat() const {
55 return repeat_;
56 }
57
58 // analyse the measurement with the given name, and store
59 // the results in the statistics_ map
AnalyseMeasurement(const std::string & name)60 void Result::AnalyseMeasurement(const std::string& name) {
61 statistics_[name].min = StatisticsGetMin(samples_[name]);
62 statistics_[name].max = StatisticsGetMax(samples_[name]);
63 statistics_[name].mean = StatisticsGetMean(samples_[name]);
64 statistics_[name].median = StatisticsGetMedian(samples_[name]);
65 statistics_[name].sd = StatisticsGetSd(samples_[name]);
66 }
67
ComputeNextInstructionPath(const std::string & instruction_path)68 std::string Result::ComputeNextInstructionPath(const std::string& instruction_path) {
69 return instruction_path + (instruction_path != "" ? "/" : "") + name_;
70 }
71
Print(const ResultsOutput results_output,const std::string & instruction_path)72 void Result::Print(const ResultsOutput results_output, const std::string& instruction_path) {
73 switch (results_output) {
74 case ResultsOutput::kReport:
75 PrintHistograms(instruction_path);
76 PrintStatisticsTables();
77 break;
78 case ResultsOutput::kCsv:
79 MakeStatisticsCsv();
80 break;
81 case ResultsOutput::kNull:
82 break;
83 }
84 }
85
PrintTableBorder()86 void PrintTableBorder() {
87 std::cout << std::setfill('-') << std::setw(kTableWidth) << "" << std::setfill(' ');
88 std::cout << '\n';
89 }
90
PrintStatisticsTableHeader()91 void PrintStatisticsTableHeader() {
92 std::cout << "\x1b[1m"; // beginning of bold
93 std::cout << '\n';
94 PrintTableBorder();
95 std::cout << "| "; // beginning of table row
96 std::cout << std::setw(70) << std::left << "Instruction name";
97 std::cout << kTableDivider;
98 std::cout << std::setw(15) << std::right << " Min";
99 std::cout << kTableDivider;
100 std::cout << std::setw(15) << " Max";
101 std::cout << kTableDivider;
102 std::cout << std::setw(15) << " Mean";
103 std::cout << kTableDivider;
104 std::cout << std::setw(15) << " Median";
105 std::cout << kTableDivider;
106 std::cout << std::setw(15) << " SD";
107 std::cout << kTableDivider;
108 std::cout << '\n';
109 PrintTableBorder();
110 std::cout << "\x1b[0m"; // ending of bold
111 }
112
PrintMeasurementInTable(const int64_t & measurement,const std::string & measurement_name)113 void PrintMeasurementInTable(const int64_t& measurement, const std::string& measurement_name) {
114 if (measurement_name == "duration") {
115 std::cout << std::setw(13) << measurement << "ns";
116 } else if (measurement_name == "bandwidth") {
117 std::cout << std::setw(11) << measurement << "KB/s";
118 }
119 }
120
121 // Recursive function to print one row at a time
122 // of statistics table content (the instruction path, min, max and mean).
PrintStatisticsTableContent(const std::string & instruction_path,const std::string & measurement_name)123 void Result::PrintStatisticsTableContent(const std::string& instruction_path,
124 const std::string& measurement_name) {
125 std::string next_instruction_path = ComputeNextInstructionPath(instruction_path);
126 int subinstruction_level =
127 std::count(next_instruction_path.begin(), next_instruction_path.end(), '/');
128 // If the instruction path name contains too many subinstrions,
129 // print only the last 2 preceded by "../".
130 if (subinstruction_level > 2) {
131 std::size_t first_truncate_pos = next_instruction_path.find('/');
132 next_instruction_path = ".." + next_instruction_path.substr(first_truncate_pos);
133 }
134
135 // Print table row
136 if (samples_.find(measurement_name) != samples_.end()) {
137 std::cout << "| "; // started new row
138 std::cout << std::setw(70) << std::left << next_instruction_path << std::right;
139 std::cout << kTableDivider;
140 PrintMeasurementInTable(statistics_[measurement_name].min, measurement_name);
141 std::cout << kTableDivider;
142 PrintMeasurementInTable(statistics_[measurement_name].max, measurement_name);
143 std::cout << kTableDivider;
144 PrintMeasurementInTable(statistics_[measurement_name].mean, measurement_name);
145 std::cout << kTableDivider;
146 PrintMeasurementInTable(statistics_[measurement_name].median, measurement_name);
147 std::cout << kTableDivider;
148 std::cout << std::setw(15)
149 << statistics_[measurement_name].sd; // SD is always printed without measurement unit
150 std::cout << kTableDivider; // ended current row
151 std::cout << '\n';
152 PrintTableBorder();
153 }
154
155 for (const auto& sub_result : sub_results_) {
156 sub_result->PrintStatisticsTableContent(next_instruction_path, measurement_name);
157 }
158 }
159
GetMeasurementsNames()160 std::set<std::string> Result::GetMeasurementsNames() {
161 std::set<std::string> names;
162 for (const auto& it : samples_) names.insert(it.first);
163 for (const auto& sub_result : sub_results_) {
164 for (const auto& sub_name : sub_result->GetMeasurementsNames()) names.insert(sub_name);
165 }
166 return names;
167 }
168
PrintStatisticsTables()169 void Result::PrintStatisticsTables() {
170 std::set<std::string> measurement_names = GetMeasurementsNames();
171 for (const auto& s : measurement_names) {
172 std::cout << s << " statistics:";
173 PrintStatisticsTableHeader();
174 PrintStatisticsTableContent("", s);
175 std::cout << '\n';
176 }
177 }
178
PrintHistogramHeader(const std::string & measurement_name)179 void Result::PrintHistogramHeader(const std::string& measurement_name) {
180 if (measurement_name == "duration") {
181 std::cout.width(kSampleDisplayWidth - 3);
182 std::cout << "Time(" << time_unit_.name << ") |";
183 std::cout << " Normalized number of time samples\n";
184 } else if (measurement_name == "bandwidth") {
185 std::cout.width(kSampleDisplayWidth - 6);
186 std::cout << "Bandwidth(" << bandwidth_unit_.name << ") |";
187 std::cout << " Normalized number of bandwidth samples\n";
188 }
189 std::cout << std::setfill('-') << std::setw(kMaxHistogramWidth) << "" << std::setfill(' ');
190 std::cout << '\n';
191 }
192
193 // makes (normalized) histogram from vector
MakeHistogramFromVector(const std::vector<int> & freq_vector,const int min_value)194 void Result::MakeHistogramFromVector(const std::vector<int>& freq_vector, const int min_value) {
195 int sum = 0;
196 int max_frequency = *std::max_element(freq_vector.begin(), freq_vector.end());
197 for (std::size_t i = 0; i < freq_vector.size(); i++) {
198 std::cout.width(kSampleDisplayWidth);
199 std::cout << min_value + bin_size * i << kTableDivider;
200
201 int hist_width = ceil(static_cast<double>(freq_vector[i]) * kMaxHistogramWidth / max_frequency);
202 std::cout << std::setfill('#') << std::setw(hist_width) << "" << std::setfill(' ');
203
204 std::cout << " { " << freq_vector[i] << " }\n";
205
206 sum += freq_vector[i];
207 }
208
209 std::cout << '\n';
210 std::cout << "Total samples: { " << sum << " }\n";
211 }
212
213 // makes and returns the normalized frequency vector
ComputeNormalizedFrequencyVector(const std::string & measurement_name)214 std::vector<int> Result::ComputeNormalizedFrequencyVector(const std::string& measurement_name) {
215 int64_t min_value = statistics_[measurement_name].min;
216 if (measurement_name == "duration") {
217 min_value /= time_unit_.dividing_factor;
218 } else if (measurement_name == "bandwidth") {
219 min_value /= bandwidth_unit_.dividing_factor;
220 }
221
222 std::vector<int> freq_vector(kMaxHistogramHeight, 0);
223 for (const auto& sample : samples_[measurement_name]) {
224 int64_t sample_copy = sample;
225 if (measurement_name == "duration") {
226 sample_copy /= time_unit_.dividing_factor;
227 } else if (measurement_name == "bandwidth") {
228 sample_copy /= bandwidth_unit_.dividing_factor;
229 }
230 int64_t bin = (sample_copy - min_value) / bin_size;
231
232 freq_vector[bin]++;
233 }
234 return freq_vector;
235 }
236
GetTimeUnit(const int64_t min_value)237 Result::TimeUnit Result::GetTimeUnit(const int64_t min_value) {
238 TimeUnit result;
239 if (min_value <= 1e7) {
240 // time unit in nanoseconds
241 result.dividing_factor = 1;
242 result.name = "ns";
243 } else if (min_value <= 1e10) {
244 // time unit in microseconds
245 result.dividing_factor = 1e3;
246 result.name = "us";
247 } else if (min_value <= 1e13) {
248 // time unit in milliseconds
249 result.dividing_factor = 1e6;
250 result.name = "ms";
251 } else {
252 // time unit in seconds
253 result.dividing_factor = 1e9;
254 result.name = "s";
255 }
256 return result;
257 }
258
GetBandwidthUnit(const int64_t min_value)259 Result::BandwidthUnit Result::GetBandwidthUnit(const int64_t min_value) {
260 BandwidthUnit result;
261 if (min_value <= (1 << 15)) {
262 // bandwidth unit in KB/s
263 result.dividing_factor = 1;
264 result.name = "KiB/s";
265 } else if (min_value <= (1 << 25)) {
266 // bandwidth unit in MB/s
267 result.dividing_factor = 1 << 10;
268 result.name = "MiB/s";
269 } else {
270 // bandwidth unit in GB/s
271 result.dividing_factor = 1 << 20;
272 result.name = "GiB/s";
273 }
274 return result;
275 }
276
PrintHistograms(const std::string & instruction_path)277 void Result::PrintHistograms(const std::string& instruction_path) {
278 std::string next_instruction_path = ComputeNextInstructionPath(instruction_path);
279 std::cout << "\x1b[1m"; // beginning of bold
280 std::cout << "Instruction path: " << next_instruction_path;
281 std::cout << "\x1b[0m"; // ending of bold
282 std::cout << "\n\n";
283
284 for (const auto& sample : samples_) {
285 int64_t min_value = statistics_[sample.first].min;
286 int64_t max_value = statistics_[sample.first].max;
287 if (sample.first == "duration") {
288 time_unit_ = GetTimeUnit(statistics_[sample.first].min);
289 min_value /= time_unit_.dividing_factor;
290 max_value /= time_unit_.dividing_factor;
291 } else if (sample.first == "bandwidth") {
292 bandwidth_unit_ = GetBandwidthUnit(min_value);
293 min_value /= bandwidth_unit_.dividing_factor;
294 max_value /= bandwidth_unit_.dividing_factor;
295 }
296 bin_size = (max_value - min_value) / kMaxHistogramHeight + 1;
297
298 std::vector<int> freq_vector = ComputeNormalizedFrequencyVector(sample.first);
299 PrintHistogramHeader(sample.first);
300 MakeHistogramFromVector(freq_vector, min_value);
301 std::cout << "\n\n";
302
303 for (const auto& sub_result : sub_results_) {
304 sub_result->PrintHistograms(next_instruction_path);
305 }
306 }
307 }
308
309 // Print statistic measurement with given name in .csv
PrintMeasurementStatisticInCsv(std::ostream & csv_stream,const std::string & name)310 void Result::PrintMeasurementStatisticInCsv(std::ostream& csv_stream, const std::string& name) {
311 csv_stream << kCsvDelimiter;
312 csv_stream << statistics_[name].min << kCsvDelimiter;
313 csv_stream << statistics_[name].max << kCsvDelimiter;
314 csv_stream << statistics_[name].mean << kCsvDelimiter;
315 csv_stream << statistics_[name].median << kCsvDelimiter;
316 csv_stream << statistics_[name].sd;
317 }
318
PrintEmptyMeasurementInCsv(std::ostream & csv_stream)319 void PrintEmptyMeasurementInCsv(std::ostream& csv_stream) {
320 csv_stream << std::setfill(kCsvDelimiter) << std::setw(5) << "" << std::setfill(' ');
321 }
322
323 // Recursive function to print one row at a time using the .csv stream given as a parameter
324 // of statistics table content (the instruction path, min, max, mean and SD).
PrintStatisticInCsv(std::ostream & csv_stream,const std::string & instruction_path,const std::set<std::string> & measurements_names)325 void Result::PrintStatisticInCsv(std::ostream& csv_stream, const std::string& instruction_path,
326 const std::set<std::string>& measurements_names) {
327 std::string next_instruction_path = ComputeNextInstructionPath(instruction_path);
328
329 // print one row in csv
330 csv_stream << next_instruction_path;
331 for (const auto& measurement : measurements_names) {
332 if (samples_.find(measurement) != samples_.end()) {
333 PrintMeasurementStatisticInCsv(csv_stream, measurement);
334 } else {
335 PrintEmptyMeasurementInCsv(csv_stream);
336 }
337 }
338 csv_stream << '\n';
339
340 for (const auto& sub_result : sub_results_) {
341 sub_result->PrintStatisticInCsv(csv_stream, next_instruction_path, measurements_names);
342 }
343 }
344
PrintCsvHeader(std::ostream & csv_stream,const std::set<std::string> & measurement_names)345 void PrintCsvHeader(std::ostream& csv_stream, const std::set<std::string>& measurement_names) {
346 csv_stream << "Instruction path";
347 for (const auto& measurement : measurement_names) {
348 csv_stream << kCsvDelimiter;
349 csv_stream << measurement << " min" << kCsvDelimiter;
350 csv_stream << measurement << " max" << kCsvDelimiter;
351 csv_stream << measurement << " mean" << kCsvDelimiter;
352 csv_stream << measurement << " median" << kCsvDelimiter;
353 csv_stream << measurement << " SD";
354 }
355 csv_stream << '\n';
356 }
357
MakeStatisticsCsv()358 void Result::MakeStatisticsCsv() {
359 std::ostream csv_stream(std::cout.rdbuf());
360
361 std::set<std::string> measurements_names = GetMeasurementsNames();
362 PrintCsvHeader(csv_stream, measurements_names);
363
364 PrintStatisticInCsv(csv_stream, "", measurements_names);
365 }
366
367 } // namespace dittosuite
368