Skip to content

Commit 2ec390a

Browse files
Nate Jensvoldfacebook-github-bot
authored andcommitted
Support time based user metrics in Folly::benchmark
Summary: Fix for issue #1 in Post [Truncation of UserMetric::Time](https://fb.workplace.com/groups/560979627394613/permalink/3320975644728317/) Our team uses [BENCHMARK_IMPL_COUNTERS](https://www.internalfb.com/code/fbsource/[f51516f2368d]/fbcode/folly/Benchmark.h?lines=379) to measure custom metrics during Folly::benchmarks execution. While working on updates to NodeAPI::SlaBenchmark in D77320464 I noticed Folly `UserMetric` accepts integer(Long) type values which works great for `UserMetric::Type::Custom` and `UserMetric::Type::Metric`, but causes issues for `UserMetric::Type::Time` values. When [time metrics](https://www.internalfb.com/code/fbsource/[f51516f2368d8097604a13de292824a7d85d7de1]/fbcode/folly/Benchmark.cpp?lines=529) are formated for display the value is implicitly cast(`Long` -> `Double`) resulting in loss of precision. This results in any value smaller than 1s being displayed as 0.00fs due to the truncation making it impossible to display small time values. ~~This diff adds support for users to provide floating point values as input to `UserMetric` allowing precision to be retained through a new `time_value` field, while remaining backward compatible with existing usage that relies on the existing `value` field.~~ See discussion below Reviewed By: yfeldblum Differential Revision: D77319941 fbshipit-source-id: a4d55165b5429fa602ea357764db03a81b9ac760
1 parent e5e17fa commit 2ec390a

File tree

3 files changed

+134
-12
lines changed

3 files changed

+134
-12
lines changed

third-party/folly/src/folly/Benchmark.cpp

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include <folly/FileUtil.h>
3131
#include <folly/MapUtil.h>
32+
#include <folly/Overload.h>
3233
#include <folly/String.h>
3334
#include <folly/detail/PerfScoped.h>
3435
#include <folly/json/json.h>
@@ -522,21 +523,32 @@ class BenchmarkResultsPrinter {
522523
for (auto const& name : counterNames_) {
523524
if (auto ptr = folly::get_ptr(datum.counters, name)) {
524525
switch (ptr->type) {
526+
// UserMetrics constructed as precision_values avoid the
527+
// implicit cast from long to double when formatting the output
525528
case UserMetric::Type::TIME:
526-
printf(
527-
" %*s",
528-
int(name.length()),
529-
readableTime(ptr->value, 2).c_str());
529+
folly::variant_match(ptr->value, [&](auto value) {
530+
printf(
531+
" %*s",
532+
int(name.length()),
533+
readableTime(value, 2).c_str());
534+
});
530535
break;
531536
case UserMetric::Type::METRIC:
532-
printf(
533-
" %*s",
534-
int(name.length()),
535-
metricReadable(ptr->value, 2).c_str());
537+
folly::variant_match(ptr->value, [&](auto value) {
538+
printf(
539+
" %*s",
540+
int(name.length()),
541+
metricReadable(value, 2).c_str());
542+
});
536543
break;
537544
case UserMetric::Type::CUSTOM:
538545
default:
539-
printf(" %*" PRId64, int(name.length()), ptr->value);
546+
folly::variant_match(ptr->value, [&](auto value) {
547+
printf(
548+
" %*" PRId64,
549+
int(name.length()),
550+
static_cast<int64_t>(value));
551+
});
540552
}
541553
} else {
542554
printf(" %*s", int(name.length()), "NaN");
@@ -577,7 +589,9 @@ void benchmarkResultsToDynamic(
577589
dynamic obj = dynamic::object;
578590
for (auto& counter : datum.counters) {
579591
dynamic counterInfo = dynamic::object;
580-
counterInfo["value"] = counter.second.value;
592+
folly::variant_match(counter.second.value, [&](auto value) {
593+
counterInfo["value"] = value;
594+
});
581595
counterInfo["type"] = static_cast<int>(counter.second.type);
582596
obj[counter.first] = counterInfo;
583597
}

third-party/folly/src/folly/Benchmark.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include <set>
3636
#include <type_traits>
3737
#include <unordered_map>
38+
#include <variant>
3839

3940
#include <boost/function_types/function_arity.hpp>
4041
#include <glog/logging.h>
@@ -63,20 +64,29 @@ inline bool runBenchmarksOnFlag() {
6364
class UserMetric {
6465
public:
6566
enum class Type { CUSTOM, TIME, METRIC };
66-
67-
int64_t value{};
67+
std::variant<int64_t, double> value;
6868
Type type{Type::CUSTOM};
6969

7070
UserMetric() = default;
7171
/* implicit */ UserMetric(int64_t val, Type typ = Type::CUSTOM)
7272
: value(val), type(typ) {}
7373

74+
// Allow users to provide precision values
75+
template <
76+
typename T,
77+
typename = std::enable_if_t<std::is_floating_point_v<T>>>
78+
explicit UserMetric(T precision_val, Type typ = Type::CUSTOM)
79+
: value(convert_helper(precision_val)), type(typ) {}
80+
7481
friend bool operator==(const UserMetric& x, const UserMetric& y) {
7582
return x.value == y.value && x.type == y.type;
7683
}
7784
friend bool operator!=(const UserMetric& x, const UserMetric& y) {
7885
return !(x == y);
7986
}
87+
88+
private:
89+
double convert_helper(double val) { return val; }
8090
};
8191

8292
using UserCounters = std::unordered_map<std::string, UserMetric>;

third-party/folly/src/folly/test/BenchmarkTest.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,104 @@ TEST_F(BenchmarkingStateTest, ListTests) {
349349
}
350350
}
351351

352+
TEST_F(BenchmarkingStateTest, UserMetricInt) {
353+
std::string output;
354+
355+
{
356+
addBenchmark(__FILE__, "int_custom", [&](UserCounters& counter) {
357+
counter["int_custom_count"] =
358+
UserMetric(123456, UserMetric::Type::CUSTOM);
359+
doBaseline();
360+
TestClock::advance(std::chrono::nanoseconds(1));
361+
return 1;
362+
});
363+
364+
::testing::internal::CaptureStdout();
365+
runBenchmarks();
366+
output = ::testing::internal::GetCapturedStdout();
367+
ASSERT_THAT(output, ::testing::HasSubstr("123456"));
368+
}
369+
370+
{
371+
addBenchmark(__FILE__, "int_metric", [&](UserCounters& counter) {
372+
counter["int_metric_count"] =
373+
UserMetric(234567, UserMetric::Type::METRIC);
374+
doBaseline();
375+
TestClock::advance(std::chrono::nanoseconds(1));
376+
return 1;
377+
});
378+
379+
::testing::internal::CaptureStdout();
380+
runBenchmarks();
381+
output = ::testing::internal::GetCapturedStdout();
382+
ASSERT_THAT(output, ::testing::HasSubstr("234.57K"));
383+
}
384+
385+
{
386+
addBenchmark(__FILE__, "int_time", [&](UserCounters& counter) {
387+
counter["int_time_count"] = UserMetric(10, UserMetric::Type::TIME);
388+
doBaseline();
389+
TestClock::advance(std::chrono::nanoseconds(1));
390+
return 1;
391+
});
392+
393+
::testing::internal::CaptureStdout();
394+
runBenchmarks();
395+
output = ::testing::internal::GetCapturedStdout();
396+
std::cout << output << std::endl;
397+
ASSERT_THAT(output, ::testing::HasSubstr("10.00s"));
398+
}
399+
}
400+
401+
TEST_F(BenchmarkingStateTest, UserMetricPrecise) {
402+
std::string output;
403+
404+
{
405+
addBenchmark(__FILE__, "double_custom", [&](UserCounters& counter) {
406+
counter["double_custom_count"] =
407+
UserMetric(1234.56, UserMetric::Type::CUSTOM);
408+
doBaseline();
409+
TestClock::advance(std::chrono::nanoseconds(1));
410+
return 1;
411+
});
412+
413+
::testing::internal::CaptureStdout();
414+
runBenchmarks();
415+
output = ::testing::internal::GetCapturedStdout();
416+
ASSERT_THAT(output, ::testing::HasSubstr("1234"));
417+
}
418+
419+
{
420+
addBenchmark(__FILE__, "double_metric", [&](UserCounters& counter) {
421+
counter["double_metric_count"] =
422+
UserMetric(23.456, UserMetric::Type::METRIC);
423+
doBaseline();
424+
TestClock::advance(std::chrono::nanoseconds(1));
425+
return 1;
426+
});
427+
428+
::testing::internal::CaptureStdout();
429+
runBenchmarks();
430+
output = ::testing::internal::GetCapturedStdout();
431+
ASSERT_THAT(output, ::testing::HasSubstr("23.46"));
432+
}
433+
434+
{
435+
addBenchmark(__FILE__, "double_time", [&](UserCounters& counter) {
436+
counter["double_time_count"] = UserMetric(34.567, UserMetric::Type::TIME);
437+
doBaseline();
438+
TestClock::advance(std::chrono::nanoseconds(1));
439+
return 1;
440+
});
441+
442+
::testing::internal::CaptureStdout();
443+
runBenchmarks();
444+
output = ::testing::internal::GetCapturedStdout();
445+
std::cout << output << std::endl;
446+
ASSERT_THAT(output, ::testing::HasSubstr("34.57s"));
447+
}
448+
}
449+
352450
} // namespace
353451
} // namespace detail
354452
} // namespace folly

0 commit comments

Comments
 (0)