1 | // Copyright 2015 Google Inc. All rights reserved. |
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 <algorithm> |
16 | #include <cmath> |
17 | #include <cstdint> |
18 | #include <iomanip> // for setprecision |
19 | #include <iostream> |
20 | #include <limits> |
21 | #include <string> |
22 | #include <tuple> |
23 | #include <vector> |
24 | |
25 | #include "benchmark/benchmark.h" |
26 | #include "complexity.h" |
27 | #include "string_util.h" |
28 | #include "timers.h" |
29 | |
30 | namespace benchmark { |
31 | namespace internal { |
32 | extern std::map<std::string, std::string>* global_context; |
33 | } |
34 | |
35 | namespace { |
36 | |
37 | std::string StrEscape(const std::string& s) { |
38 | std::string tmp; |
39 | tmp.reserve(s.size()); |
40 | for (char c : s) { |
41 | switch (c) { |
42 | case '\b': |
43 | tmp += "\\b" ; |
44 | break; |
45 | case '\f': |
46 | tmp += "\\f" ; |
47 | break; |
48 | case '\n': |
49 | tmp += "\\n" ; |
50 | break; |
51 | case '\r': |
52 | tmp += "\\r" ; |
53 | break; |
54 | case '\t': |
55 | tmp += "\\t" ; |
56 | break; |
57 | case '\\': |
58 | tmp += "\\\\" ; |
59 | break; |
60 | case '"': |
61 | tmp += "\\\"" ; |
62 | break; |
63 | default: |
64 | tmp += c; |
65 | break; |
66 | } |
67 | } |
68 | return tmp; |
69 | } |
70 | |
71 | std::string FormatKV(std::string const& key, std::string const& value) { |
72 | return StrFormat("\"%s\": \"%s\"" , StrEscape(key).c_str(), |
73 | StrEscape(value).c_str()); |
74 | } |
75 | |
76 | std::string FormatKV(std::string const& key, const char* value) { |
77 | return StrFormat("\"%s\": \"%s\"" , StrEscape(key).c_str(), |
78 | StrEscape(value).c_str()); |
79 | } |
80 | |
81 | std::string FormatKV(std::string const& key, bool value) { |
82 | return StrFormat("\"%s\": %s" , StrEscape(key).c_str(), |
83 | value ? "true" : "false" ); |
84 | } |
85 | |
86 | std::string FormatKV(std::string const& key, int64_t value) { |
87 | std::stringstream ss; |
88 | ss << '"' << StrEscape(key) << "\": " << value; |
89 | return ss.str(); |
90 | } |
91 | |
92 | std::string FormatKV(std::string const& key, IterationCount value) { |
93 | std::stringstream ss; |
94 | ss << '"' << StrEscape(key) << "\": " << value; |
95 | return ss.str(); |
96 | } |
97 | |
98 | std::string FormatKV(std::string const& key, double value) { |
99 | std::stringstream ss; |
100 | ss << '"' << StrEscape(key) << "\": " ; |
101 | |
102 | if (std::isnan(value)) |
103 | ss << (value < 0 ? "-" : "" ) << "NaN" ; |
104 | else if (std::isinf(value)) |
105 | ss << (value < 0 ? "-" : "" ) << "Infinity" ; |
106 | else { |
107 | const auto max_digits10 = |
108 | std::numeric_limits<decltype(value)>::max_digits10; |
109 | const auto max_fractional_digits10 = max_digits10 - 1; |
110 | ss << std::scientific << std::setprecision(max_fractional_digits10) |
111 | << value; |
112 | } |
113 | return ss.str(); |
114 | } |
115 | |
116 | int64_t RoundDouble(double v) { return std::lround(v); } |
117 | |
118 | } // end namespace |
119 | |
120 | bool JSONReporter::ReportContext(const Context& context) { |
121 | std::ostream& out = GetOutputStream(); |
122 | |
123 | out << "{\n" ; |
124 | std::string inner_indent(2, ' '); |
125 | |
126 | // Open context block and print context information. |
127 | out << inner_indent << "\"context\": {\n" ; |
128 | std::string indent(4, ' '); |
129 | |
130 | std::string walltime_value = LocalDateTimeString(); |
131 | out << indent << FormatKV("date" , walltime_value) << ",\n" ; |
132 | |
133 | out << indent << FormatKV("host_name" , context.sys_info.name) << ",\n" ; |
134 | |
135 | if (Context::executable_name) { |
136 | out << indent << FormatKV("executable" , Context::executable_name) << ",\n" ; |
137 | } |
138 | |
139 | CPUInfo const& info = context.cpu_info; |
140 | out << indent << FormatKV("num_cpus" , static_cast<int64_t>(info.num_cpus)) |
141 | << ",\n" ; |
142 | out << indent |
143 | << FormatKV("mhz_per_cpu" , |
144 | RoundDouble(info.cycles_per_second / 1000000.0)) |
145 | << ",\n" ; |
146 | if (CPUInfo::Scaling::UNKNOWN != info.scaling) { |
147 | out << indent |
148 | << FormatKV("cpu_scaling_enabled" , |
149 | info.scaling == CPUInfo::Scaling::ENABLED ? true : false) |
150 | << ",\n" ; |
151 | } |
152 | |
153 | out << indent << "\"caches\": [\n" ; |
154 | indent = std::string(6, ' '); |
155 | std::string cache_indent(8, ' '); |
156 | for (size_t i = 0; i < info.caches.size(); ++i) { |
157 | auto& CI = info.caches[i]; |
158 | out << indent << "{\n" ; |
159 | out << cache_indent << FormatKV("type" , CI.type) << ",\n" ; |
160 | out << cache_indent << FormatKV("level" , static_cast<int64_t>(CI.level)) |
161 | << ",\n" ; |
162 | out << cache_indent << FormatKV("size" , static_cast<int64_t>(CI.size)) |
163 | << ",\n" ; |
164 | out << cache_indent |
165 | << FormatKV("num_sharing" , static_cast<int64_t>(CI.num_sharing)) |
166 | << "\n" ; |
167 | out << indent << "}" ; |
168 | if (i != info.caches.size() - 1) out << "," ; |
169 | out << "\n" ; |
170 | } |
171 | indent = std::string(4, ' '); |
172 | out << indent << "],\n" ; |
173 | out << indent << "\"load_avg\": [" ; |
174 | for (auto it = info.load_avg.begin(); it != info.load_avg.end();) { |
175 | out << *it++; |
176 | if (it != info.load_avg.end()) out << "," ; |
177 | } |
178 | out << "],\n" ; |
179 | |
180 | #if defined(NDEBUG) |
181 | const char build_type[] = "release" ; |
182 | #else |
183 | const char build_type[] = "debug" ; |
184 | #endif |
185 | out << indent << FormatKV("library_build_type" , build_type); |
186 | |
187 | if (internal::global_context != nullptr) { |
188 | for (const auto& kv : *internal::global_context) { |
189 | out << ",\n" ; |
190 | out << indent << FormatKV(kv.first, kv.second); |
191 | } |
192 | } |
193 | out << "\n" ; |
194 | |
195 | // Close context block and open the list of benchmarks. |
196 | out << inner_indent << "},\n" ; |
197 | out << inner_indent << "\"benchmarks\": [\n" ; |
198 | return true; |
199 | } |
200 | |
201 | void JSONReporter::ReportRuns(std::vector<Run> const& reports) { |
202 | if (reports.empty()) { |
203 | return; |
204 | } |
205 | std::string indent(4, ' '); |
206 | std::ostream& out = GetOutputStream(); |
207 | if (!first_report_) { |
208 | out << ",\n" ; |
209 | } |
210 | first_report_ = false; |
211 | |
212 | for (auto it = reports.begin(); it != reports.end(); ++it) { |
213 | out << indent << "{\n" ; |
214 | PrintRunData(*it); |
215 | out << indent << '}'; |
216 | auto it_cp = it; |
217 | if (++it_cp != reports.end()) { |
218 | out << ",\n" ; |
219 | } |
220 | } |
221 | } |
222 | |
223 | void JSONReporter::Finalize() { |
224 | // Close the list of benchmarks and the top level object. |
225 | GetOutputStream() << "\n ]\n}\n" ; |
226 | } |
227 | |
228 | void JSONReporter::PrintRunData(Run const& run) { |
229 | std::string indent(6, ' '); |
230 | std::ostream& out = GetOutputStream(); |
231 | out << indent << FormatKV("name" , run.benchmark_name()) << ",\n" ; |
232 | out << indent << FormatKV("family_index" , run.family_index) << ",\n" ; |
233 | out << indent |
234 | << FormatKV("per_family_instance_index" , run.per_family_instance_index) |
235 | << ",\n" ; |
236 | out << indent << FormatKV("run_name" , run.run_name.str()) << ",\n" ; |
237 | out << indent << FormatKV("run_type" , [&run]() -> const char* { |
238 | switch (run.run_type) { |
239 | case BenchmarkReporter::Run::RT_Iteration: |
240 | return "iteration" ; |
241 | case BenchmarkReporter::Run::RT_Aggregate: |
242 | return "aggregate" ; |
243 | } |
244 | BENCHMARK_UNREACHABLE(); |
245 | }()) << ",\n" ; |
246 | out << indent << FormatKV("repetitions" , run.repetitions) << ",\n" ; |
247 | if (run.run_type != BenchmarkReporter::Run::RT_Aggregate) { |
248 | out << indent << FormatKV("repetition_index" , run.repetition_index) |
249 | << ",\n" ; |
250 | } |
251 | out << indent << FormatKV("threads" , run.threads) << ",\n" ; |
252 | if (run.run_type == BenchmarkReporter::Run::RT_Aggregate) { |
253 | out << indent << FormatKV("aggregate_name" , run.aggregate_name) << ",\n" ; |
254 | out << indent << FormatKV("aggregate_unit" , [&run]() -> const char* { |
255 | switch (run.aggregate_unit) { |
256 | case StatisticUnit::kTime: |
257 | return "time" ; |
258 | case StatisticUnit::kPercentage: |
259 | return "percentage" ; |
260 | } |
261 | BENCHMARK_UNREACHABLE(); |
262 | }()) << ",\n" ; |
263 | } |
264 | if (run.error_occurred) { |
265 | out << indent << FormatKV("error_occurred" , run.error_occurred) << ",\n" ; |
266 | out << indent << FormatKV("error_message" , run.error_message) << ",\n" ; |
267 | } |
268 | if (!run.report_big_o && !run.report_rms) { |
269 | out << indent << FormatKV("iterations" , run.iterations) << ",\n" ; |
270 | if (run.run_type != Run::RT_Aggregate || |
271 | run.aggregate_unit == StatisticUnit::kTime) { |
272 | out << indent << FormatKV("real_time" , run.GetAdjustedRealTime()) |
273 | << ",\n" ; |
274 | out << indent << FormatKV("cpu_time" , run.GetAdjustedCPUTime()); |
275 | } else { |
276 | assert(run.aggregate_unit == StatisticUnit::kPercentage); |
277 | out << indent << FormatKV("real_time" , run.real_accumulated_time) |
278 | << ",\n" ; |
279 | out << indent << FormatKV("cpu_time" , run.cpu_accumulated_time); |
280 | } |
281 | out << ",\n" |
282 | << indent << FormatKV("time_unit" , GetTimeUnitString(run.time_unit)); |
283 | } else if (run.report_big_o) { |
284 | out << indent << FormatKV("cpu_coefficient" , run.GetAdjustedCPUTime()) |
285 | << ",\n" ; |
286 | out << indent << FormatKV("real_coefficient" , run.GetAdjustedRealTime()) |
287 | << ",\n" ; |
288 | out << indent << FormatKV("big_o" , GetBigOString(run.complexity)) << ",\n" ; |
289 | out << indent << FormatKV("time_unit" , GetTimeUnitString(run.time_unit)); |
290 | } else if (run.report_rms) { |
291 | out << indent << FormatKV("rms" , run.GetAdjustedCPUTime()); |
292 | } |
293 | |
294 | for (auto& c : run.counters) { |
295 | out << ",\n" << indent << FormatKV(c.first, c.second); |
296 | } |
297 | |
298 | if (run.memory_result) { |
299 | const MemoryManager::Result memory_result = *run.memory_result; |
300 | out << ",\n" << indent << FormatKV("allocs_per_iter" , run.allocs_per_iter); |
301 | out << ",\n" |
302 | << indent << FormatKV("max_bytes_used" , memory_result.max_bytes_used); |
303 | |
304 | auto report_if_present = [&out, &indent](const char* label, int64_t val) { |
305 | if (val != MemoryManager::TombstoneValue) |
306 | out << ",\n" << indent << FormatKV(label, val); |
307 | }; |
308 | |
309 | report_if_present("total_allocated_bytes" , |
310 | memory_result.total_allocated_bytes); |
311 | report_if_present("net_heap_growth" , memory_result.net_heap_growth); |
312 | } |
313 | |
314 | if (!run.report_label.empty()) { |
315 | out << ",\n" << indent << FormatKV("label" , run.report_label); |
316 | } |
317 | out << '\n'; |
318 | } |
319 | |
320 | const int64_t MemoryManager::TombstoneValue = |
321 | std::numeric_limits<int64_t>::max(); |
322 | |
323 | } // end namespace benchmark |
324 | |