• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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