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 <google/protobuf/util/json_util.h>
20 
21 #include <algorithm>
22 #include <fstream>
23 #include <iomanip>
24 #include <iostream>
25 #include <set>
26 #include <string>
27 
28 const int kSampleDisplayWidth = 16;  // this width is used displaying a sample value
29 const int kTableWidth = 164;  // table width; can be adjusted in case of longer instruction paths
30 const char* kTableDivider = " | ";   // table character divider
31 const int kMaxHistogramHeight = 20;  // used for normalizing the histogram (represents the
32                                      //  maximum height of the histogram)
33 const int kMaxHistogramWidth = 50;   // used for normalizing the histogram (represents the
34                                      // maximum width of the histogram)
35 const char kCsvDelimiter = ',';      // delimiter used for .csv files
36 static int bin_size;                 // bin size corresponding to the normalization
37                                      // of the Oy axis of the histograms
38 
39 namespace dittosuite {
40 
Result(const std::string & name,const int repeat)41 Result::Result(const std::string& name, const int repeat) : name_(name), repeat_(repeat) {}
42 
AddMeasurement(const std::string & name,const std::vector<double> & samples)43 void Result::AddMeasurement(const std::string& name, const std::vector<double>& samples) {
44   samples_[name] = samples;
45   AnalyseMeasurement(name);
46 }
47 
AddSubResult(std::unique_ptr<Result> result)48 void Result::AddSubResult(std::unique_ptr<Result> result) {
49   sub_results_.push_back(std::move(result));
50 }
51 
GetSamples(const std::string & measurement_name) const52 std::vector<double> Result::GetSamples(const std::string& measurement_name) const {
53   return samples_.find(measurement_name)->second;
54 }
55 
GetRepeat() const56 int Result::GetRepeat() const {
57   return repeat_;
58 }
59 
60 // analyse the measurement with the given name, and store
61 // the results in the statistics_ map
AnalyseMeasurement(const std::string & name)62 void Result::AnalyseMeasurement(const std::string& name) {
63   statistics_[name].min = StatisticsGetMin(samples_[name]);
64   statistics_[name].max = StatisticsGetMax(samples_[name]);
65   statistics_[name].mean = StatisticsGetMean(samples_[name]);
66   statistics_[name].median = StatisticsGetMedian(samples_[name]);
67   statistics_[name].sd = StatisticsGetSd(samples_[name]);
68 }
69 
ComputeNextInstructionPath(const std::string & instruction_path)70 std::string Result::ComputeNextInstructionPath(const std::string& instruction_path) {
71   return instruction_path + (instruction_path != "" ? "/" : "") + name_;
72 }
73 
Print(const ResultsOutput results_output,const std::string & instruction_path)74 void Result::Print(const ResultsOutput results_output, const std::string& instruction_path) {
75   switch (results_output) {
76     case ResultsOutput::kReport:
77       PrintHistograms(instruction_path);
78       PrintStatisticsTables();
79       break;
80     case ResultsOutput::kCsv:
81       MakeStatisticsCsv();
82       break;
83     case ResultsOutput::kPb:
84       PrintPb(ToPb());
85       break;
86     case ResultsOutput::kNull:
87       break;
88   }
89 }
90 
PrintTableBorder()91 void PrintTableBorder() {
92   std::cout << std::setfill('-') << std::setw(kTableWidth) << "" << std::setfill(' ');
93   std::cout << '\n';
94 }
95 
PrintStatisticsTableHeader()96 void PrintStatisticsTableHeader() {
97   std::cout << "\x1b[1m";  // beginning of bold
98   std::cout << '\n';
99   PrintTableBorder();
100   std::cout << "| ";  // beginning of table row
101   std::cout << std::setw(70) << std::left << "Instruction name";
102   std::cout << kTableDivider;
103   std::cout << std::setw(15) << std::right << " Min";
104   std::cout << kTableDivider;
105   std::cout << std::setw(15) << " Max";
106   std::cout << kTableDivider;
107   std::cout << std::setw(15) << " Mean";
108   std::cout << kTableDivider;
109   std::cout << std::setw(15) << " Median";
110   std::cout << kTableDivider;
111   std::cout << std::setw(15) << " SD";
112   std::cout << kTableDivider;
113   std::cout << '\n';
114   PrintTableBorder();
115   std::cout << "\x1b[0m";  // ending of bold
116 }
117 
PrintMeasurementInTable(const int64_t & measurement,const std::string & measurement_name)118 void PrintMeasurementInTable(const int64_t& measurement, const std::string& measurement_name) {
119   if (measurement_name == "duration") {
120     std::cout << std::setw(13) << measurement << "ns";
121   } else if (measurement_name == "bandwidth") {
122     std::cout << std::setw(11) << measurement << "KB/s";
123   }
124 }
125 
126 // Recursive function to print one row at a time
127 // of statistics table content (the instruction path, min, max and mean).
PrintStatisticsTableContent(const std::string & instruction_path,const std::string & measurement_name)128 void Result::PrintStatisticsTableContent(const std::string& instruction_path,
129                                          const std::string& measurement_name) {
130   std::string next_instruction_path = ComputeNextInstructionPath(instruction_path);
131   int subinstruction_level =
132       std::count(next_instruction_path.begin(), next_instruction_path.end(), '/');
133   // If the instruction path name contains too many subinstrions,
134   // print only the last 2 preceded by "../".
135   if (subinstruction_level > 2) {
136     std::size_t first_truncate_pos = next_instruction_path.find('/');
137     next_instruction_path = ".." + next_instruction_path.substr(first_truncate_pos);
138   }
139 
140   // Print table row
141   if (samples_.find(measurement_name) != samples_.end()) {
142     std::cout << "| ";  // started new row
143     std::cout << std::setw(70) << std::left << next_instruction_path << std::right;
144     std::cout << kTableDivider;
145     PrintMeasurementInTable(statistics_[measurement_name].min, measurement_name);
146     std::cout << kTableDivider;
147     PrintMeasurementInTable(statistics_[measurement_name].max, measurement_name);
148     std::cout << kTableDivider;
149     PrintMeasurementInTable(statistics_[measurement_name].mean, measurement_name);
150     std::cout << kTableDivider;
151     PrintMeasurementInTable(statistics_[measurement_name].median, measurement_name);
152     std::cout << kTableDivider;
153     std::cout << std::setw(15)
154               << statistics_[measurement_name].sd;  // SD is always printed without measurement unit
155     std::cout << kTableDivider;                     // ended current row
156     std::cout << '\n';
157     PrintTableBorder();
158   }
159 
160   for (const auto& sub_result : sub_results_) {
161     sub_result->PrintStatisticsTableContent(next_instruction_path, measurement_name);
162   }
163 }
164 
GetMeasurementsNames()165 std::set<std::string> Result::GetMeasurementsNames() {
166   std::set<std::string> names;
167 
168   for (const auto& it : samples_) {
169     names.insert(it.first);
170   }
171   for (const auto& sub_result : sub_results_) {
172     for (const auto& sub_name : sub_result->GetMeasurementsNames()) {
173       names.insert(sub_name);
174     }
175   }
176 
177   return names;
178 }
179 
PrintStatisticsTables()180 void Result::PrintStatisticsTables() {
181   std::set<std::string> measurement_names = GetMeasurementsNames();
182   for (const auto& s : measurement_names) {
183     std::cout << s << " statistics:";
184     PrintStatisticsTableHeader();
185     PrintStatisticsTableContent("", s);
186     std::cout << '\n';
187   }
188 }
189 
PrintHistogramHeader(const std::string & measurement_name)190 void Result::PrintHistogramHeader(const std::string& measurement_name) {
191   if (measurement_name == "duration") {
192     std::cout.width(kSampleDisplayWidth - 3);
193     std::cout << "Time(" << time_unit_.name << ") |";
194     std::cout << " Normalized number of time samples\n";
195   } else if (measurement_name == "bandwidth") {
196     std::cout.width(kSampleDisplayWidth - 6);
197     std::cout << "Bandwidth(" << bandwidth_unit_.name << ") |";
198     std::cout << " Normalized number of bandwidth samples\n";
199   }
200   std::cout << std::setfill('-') << std::setw(kMaxHistogramWidth) << "" << std::setfill(' ');
201   std::cout << '\n';
202 }
203 
204 // makes (normalized) histogram from vector
MakeHistogramFromVector(const std::vector<int> & freq_vector,const int min_value)205 void Result::MakeHistogramFromVector(const std::vector<int>& freq_vector, const int min_value) {
206   int sum = 0;
207   int max_frequency = *std::max_element(freq_vector.begin(), freq_vector.end());
208   for (std::size_t i = 0; i < freq_vector.size(); i++) {
209     std::cout.width(kSampleDisplayWidth);
210     std::cout << min_value + bin_size * i << kTableDivider;
211 
212     int hist_width = ceil(static_cast<double>(freq_vector[i]) * kMaxHistogramWidth / max_frequency);
213     std::cout << std::setfill('#') << std::setw(hist_width) << "" << std::setfill(' ');
214 
215     std::cout << " { " << freq_vector[i] << " }\n";
216 
217     sum += freq_vector[i];
218   }
219 
220   std::cout << '\n';
221   std::cout << "Total samples: { " << sum << " }\n";
222 }
223 
224 // makes and returns the normalized frequency vector
ComputeNormalizedFrequencyVector(const std::string & measurement_name)225 std::vector<int> Result::ComputeNormalizedFrequencyVector(const std::string& measurement_name) {
226   int64_t min_value = statistics_[measurement_name].min;
227   if (measurement_name == "duration") {
228     min_value /= time_unit_.dividing_factor;
229   } else if (measurement_name == "bandwidth") {
230     min_value /= bandwidth_unit_.dividing_factor;
231   }
232 
233   std::vector<int> freq_vector(kMaxHistogramHeight, 0);
234   for (const auto& sample : samples_[measurement_name]) {
235     int64_t sample_copy = sample;
236     if (measurement_name == "duration") {
237       sample_copy /= time_unit_.dividing_factor;
238     } else if (measurement_name == "bandwidth") {
239       sample_copy /= bandwidth_unit_.dividing_factor;
240     }
241     int64_t bin = (sample_copy - min_value) / bin_size;
242 
243     freq_vector[bin]++;
244   }
245   return freq_vector;
246 }
247 
GetTimeUnit(const int64_t min_value)248 Result::TimeUnit Result::GetTimeUnit(const int64_t min_value) {
249   TimeUnit result;
250   if (min_value <= 1e7) {
251     // time unit in nanoseconds
252     result.dividing_factor = 1;
253     result.name = "ns";
254   } else if (min_value <= 1e10) {
255     // time unit in microseconds
256     result.dividing_factor = 1e3;
257     result.name = "us";
258   } else if (min_value <= 1e13) {
259     // time unit in milliseconds
260     result.dividing_factor = 1e6;
261     result.name = "ms";
262   } else {
263     // time unit in seconds
264     result.dividing_factor = 1e9;
265     result.name = "s";
266   }
267   return result;
268 }
269 
GetBandwidthUnit(const int64_t min_value)270 Result::BandwidthUnit Result::GetBandwidthUnit(const int64_t min_value) {
271   BandwidthUnit result;
272   if (min_value <= (1 << 15)) {
273     // bandwidth unit in KB/s
274     result.dividing_factor = 1;
275     result.name = "KiB/s";
276   } else if (min_value <= (1 << 25)) {
277     // bandwidth unit in MB/s
278     result.dividing_factor = 1 << 10;
279     result.name = "MiB/s";
280   } else {
281     // bandwidth unit in GB/s
282     result.dividing_factor = 1 << 20;
283     result.name = "GiB/s";
284   }
285   return result;
286 }
287 
PrintHistograms(const std::string & instruction_path)288 void Result::PrintHistograms(const std::string& instruction_path) {
289   std::string next_instruction_path = ComputeNextInstructionPath(instruction_path);
290   std::cout << "\x1b[1m";  // beginning of bold
291   std::cout << "Instruction path: " << next_instruction_path;
292   std::cout << "\x1b[0m";  // ending of bold
293   std::cout << "\n\n";
294 
295   for (const auto& sample : samples_) {
296     int64_t min_value = statistics_[sample.first].min;
297     int64_t max_value = statistics_[sample.first].max;
298     if (sample.first == "duration") {
299       time_unit_ = GetTimeUnit(statistics_[sample.first].min);
300       min_value /= time_unit_.dividing_factor;
301       max_value /= time_unit_.dividing_factor;
302     } else if (sample.first == "bandwidth") {
303       bandwidth_unit_ = GetBandwidthUnit(min_value);
304       min_value /= bandwidth_unit_.dividing_factor;
305       max_value /= bandwidth_unit_.dividing_factor;
306     }
307     bin_size = (max_value - min_value) / kMaxHistogramHeight + 1;
308 
309     std::vector<int> freq_vector = ComputeNormalizedFrequencyVector(sample.first);
310     PrintHistogramHeader(sample.first);
311     MakeHistogramFromVector(freq_vector, min_value);
312     std::cout << "\n\n";
313 
314     for (const auto& sub_result : sub_results_) {
315       sub_result->PrintHistograms(next_instruction_path);
316     }
317   }
318 }
319 
320 // Print statistic measurement with given name in .csv
PrintMeasurementStatisticInCsv(std::ostream & csv_stream,const std::string & name)321 void Result::PrintMeasurementStatisticInCsv(std::ostream& csv_stream, const std::string& name) {
322   csv_stream << kCsvDelimiter;
323   csv_stream << statistics_[name].min << kCsvDelimiter;
324   csv_stream << statistics_[name].max << kCsvDelimiter;
325   csv_stream << statistics_[name].mean << kCsvDelimiter;
326   csv_stream << statistics_[name].median << kCsvDelimiter;
327   csv_stream << statistics_[name].sd;
328 }
329 
PrintEmptyMeasurementInCsv(std::ostream & csv_stream)330 void PrintEmptyMeasurementInCsv(std::ostream& csv_stream) {
331   csv_stream << std::setfill(kCsvDelimiter) << std::setw(5) << "" << std::setfill(' ');
332 }
333 
334 // Recursive function to print one row at a time using the .csv stream given as a parameter
335 // 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)336 void Result::PrintStatisticInCsv(std::ostream& csv_stream, const std::string& instruction_path,
337                                  const std::set<std::string>& measurements_names) {
338   std::string next_instruction_path = ComputeNextInstructionPath(instruction_path);
339 
340   // print one row in csv
341   csv_stream << next_instruction_path;
342   for (const auto& measurement : measurements_names) {
343     if (statistics_.find(measurement) != statistics_.end()) {
344       PrintMeasurementStatisticInCsv(csv_stream, measurement);
345     } else {
346       PrintEmptyMeasurementInCsv(csv_stream);
347     }
348   }
349   csv_stream << '\n';
350 
351   for (const auto& sub_result : sub_results_) {
352     sub_result->PrintStatisticInCsv(csv_stream, next_instruction_path, measurements_names);
353   }
354 }
355 
PrintCsvHeader(std::ostream & csv_stream,const std::set<std::string> & measurement_names)356 void PrintCsvHeader(std::ostream& csv_stream, const std::set<std::string>& measurement_names) {
357   csv_stream << "Instruction path";
358   for (const auto& measurement : measurement_names) {
359     csv_stream << kCsvDelimiter;
360     csv_stream << measurement << " min" << kCsvDelimiter;
361     csv_stream << measurement << " max" << kCsvDelimiter;
362     csv_stream << measurement << " mean" << kCsvDelimiter;
363     csv_stream << measurement << " median" << kCsvDelimiter;
364     csv_stream << measurement << " SD";
365   }
366   csv_stream << '\n';
367 }
368 
MakeStatisticsCsv()369 void Result::MakeStatisticsCsv() {
370   std::ostream csv_stream(std::cout.rdbuf());
371 
372   std::set<std::string> measurements_names = GetMeasurementsNames();
373   PrintCsvHeader(csv_stream, measurements_names);
374 
375   PrintStatisticInCsv(csv_stream, "", measurements_names);
376 }
377 
StoreStatisticsInPb(dittosuiteproto::Metrics * metrics,const std::string & name)378 void Result::StoreStatisticsInPb(dittosuiteproto::Metrics* metrics,
379                                            const std::string& name) {
380   metrics->set_name(name);
381   metrics->set_min(statistics_[name].min);
382   metrics->set_max(statistics_[name].max);
383   metrics->set_mean(statistics_[name].mean);
384   metrics->set_median(statistics_[name].median);
385   metrics->set_sd(statistics_[name].sd);
386 }
387 
__ToPb(dittosuiteproto::Result * result_pb)388 void Result::__ToPb(dittosuiteproto::Result* result_pb) {
389   result_pb->set_name(name_);
390 
391 
392   for (const auto &stats : statistics_) {
393       StoreStatisticsInPb(result_pb->add_metrics(), stats.first);
394   }
395 
396   for (const auto& sub_result : sub_results_) {
397     sub_result->__ToPb(result_pb->add_sub_result());
398   }
399 }
400 
ToPb()401 dittosuiteproto::Result Result::ToPb() {
402   dittosuiteproto::Result result_pb;
403   std::set<std::string> measurements_names = GetMeasurementsNames();
404 
405   __ToPb(&result_pb);
406 
407   return result_pb;
408 }
409 
SetStatistics(const std::string & name,const Result::Statistics & statistics)410 void Result::SetStatistics(const std::string& name, const Result::Statistics& statistics) {
411   statistics_[name] = statistics;
412 }
413 
PrintPb(const dittosuiteproto::Result & pb)414 void PrintPb(const dittosuiteproto::Result &pb) {
415   std::string json;
416   google::protobuf::util::JsonPrintOptions options;
417 
418   options.add_whitespace = true;
419   auto status = google::protobuf::util::MessageToJsonString(pb, &json, options);
420   if (status.ok()) {
421     std::ostream pb_stream(std::cout.rdbuf());
422     pb_stream << json << std::endl;
423   }
424 }
425 
FromPb(const dittosuiteproto::Result & pb)426 std::unique_ptr<Result> Result::FromPb(const dittosuiteproto::Result& pb) {
427   auto result = std::make_unique<Result>(pb.name(), 1);
428 
429   for (const auto& m : pb.metrics()) {
430     Result::Statistics stats = {
431         .min = m.min(), .max = m.max(), .mean = m.mean(), .median = m.median(), .sd = m.sd()};
432     result->SetStatistics(m.name(), stats);
433   }
434 
435   for (const auto& r : pb.sub_result()) {
436     result->AddSubResult(Result::FromPb(r));
437   }
438 
439   return result;
440 }
441 
442 }  // namespace dittosuite
443