diff --git a/llvm/test/tools/llvm-exegesis/X86/validation-counters.asm b/llvm/test/tools/llvm-exegesis/X86/validation-counters.asm new file mode 100644 index 0000000000000..7d0c940519e6a --- /dev/null +++ b/llvm/test/tools/llvm-exegesis/X86/validation-counters.asm @@ -0,0 +1,12 @@ +# REQUIRES: exegesis-can-measure-latency, exegesis-can-measure-uops, x86_64-linux + +# Check that when specifying validation counters, the validation counter is +# collected and the information is displayed in the output. Test across +# multiple configurations that need to be wired up separately for validation +# counter support. + +# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -opcode-name=ADD64rr --validation-counter=instructions-retired | FileCheck %s +# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=latency -opcode-name=ADD64rr --validation-counter=instructions-retired -execution-mode=subprocess | FileCheck %s +# RUN: llvm-exegesis -mtriple=x86_64-unknown-unknown -mode=uops -opcode-name=ADD64rr --validation-counter=instructions-retired -execution-mode=subprocess | FileCheck %s + +# CHECK: instructions-retired: {{[0-9]+}} diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp index 02c4da11e032d..60264ca87a988 100644 --- a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp +++ b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.cpp @@ -14,6 +14,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/bit.h" #include "llvm/ObjectYAML/YAML.h" +#include "llvm/Support/Errc.h" #include "llvm/Support/FileOutputBuffer.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" @@ -192,6 +193,41 @@ template <> struct SequenceElementTraits { static const bool flow = false; }; +const char *validationEventToString(exegesis::ValidationEvent VE) { + switch (VE) { + case exegesis::ValidationEvent::InstructionRetired: + return "instructions-retired"; + } +} + +Expected stringToValidationEvent(StringRef Input) { + if (Input == "instructions-retired") + return exegesis::ValidationEvent::InstructionRetired; + else + return make_error("Invalid validation event string", + errc::invalid_argument); +} + +template <> +struct CustomMappingTraits> { + static void inputOne(IO &Io, StringRef KeyStr, + std::map &VI) { + Expected Key = stringToValidationEvent(KeyStr); + if (!Key) { + Io.setError("Key is not a valid validation event"); + return; + } + Io.mapRequired(KeyStr.str().c_str(), VI[*Key]); + } + + static void output(IO &Io, std::map &VI) { + for (auto &IndividualVI : VI) { + Io.mapRequired(validationEventToString(IndividualVI.first), + IndividualVI.second); + } + } +}; + // exegesis::Measure is rendererd as a flow instead of a list. // e.g. { "key": "the key", "value": 0123 } template <> struct MappingTraits { @@ -203,6 +239,7 @@ template <> struct MappingTraits { } Io.mapRequired("value", Obj.PerInstructionValue); Io.mapOptional("per_snippet_value", Obj.PerSnippetValue); + Io.mapOptional("validation_counters", Obj.ValidationCounters); } static const bool flow = true; }; diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h index 0d08febae20cb..c983d6d6e00d9 100644 --- a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h +++ b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h @@ -32,6 +32,8 @@ class Error; namespace exegesis { +enum ValidationEvent { InstructionRetired }; + enum class BenchmarkPhaseSelectorE { PrepareSnippet, PrepareAndAssembleSnippet, @@ -77,8 +79,10 @@ struct BenchmarkKey { struct BenchmarkMeasure { // A helper to create an unscaled BenchmarkMeasure. - static BenchmarkMeasure Create(std::string Key, double Value) { - return {Key, Value, Value}; + static BenchmarkMeasure + Create(std::string Key, double Value, + std::map ValCounters) { + return {Key, Value, Value, ValCounters}; } std::string Key; // This is the per-instruction value, i.e. measured quantity scaled per @@ -87,6 +91,8 @@ struct BenchmarkMeasure { // This is the per-snippet value, i.e. measured quantity for one repetition of // the whole snippet. double PerSnippetValue; + // These are the validation counter values. + std::map ValidationCounters; }; // The result of an instruction benchmark. diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp b/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp index eb1393ad94ff9..2b512284f9409 100644 --- a/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp +++ b/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.cpp @@ -54,9 +54,11 @@ namespace exegesis { BenchmarkRunner::BenchmarkRunner(const LLVMState &State, Benchmark::ModeE Mode, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, - ExecutionModeE ExecutionMode) + ExecutionModeE ExecutionMode, + ArrayRef ValCounters) : State(State), Mode(Mode), BenchmarkPhaseSelector(BenchmarkPhaseSelector), - ExecutionMode(ExecutionMode), Scratch(std::make_unique()) {} + ExecutionMode(ExecutionMode), ValidationCounters(ValCounters), + Scratch(std::make_unique()) {} BenchmarkRunner::~BenchmarkRunner() = default; @@ -71,7 +73,9 @@ void BenchmarkRunner::FunctionExecutor::accumulateCounterValues( } Expected> -BenchmarkRunner::FunctionExecutor::runAndSample(const char *Counters) const { +BenchmarkRunner::FunctionExecutor::runAndSample( + const char *Counters, ArrayRef ValidationCounters, + SmallVectorImpl &ValidationCounterValues) const { // We sum counts when there are several counters for a single ProcRes // (e.g. P23 on SandyBridge). llvm::SmallVector CounterValues; @@ -79,8 +83,8 @@ BenchmarkRunner::FunctionExecutor::runAndSample(const char *Counters) const { StringRef(Counters).split(CounterNames, '+'); for (auto &CounterName : CounterNames) { CounterName = CounterName.trim(); - Expected> ValueOrError = - runWithCounter(CounterName); + Expected> ValueOrError = runWithCounter( + CounterName, ValidationCounters, ValidationCounterValues); if (!ValueOrError) return ValueOrError.takeError(); accumulateCounterValues(ValueOrError.get(), &CounterValues); @@ -120,11 +124,13 @@ class InProcessFunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor { (*Result)[I] += NewValues[I]; } - Expected> - runWithCounter(StringRef CounterName) const override { + Expected> runWithCounter( + StringRef CounterName, ArrayRef ValidationCounters, + SmallVectorImpl &ValidationCounterValues) const override { const ExegesisTarget &ET = State.getExegesisTarget(); char *const ScratchPtr = Scratch->ptr(); - auto CounterOrError = ET.createCounter(CounterName, State); + auto CounterOrError = + ET.createCounter(CounterName, State, ValidationCounters); if (!CounterOrError) return CounterOrError.takeError(); @@ -156,6 +162,14 @@ class InProcessFunctionExecutorImpl : public BenchmarkRunner::FunctionExecutor { } } + auto ValidationValuesOrErr = Counter->readValidationCountersOrError(); + if (!ValidationValuesOrErr) + return ValidationValuesOrErr.takeError(); + + ArrayRef RealValidationValues = *ValidationValuesOrErr; + for (size_t I = 0; I < RealValidationValues.size(); ++I) + ValidationCounterValues[I] = RealValidationValues[I]; + return Counter->readOrError(Function.getFunctionBytes()); } @@ -266,7 +280,9 @@ class SubProcessFunctionExecutorImpl } Error createSubProcessAndRunBenchmark( - StringRef CounterName, SmallVectorImpl &CounterValues) const { + StringRef CounterName, SmallVectorImpl &CounterValues, + ArrayRef ValidationCounters, + SmallVectorImpl &ValidationCounterValues) const { int PipeFiles[2]; int PipeSuccessOrErr = socketpair(AF_UNIX, SOCK_DGRAM, 0, PipeFiles); if (PipeSuccessOrErr != 0) { @@ -306,8 +322,8 @@ class SubProcessFunctionExecutorImpl } const ExegesisTarget &ET = State.getExegesisTarget(); - auto CounterOrError = - ET.createCounter(CounterName, State, ParentOrChildPID); + auto CounterOrError = ET.createCounter( + CounterName, State, ValidationCounters, ParentOrChildPID); if (!CounterOrError) return CounterOrError.takeError(); @@ -362,6 +378,14 @@ class SubProcessFunctionExecutorImpl return CounterValueOrErr.takeError(); CounterValues = std::move(*CounterValueOrErr); + auto ValidationValuesOrErr = Counter->readValidationCountersOrError(); + if (!ValidationValuesOrErr) + return ValidationValuesOrErr.takeError(); + + ArrayRef RealValidationValues = *ValidationValuesOrErr; + for (size_t I = 0; I < RealValidationValues.size(); ++I) + ValidationCounterValues[I] = RealValidationValues[I]; + return Error::success(); } // The child exited, but not successfully @@ -460,15 +484,15 @@ class SubProcessFunctionExecutorImpl exit(0); } - Expected> - runWithCounter(StringRef CounterName) const override { + Expected> runWithCounter( + StringRef CounterName, ArrayRef ValidationCounters, + SmallVectorImpl &ValidationCounterValues) const override { SmallVector Value(1, 0); - Error PossibleBenchmarkError = - createSubProcessAndRunBenchmark(CounterName, Value); + Error PossibleBenchmarkError = createSubProcessAndRunBenchmark( + CounterName, Value, ValidationCounters, ValidationCounterValues); - if (PossibleBenchmarkError) { + if (PossibleBenchmarkError) return std::move(PossibleBenchmarkError); - } return Value; } @@ -642,6 +666,34 @@ BenchmarkRunner::writeObjectFile(StringRef Buffer, StringRef FileName) const { return std::string(ResultPath); } +static bool EventLessThan(const std::pair LHS, + const ValidationEvent RHS) { + return static_cast(LHS.first) < static_cast(RHS); +} + +Error BenchmarkRunner::getValidationCountersToRun( + SmallVector &ValCountersToRun) const { + const PfmCountersInfo &PCI = State.getPfmCounters(); + ValCountersToRun.reserve(ValidationCounters.size()); + + ValCountersToRun.reserve(ValidationCounters.size()); + ArrayRef TargetValidationEvents(PCI.ValidationEvents, + PCI.NumValidationEvents); + for (const ValidationEvent RequestedValEvent : ValidationCounters) { + auto ValCounterIt = + lower_bound(TargetValidationEvents, RequestedValEvent, EventLessThan); + if (ValCounterIt == TargetValidationEvents.end() || + ValCounterIt->first != RequestedValEvent) + return make_error("Cannot create validation counter"); + + assert(ValCounterIt->first == RequestedValEvent && + "The array of validation events from the target should be sorted"); + ValCountersToRun.push_back(ValCounterIt->second); + } + + return Error::success(); +} + BenchmarkRunner::FunctionExecutor::~FunctionExecutor() {} } // namespace exegesis diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h b/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h index d746a0f775646..aab4b54242e4b 100644 --- a/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h +++ b/llvm/tools/llvm-exegesis/lib/BenchmarkRunner.h @@ -38,7 +38,8 @@ class BenchmarkRunner { explicit BenchmarkRunner(const LLVMState &State, Benchmark::ModeE Mode, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, - ExecutionModeE ExecutionMode); + ExecutionModeE ExecutionMode, + ArrayRef ValCounters); virtual ~BenchmarkRunner(); @@ -93,14 +94,18 @@ class BenchmarkRunner { virtual ~FunctionExecutor(); Expected> - runAndSample(const char *Counters) const; + runAndSample(const char *Counters, + ArrayRef ValidationCounters, + SmallVectorImpl &ValidationCounterValues) const; protected: static void accumulateCounterValues(const llvm::SmallVectorImpl &NewValues, llvm::SmallVectorImpl *Result); virtual Expected> - runWithCounter(StringRef CounterName) const = 0; + runWithCounter(StringRef CounterName, + ArrayRef ValidationCounters, + SmallVectorImpl &ValidationCounterValues) const = 0; }; protected: @@ -109,6 +114,11 @@ class BenchmarkRunner { const BenchmarkPhaseSelectorE BenchmarkPhaseSelector; const ExecutionModeE ExecutionMode; + SmallVector ValidationCounters; + + Error + getValidationCountersToRun(SmallVector &ValCountersToRun) const; + private: virtual Expected> runMeasurements(const FunctionExecutor &Executor) const = 0; diff --git a/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp b/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp index eda450579a583..eb7f70fb2b840 100644 --- a/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp +++ b/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.cpp @@ -22,8 +22,9 @@ LatencyBenchmarkRunner::LatencyBenchmarkRunner( const LLVMState &State, Benchmark::ModeE Mode, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Benchmark::ResultAggregationModeE ResultAgg, ExecutionModeE ExecutionMode, - unsigned BenchmarkRepeatCount) - : BenchmarkRunner(State, Mode, BenchmarkPhaseSelector, ExecutionMode) { + ArrayRef ValCounters, unsigned BenchmarkRepeatCount) + : BenchmarkRunner(State, Mode, BenchmarkPhaseSelector, ExecutionMode, + ValCounters) { assert((Mode == Benchmark::Latency || Mode == Benchmark::InverseThroughput) && "invalid mode"); ResultAggMode = ResultAgg; @@ -72,11 +73,21 @@ Expected> LatencyBenchmarkRunner::runMeasurements( // ResultAggMode. llvm::SmallVector AccumulatedValues; double MinVariance = std::numeric_limits::infinity(); - const char *CounterName = State.getPfmCounters().CycleCounter; + const PfmCountersInfo &PCI = State.getPfmCounters(); + const char *CounterName = PCI.CycleCounter; + + SmallVector ValCountersToRun; + Error ValCounterErr = getValidationCountersToRun(ValCountersToRun); + if (ValCounterErr) + return std::move(ValCounterErr); + + SmallVector ValCounterValues(ValCountersToRun.size(), 0); // Values count for each run. int ValuesCount = 0; for (size_t I = 0; I < NumMeasurements; ++I) { - auto ExpectedCounterValues = Executor.runAndSample(CounterName); + SmallVector IterationValCounterValues(ValCountersToRun.size(), -1); + auto ExpectedCounterValues = Executor.runAndSample( + CounterName, ValCountersToRun, IterationValCounterValues); if (!ExpectedCounterValues) return ExpectedCounterValues.takeError(); ValuesCount = ExpectedCounterValues.get().size(); @@ -90,8 +101,15 @@ Expected> LatencyBenchmarkRunner::runMeasurements( MinVariance = Variance; } } + + for (size_t I = 0; I < ValCounterValues.size(); ++I) + ValCounterValues[I] += IterationValCounterValues[I]; } + std::map ValidationInfo; + for (size_t I = 0; I < ValidationCounters.size(); ++I) + ValidationInfo[ValidationCounters[I]] = ValCounterValues[I]; + std::string ModeName; switch (Mode) { case Benchmark::Latency: @@ -112,25 +130,26 @@ Expected> LatencyBenchmarkRunner::runMeasurements( std::vector Result; Result.reserve(AccumulatedValues.size()); for (const int64_t Value : AccumulatedValues) - Result.push_back(BenchmarkMeasure::Create(ModeName, Value)); + Result.push_back( + BenchmarkMeasure::Create(ModeName, Value, ValidationInfo)); return std::move(Result); } case Benchmark::Min: { std::vector Result; - Result.push_back( - BenchmarkMeasure::Create(ModeName, findMin(AccumulatedValues))); + Result.push_back(BenchmarkMeasure::Create( + ModeName, findMin(AccumulatedValues), ValidationInfo)); return std::move(Result); } case Benchmark::Max: { std::vector Result; - Result.push_back( - BenchmarkMeasure::Create(ModeName, findMax(AccumulatedValues))); + Result.push_back(BenchmarkMeasure::Create( + ModeName, findMax(AccumulatedValues), ValidationInfo)); return std::move(Result); } case Benchmark::Mean: { std::vector Result; - Result.push_back( - BenchmarkMeasure::Create(ModeName, findMean(AccumulatedValues))); + Result.push_back(BenchmarkMeasure::Create( + ModeName, findMean(AccumulatedValues), ValidationInfo)); return std::move(Result); } } diff --git a/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.h b/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.h index fc159d7d9b5e9..6e21197b698b9 100644 --- a/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.h +++ b/llvm/tools/llvm-exegesis/lib/LatencyBenchmarkRunner.h @@ -15,6 +15,7 @@ #define LLVM_TOOLS_LLVM_EXEGESIS_LATENCY_H #include "BenchmarkRunner.h" +#include "Target.h" namespace llvm { namespace exegesis { @@ -25,6 +26,7 @@ class LatencyBenchmarkRunner : public BenchmarkRunner { BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Benchmark::ResultAggregationModeE ResultAggMode, ExecutionModeE ExecutionMode, + ArrayRef ValCounters, unsigned BenchmarkRepeatCount); ~LatencyBenchmarkRunner() override; diff --git a/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp b/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp index 1dab8809d3028..6b8df01e9f544 100644 --- a/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp +++ b/llvm/tools/llvm-exegesis/lib/PerfHelper.cpp @@ -113,9 +113,8 @@ ConfiguredEvent::ConfiguredEvent(PerfEvent &&EventToConfigure) } #ifdef HAVE_LIBPFM -void ConfiguredEvent::initRealEvent(const pid_t ProcessID) { +void ConfiguredEvent::initRealEvent(const pid_t ProcessID, const int GroupFD) { const int CPU = -1; - const int GroupFD = -1; const uint32_t Flags = 0; perf_event_attr AttrCopy = *Event.attribute(); FileDescriptor = perf_event_open(&AttrCopy, ProcessID, CPU, GroupFD, Flags); @@ -147,7 +146,7 @@ ConfiguredEvent::readOrError(StringRef /*unused*/) const { ConfiguredEvent::~ConfiguredEvent() { close(FileDescriptor); } #else -void ConfiguredEvent::initRealEvent(pid_t ProcessID) {} +void ConfiguredEvent::initRealEvent(pid_t ProcessID, const int GroupFD) {} Expected> ConfiguredEvent::readOrError(StringRef /*unused*/) const { @@ -158,9 +157,14 @@ ConfiguredEvent::readOrError(StringRef /*unused*/) const { ConfiguredEvent::~ConfiguredEvent() = default; #endif // HAVE_LIBPFM -CounterGroup::CounterGroup(PerfEvent &&E, pid_t ProcessID) +CounterGroup::CounterGroup(PerfEvent &&E, std::vector &&ValEvents, + pid_t ProcessID) : EventCounter(std::move(E)) { IsDummyEvent = EventCounter.isDummyEvent(); + + for (auto &&ValEvent : ValEvents) + ValidationEventCounters.emplace_back(std::move(ValEvent)); + if (!IsDummyEvent) initRealEvent(ProcessID); } @@ -168,16 +172,19 @@ CounterGroup::CounterGroup(PerfEvent &&E, pid_t ProcessID) #ifdef HAVE_LIBPFM void CounterGroup::initRealEvent(pid_t ProcessID) { EventCounter.initRealEvent(ProcessID); + + for (auto &ValCounter : ValidationEventCounters) + ValCounter.initRealEvent(ProcessID, getFileDescriptor()); } void CounterGroup::start() { if (!IsDummyEvent) - ioctl(getFileDescriptor(), PERF_EVENT_IOC_RESET, 0); + ioctl(getFileDescriptor(), PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP); } void CounterGroup::stop() { if (!IsDummyEvent) - ioctl(getFileDescriptor(), PERF_EVENT_IOC_DISABLE, 0); + ioctl(getFileDescriptor(), PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP); } llvm::Expected> @@ -188,6 +195,25 @@ CounterGroup::readOrError(StringRef FunctionBytes) const { return SmallVector(1, 42); } +llvm::Expected> +CounterGroup::readValidationCountersOrError() const { + llvm::SmallVector Result; + for (const auto &ValCounter : ValidationEventCounters) { + Expected> ValueOrError = + ValCounter.readOrError(StringRef()); + + if (!ValueOrError) + return ValueOrError.takeError(); + + // Reading a validation counter will only return a single value, so it is + // safe to only append the first value here. Also assert that this is true. + assert(ValueOrError->size() == 1 && + "Validation counters should only return a single value"); + Result.push_back((*ValueOrError)[0]); + } + return Result; +} + int CounterGroup::numValues() const { return 1; } #else @@ -208,6 +234,11 @@ CounterGroup::readOrError(StringRef /*unused*/) const { llvm::errc::io_error); } +llvm::Expected> +CounterGroup::readValidationCountersOrError() const { + return SmallVector(0); +} + int CounterGroup::numValues() const { return 1; } #endif diff --git a/llvm/tools/llvm-exegesis/lib/PerfHelper.h b/llvm/tools/llvm-exegesis/lib/PerfHelper.h index e9b73da383628..7d050b85c9a8c 100644 --- a/llvm/tools/llvm-exegesis/lib/PerfHelper.h +++ b/llvm/tools/llvm-exegesis/lib/PerfHelper.h @@ -83,7 +83,7 @@ class ConfiguredEvent { public: ConfiguredEvent(PerfEvent &&EventToConfigure); - void initRealEvent(const pid_t ProcessID); + void initRealEvent(const pid_t ProcessID, const int GroupFD = -1); Expected> readOrError(StringRef FunctionBytes) const; int getFileDescriptor() const { return FileDescriptor; } bool isDummyEvent() const { @@ -107,7 +107,8 @@ class ConfiguredEvent { class CounterGroup { public: // event: the PerfEvent to measure. - explicit CounterGroup(PerfEvent &&event, pid_t ProcessID = 0); + explicit CounterGroup(PerfEvent &&event, std::vector &&ValEvents, + pid_t ProcessID = 0); CounterGroup(const CounterGroup &) = delete; CounterGroup(CounterGroup &&other) = default; @@ -129,6 +130,9 @@ class CounterGroup { virtual llvm::Expected> readOrError(StringRef FunctionBytes = StringRef()) const; + virtual llvm::Expected> + readValidationCountersOrError() const; + virtual int numValues() const; int getFileDescriptor() const { return EventCounter.getFileDescriptor(); } @@ -136,6 +140,7 @@ class CounterGroup { protected: ConfiguredEvent EventCounter; bool IsDummyEvent; + std::vector ValidationEventCounters; private: void initRealEvent(pid_t ProcessID); diff --git a/llvm/tools/llvm-exegesis/lib/Target.cpp b/llvm/tools/llvm-exegesis/lib/Target.cpp index 8f1c5a157eea3..58cf1b96fea4e 100644 --- a/llvm/tools/llvm-exegesis/lib/Target.cpp +++ b/llvm/tools/llvm-exegesis/lib/Target.cpp @@ -37,6 +37,7 @@ const ExegesisTarget *ExegesisTarget::lookup(Triple TT) { Expected> ExegesisTarget::createCounter(StringRef CounterName, const LLVMState &, + ArrayRef ValidationCounters, const pid_t ProcessID) const { pfm::PerfEvent Event(CounterName); if (!Event.valid()) @@ -45,7 +46,18 @@ ExegesisTarget::createCounter(StringRef CounterName, const LLVMState &, .concat(CounterName) .concat("'")); - return std::make_unique(std::move(Event), ProcessID); + std::vector ValidationEvents; + for (const char *ValCounterName : ValidationCounters) { + ValidationEvents.emplace_back(ValCounterName); + if (!ValidationEvents.back().valid()) + return llvm::make_error( + llvm::Twine("Unable to create validation counter with name '") + .concat(ValCounterName) + .concat("'")); + } + + return std::make_unique( + std::move(Event), std::move(ValidationEvents), ProcessID); } void ExegesisTarget::registerTarget(ExegesisTarget *Target) { @@ -79,7 +91,7 @@ ExegesisTarget::createBenchmarkRunner( Benchmark::ModeE Mode, const LLVMState &State, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, BenchmarkRunner::ExecutionModeE ExecutionMode, - unsigned BenchmarkRepeatCount, + unsigned BenchmarkRepeatCount, ArrayRef ValidationCounters, Benchmark::ResultAggregationModeE ResultAggMode) const { PfmCountersInfo PfmCounters = State.getPfmCounters(); switch (Mode) { @@ -101,9 +113,9 @@ ExegesisTarget::createBenchmarkRunner( "benchmarking or --use-dummy-perf-counters to not query " "the kernel for real event counts.")); } - return createLatencyBenchmarkRunner(State, Mode, BenchmarkPhaseSelector, - ResultAggMode, ExecutionMode, - BenchmarkRepeatCount); + return createLatencyBenchmarkRunner( + State, Mode, BenchmarkPhaseSelector, ResultAggMode, ExecutionMode, + ValidationCounters, BenchmarkRepeatCount); case Benchmark::Uops: if (BenchmarkPhaseSelector == BenchmarkPhaseSelectorE::Measure && !PfmCounters.UopsCounter && !PfmCounters.IssueCounters) @@ -113,7 +125,8 @@ ExegesisTarget::createBenchmarkRunner( "benchmarking or --use-dummy-perf-counters to not query the kernel " "for real event counts."); return createUopsBenchmarkRunner(State, BenchmarkPhaseSelector, - ResultAggMode, ExecutionMode); + ResultAggMode, ExecutionMode, + ValidationCounters); } return nullptr; } @@ -133,18 +146,20 @@ std::unique_ptr ExegesisTarget::createLatencyBenchmarkRunner( BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Benchmark::ResultAggregationModeE ResultAggMode, BenchmarkRunner::ExecutionModeE ExecutionMode, + ArrayRef ValidationCounters, unsigned BenchmarkRepeatCount) const { return std::make_unique( State, Mode, BenchmarkPhaseSelector, ResultAggMode, ExecutionMode, - BenchmarkRepeatCount); + ValidationCounters, BenchmarkRepeatCount); } std::unique_ptr ExegesisTarget::createUopsBenchmarkRunner( const LLVMState &State, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Benchmark::ResultAggregationModeE /*unused*/, - BenchmarkRunner::ExecutionModeE ExecutionMode) const { - return std::make_unique(State, BenchmarkPhaseSelector, - ExecutionMode); + BenchmarkRunner::ExecutionModeE ExecutionMode, + ArrayRef ValidationCounters) const { + return std::make_unique( + State, BenchmarkPhaseSelector, ExecutionMode, ValidationCounters); } static_assert(std::is_trivial_v, diff --git a/llvm/tools/llvm-exegesis/lib/Target.h b/llvm/tools/llvm-exegesis/lib/Target.h index da6d44611eca7..3d6169c965021 100644 --- a/llvm/tools/llvm-exegesis/lib/Target.h +++ b/llvm/tools/llvm-exegesis/lib/Target.h @@ -39,10 +39,6 @@ extern cl::OptionCategory Options; extern cl::OptionCategory BenchmarkOptions; extern cl::OptionCategory AnalysisOptions; -enum ValidationEvent { - InstructionRetired -}; - struct PfmCountersInfo { // An optional name of a performance counter that can be used to measure // cycles. @@ -86,6 +82,7 @@ class ExegesisTarget { // Targets can use this to create target-specific perf counters. virtual Expected> createCounter(StringRef CounterName, const LLVMState &State, + ArrayRef ValidationCounters, const pid_t ProcessID = 0) const; // Targets can use this to add target-specific passes in assembleToStream(); @@ -270,6 +267,7 @@ class ExegesisTarget { BenchmarkPhaseSelectorE BenchmarkPhaseSelector, BenchmarkRunner::ExecutionModeE ExecutionMode, unsigned BenchmarkRepeatCount, + ArrayRef ValidationCounters, Benchmark::ResultAggregationModeE ResultAggMode = Benchmark::Min) const; // Returns the ExegesisTarget for the given triple or nullptr if the target @@ -314,11 +312,13 @@ class ExegesisTarget { BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Benchmark::ResultAggregationModeE ResultAggMode, BenchmarkRunner::ExecutionModeE ExecutionMode, + ArrayRef ValidationCounters, unsigned BenchmarkRepeatCount) const; std::unique_ptr virtual createUopsBenchmarkRunner( const LLVMState &State, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, Benchmark::ResultAggregationModeE ResultAggMode, - BenchmarkRunner::ExecutionModeE ExecutionMode) const; + BenchmarkRunner::ExecutionModeE ExecutionMode, + ArrayRef ValidationCounters) const; const ExegesisTarget *Next = nullptr; const ArrayRef CpuPfmCounters; diff --git a/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.cpp b/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.cpp index 6351fdd3345a8..d6fb59970b94a 100644 --- a/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.cpp +++ b/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.cpp @@ -19,25 +19,45 @@ Expected> UopsBenchmarkRunner::runMeasurements(const FunctionExecutor &Executor) const { std::vector Result; const PfmCountersInfo &PCI = State.getPfmCounters(); + + SmallVector ValCountersToRun; + Error ValCounterErr = getValidationCountersToRun(ValCountersToRun); + if (ValCounterErr) + return std::move(ValCounterErr); + // Uops per port. for (const auto *IssueCounter = PCI.IssueCounters, *IssueCounterEnd = PCI.IssueCounters + PCI.NumIssueCounters; IssueCounter != IssueCounterEnd; ++IssueCounter) { + SmallVector ValCounterPortValues(ValCountersToRun.size(), -1); if (!IssueCounter->Counter) continue; - auto ExpectedCounterValue = Executor.runAndSample(IssueCounter->Counter); + auto ExpectedCounterValue = Executor.runAndSample( + IssueCounter->Counter, ValCountersToRun, ValCounterPortValues); if (!ExpectedCounterValue) return ExpectedCounterValue.takeError(); - Result.push_back(BenchmarkMeasure::Create(IssueCounter->ProcResName, - (*ExpectedCounterValue)[0])); + + std::map ValidationInfo; + for (size_t I = 0; I < ValidationCounters.size(); ++I) + ValidationInfo[ValidationCounters[I]] = ValCounterPortValues[I]; + + Result.push_back(BenchmarkMeasure::Create( + IssueCounter->ProcResName, (*ExpectedCounterValue)[0], ValidationInfo)); } // NumMicroOps. if (const char *const UopsCounter = PCI.UopsCounter) { - auto ExpectedCounterValue = Executor.runAndSample(UopsCounter); + SmallVector ValCounterUopsValues(ValCountersToRun.size(), -1); + auto ExpectedCounterValue = Executor.runAndSample( + UopsCounter, ValCountersToRun, ValCounterUopsValues); if (!ExpectedCounterValue) return ExpectedCounterValue.takeError(); - Result.push_back( - BenchmarkMeasure::Create("NumMicroOps", (*ExpectedCounterValue)[0])); + + std::map ValidationInfo; + for (size_t I = 0; I < ValidationCounters.size(); ++I) + ValidationInfo[ValidationCounters[I]] = ValCounterUopsValues[I]; + + Result.push_back(BenchmarkMeasure::Create( + "NumMicroOps", (*ExpectedCounterValue)[0], ValidationInfo)); } return std::move(Result); } diff --git a/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.h b/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.h index 337f093670122..ef47b7fe8a655 100644 --- a/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.h +++ b/llvm/tools/llvm-exegesis/lib/UopsBenchmarkRunner.h @@ -15,6 +15,7 @@ #define LLVM_TOOLS_LLVM_EXEGESIS_UOPSBENCHMARKRUNNER_H #include "BenchmarkRunner.h" +#include "Target.h" namespace llvm { namespace exegesis { @@ -23,9 +24,10 @@ class UopsBenchmarkRunner : public BenchmarkRunner { public: UopsBenchmarkRunner(const LLVMState &State, BenchmarkPhaseSelectorE BenchmarkPhaseSelector, - ExecutionModeE ExecutionMode) + ExecutionModeE ExecutionMode, + ArrayRef ValCounters) : BenchmarkRunner(State, Benchmark::Uops, BenchmarkPhaseSelector, - ExecutionMode) {} + ExecutionMode, ValCounters) {} ~UopsBenchmarkRunner() override; static constexpr const size_t kMinNumDifferentAddresses = 6; diff --git a/llvm/tools/llvm-exegesis/lib/X86/Target.cpp b/llvm/tools/llvm-exegesis/lib/X86/Target.cpp index 25df89fb0cf31..adb345f023a8e 100644 --- a/llvm/tools/llvm-exegesis/lib/X86/Target.cpp +++ b/llvm/tools/llvm-exegesis/lib/X86/Target.cpp @@ -45,6 +45,9 @@ #include #include #include +#ifdef HAVE_LIBPFM +#include +#endif // HAVE_LIBPFM #endif #define GET_AVAILABLE_OPCODE_CHECKER @@ -681,6 +684,7 @@ class ExegesisX86Target : public ExegesisTarget { Expected> createCounter(StringRef CounterName, const LLVMState &State, + ArrayRef ValidationCounters, const pid_t ProcessID) const override { // If LbrSamplingPeriod was provided, then ignore the // CounterName because we only have one for LBR. @@ -689,6 +693,13 @@ class ExegesisX86Target : public ExegesisTarget { // __linux__ (for now) #if defined(HAVE_LIBPFM) && defined(LIBPFM_HAS_FIELD_CYCLES) && \ defined(__linux__) + // TODO(boomanaiden154): Add in support for using validation counters when + // using LBR counters. + if (ValidationCounters.size() > 0) + return llvm::make_error( + "Using LBR is not currently supported with validation counters", + llvm::errc::invalid_argument); + return std::make_unique( X86LbrPerfEvent(LbrSamplingPeriod)); #else @@ -698,7 +709,8 @@ class ExegesisX86Target : public ExegesisTarget { llvm::errc::invalid_argument); #endif } - return ExegesisTarget::createCounter(CounterName, State, ProcessID); + return ExegesisTarget::createCounter(CounterName, State, ValidationCounters, + ProcessID); } enum ArgumentRegisters { CodeSize = X86::R12, AuxiliaryMemoryFD = X86::R13 }; @@ -1243,7 +1255,7 @@ std::vector ExegesisX86Target::configurePerfCounter(long Request, bool SaveRegisters) const { std::vector ConfigurePerfCounterCode; if (SaveRegisters) - saveSyscallRegisters(ConfigurePerfCounterCode, 2); + saveSyscallRegisters(ConfigurePerfCounterCode, 3); ConfigurePerfCounterCode.push_back( loadImmediate(X86::RDI, 64, APInt(64, getAuxiliaryMemoryStartAddress()))); ConfigurePerfCounterCode.push_back(MCInstBuilder(X86::MOV32rm) @@ -1255,9 +1267,13 @@ ExegesisX86Target::configurePerfCounter(long Request, bool SaveRegisters) const .addReg(0)); ConfigurePerfCounterCode.push_back( loadImmediate(X86::RSI, 64, APInt(64, Request))); +#ifdef HAVE_LIBPFM + ConfigurePerfCounterCode.push_back( + loadImmediate(X86::RDX, 64, APInt(64, PERF_IOC_FLAG_GROUP))); +#endif // HAVE_LIBPFM generateSyscall(SYS_ioctl, ConfigurePerfCounterCode); if (SaveRegisters) - restoreSyscallRegisters(ConfigurePerfCounterCode, 2); + restoreSyscallRegisters(ConfigurePerfCounterCode, 3); return ConfigurePerfCounterCode; } diff --git a/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp b/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp index 96fb0f085a1ee..26be9f846a210 100644 --- a/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp +++ b/llvm/tools/llvm-exegesis/lib/X86/X86Counter.cpp @@ -141,7 +141,7 @@ X86LbrPerfEvent::X86LbrPerfEvent(unsigned SamplingPeriod) { } X86LbrCounter::X86LbrCounter(pfm::PerfEvent &&NewEvent) - : CounterGroup(std::move(NewEvent)) { + : CounterGroup(std::move(NewEvent), {}) { MMappedBuffer = mmap(nullptr, kMappedBufferSize, PROT_READ | PROT_WRITE, MAP_SHARED, getFileDescriptor(), 0); if (MMappedBuffer == MAP_FAILED) diff --git a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp index ffbf94ce0fcb2..2a121bec98d95 100644 --- a/llvm/tools/llvm-exegesis/llvm-exegesis.cpp +++ b/llvm/tools/llvm-exegesis/llvm-exegesis.cpp @@ -268,6 +268,16 @@ static cl::opt BenchmarkRepeatCount( "before aggregating the results"), cl::cat(BenchmarkOptions), cl::init(30)); +static cl::list ValidationCounters( + "validation-counter", + cl::desc( + "The name of a validation counter to run concurrently with the main " + "counter to validate benchmarking assumptions"), + cl::CommaSeparated, cl::cat(BenchmarkOptions), + cl::values(clEnumValN(ValidationEvent::InstructionRetired, + "instructions-retired", + "Count retired instructions"))); + static ExitOnError ExitOnErr("llvm-exegesis error: "); // Helper function that logs the error(s) and exits. @@ -501,7 +511,7 @@ void benchmarkMain() { const std::unique_ptr Runner = ExitOnErr(State.getExegesisTarget().createBenchmarkRunner( BenchmarkMode, State, BenchmarkPhaseSelector, ExecutionMode, - BenchmarkRepeatCount, ResultAggMode)); + BenchmarkRepeatCount, ValidationCounters, ResultAggMode)); if (!Runner) { ExitWithError("cannot create benchmark runner"); } diff --git a/llvm/unittests/tools/llvm-exegesis/ClusteringTest.cpp b/llvm/unittests/tools/llvm-exegesis/ClusteringTest.cpp index 25fe813502e54..26bb6c5d2e4c2 100644 --- a/llvm/unittests/tools/llvm-exegesis/ClusteringTest.cpp +++ b/llvm/unittests/tools/llvm-exegesis/ClusteringTest.cpp @@ -32,17 +32,17 @@ TEST(ClusteringTest, Clusters3D) { // Cluster around (x=0, y=1, z=2): points {0, 3}. Points[0].Measurements = { - {"x", 0.01, 0.0}, {"y", 1.02, 0.0}, {"z", 1.98, 0.0}}; + {"x", 0.01, 0.0, {}}, {"y", 1.02, 0.0, {}}, {"z", 1.98, 0.0, {}}}; Points[3].Measurements = { - {"x", -0.01, 0.0}, {"y", 1.02, 0.0}, {"z", 1.98, 0.0}}; + {"x", -0.01, 0.0, {}}, {"y", 1.02, 0.0, {}}, {"z", 1.98, 0.0, {}}}; // Cluster around (x=1, y=1, z=2): points {1, 4}. Points[1].Measurements = { - {"x", 1.01, 0.0}, {"y", 1.02, 0.0}, {"z", 1.98, 0.0}}; + {"x", 1.01, 0.0, {}}, {"y", 1.02, 0.0, {}}, {"z", 1.98, 0.0, {}}}; Points[4].Measurements = { - {"x", 0.99, 0.0}, {"y", 1.02, 0.0}, {"z", 1.98, 0.0}}; + {"x", 0.99, 0.0, {}}, {"y", 1.02, 0.0, {}}, {"z", 1.98, 0.0, {}}}; // Cluster around (x=0, y=0, z=0): points {5}, marked as noise. Points[5].Measurements = { - {"x", 0.0, 0.0}, {"y", 0.01, 0.0}, {"z", -0.02, 0.0}}; + {"x", 0.0, 0.0, {}}, {"y", 0.01, 0.0, {}}, {"z", -0.02, 0.0, {}}}; // Error cluster: points {2} Points[2].Error = "oops"; @@ -71,8 +71,8 @@ TEST(ClusteringTest, Clusters3D) { TEST(ClusteringTest, Clusters3D_InvalidSize) { std::vector Points(6); Points[0].Measurements = { - {"x", 0.01, 0.0}, {"y", 1.02, 0.0}, {"z", 1.98, 0.0}}; - Points[1].Measurements = {{"y", 1.02, 0.0}, {"z", 1.98, 0.0}}; + {"x", 0.01, 0.0, {}}, {"y", 1.02, 0.0, {}}, {"z", 1.98, 0.0, {}}}; + Points[1].Measurements = {{"y", 1.02, 0.0, {}}, {"z", 1.98, 0.0, {}}}; auto Error = BenchmarkClustering::create( Points, BenchmarkClustering::ModeE::Dbscan, 2, 0.25) @@ -83,8 +83,8 @@ TEST(ClusteringTest, Clusters3D_InvalidSize) { TEST(ClusteringTest, Clusters3D_InvalidOrder) { std::vector Points(6); - Points[0].Measurements = {{"x", 0.01, 0.0}, {"y", 1.02, 0.0}}; - Points[1].Measurements = {{"y", 1.02, 0.0}, {"x", 1.98, 0.0}}; + Points[0].Measurements = {{"x", 0.01, 0.0, {}}, {"y", 1.02, 0.0, {}}}; + Points[1].Measurements = {{"y", 1.02, 0.0, {}}, {"x", 1.98, 0.0, {}}}; auto Error = BenchmarkClustering::create( Points, BenchmarkClustering::ModeE::Dbscan, 2, 0.25) @@ -110,12 +110,9 @@ TEST(ClusteringTest, Ordering) { TEST(ClusteringTest, Ordering1) { std::vector Points(3); - Points[0].Measurements = { - {"x", 0.0, 0.0}}; - Points[1].Measurements = { - {"x", 1.0, 0.0}}; - Points[2].Measurements = { - {"x", 2.0, 0.0}}; + Points[0].Measurements = {{"x", 0.0, 0.0, {}}}; + Points[1].Measurements = {{"x", 1.0, 0.0, {}}}; + Points[2].Measurements = {{"x", 2.0, 0.0, {}}}; auto Clustering = BenchmarkClustering::create( Points, BenchmarkClustering::ModeE::Dbscan, 2, 1.1); @@ -127,12 +124,9 @@ TEST(ClusteringTest, Ordering1) { TEST(ClusteringTest, Ordering2) { std::vector Points(3); - Points[0].Measurements = { - {"x", 0.0, 0.0}}; - Points[1].Measurements = { - {"x", 2.0, 0.0}}; - Points[2].Measurements = { - {"x", 1.0, 0.0}}; + Points[0].Measurements = {{"x", 0.0, 0.0, {}}}; + Points[1].Measurements = {{"x", 2.0, 0.0, {}}}; + Points[2].Measurements = {{"x", 1.0, 0.0, {}}}; auto Clustering = BenchmarkClustering::create( Points, BenchmarkClustering::ModeE::Dbscan, 2, 1.1); diff --git a/llvm/unittests/tools/llvm-exegesis/Mips/BenchmarkResultTest.cpp b/llvm/unittests/tools/llvm-exegesis/Mips/BenchmarkResultTest.cpp index 201e0a8e7acce..3d02b8f648411 100644 --- a/llvm/unittests/tools/llvm-exegesis/Mips/BenchmarkResultTest.cpp +++ b/llvm/unittests/tools/llvm-exegesis/Mips/BenchmarkResultTest.cpp @@ -65,8 +65,8 @@ TEST_F(MipsBenchmarkResultTest, WriteToAndReadFromDisk) { ToDisk.CpuName = "cpu_name"; ToDisk.LLVMTriple = "llvm_triple"; ToDisk.NumRepetitions = 1; - ToDisk.Measurements.push_back(BenchmarkMeasure{"a", 1, 1}); - ToDisk.Measurements.push_back(BenchmarkMeasure{"b", 2, 2}); + ToDisk.Measurements.push_back(BenchmarkMeasure{"a", 1, 1, {}}); + ToDisk.Measurements.push_back(BenchmarkMeasure{"b", 2, 2, {}}); ToDisk.Error = "error"; ToDisk.Info = "info"; @@ -124,10 +124,10 @@ TEST_F(MipsBenchmarkResultTest, WriteToAndReadFromDisk) { TEST_F(MipsBenchmarkResultTest, PerInstructionStats) { PerInstructionStats Stats; - Stats.push(BenchmarkMeasure{"a", 0.5, 0.0}); - Stats.push(BenchmarkMeasure{"a", 1.5, 0.0}); - Stats.push(BenchmarkMeasure{"a", -1.0, 0.0}); - Stats.push(BenchmarkMeasure{"a", 0.0, 0.0}); + Stats.push(BenchmarkMeasure{"a", 0.5, 0.0, {}}); + Stats.push(BenchmarkMeasure{"a", 1.5, 0.0, {}}); + Stats.push(BenchmarkMeasure{"a", -1.0, 0.0, {}}); + Stats.push(BenchmarkMeasure{"a", 0.0, 0.0, {}}); EXPECT_EQ(Stats.min(), -1.0); EXPECT_EQ(Stats.max(), 1.5); EXPECT_EQ(Stats.avg(), 0.25); // (0.5+1.5-1.0+0.0) / 4 diff --git a/llvm/unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp b/llvm/unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp index 616f7bac54bc4..fafd08131dffc 100644 --- a/llvm/unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp +++ b/llvm/unittests/tools/llvm-exegesis/X86/BenchmarkResultTest.cpp @@ -84,8 +84,8 @@ TEST(BenchmarkResultTest, WriteToAndReadFromDisk) { ToDisk.CpuName = "cpu_name"; ToDisk.LLVMTriple = "llvm_triple"; ToDisk.NumRepetitions = 1; - ToDisk.Measurements.push_back(BenchmarkMeasure{"a", 1, 1}); - ToDisk.Measurements.push_back(BenchmarkMeasure{"b", 2, 2}); + ToDisk.Measurements.push_back(BenchmarkMeasure{"a", 1, 1, {}}); + ToDisk.Measurements.push_back(BenchmarkMeasure{"b", 2, 2, {}}); ToDisk.Error = "error"; ToDisk.Info = "info"; @@ -162,10 +162,10 @@ TEST(BenchmarkResultTest, WriteToAndReadFromDisk) { TEST(BenchmarkResultTest, PerInstructionStats) { PerInstructionStats Stats; - Stats.push(BenchmarkMeasure{"a", 0.5, 0.0}); - Stats.push(BenchmarkMeasure{"a", 1.5, 0.0}); - Stats.push(BenchmarkMeasure{"a", -1.0, 0.0}); - Stats.push(BenchmarkMeasure{"a", 0.0, 0.0}); + Stats.push(BenchmarkMeasure{"a", 0.5, 0.0, {}}); + Stats.push(BenchmarkMeasure{"a", 1.5, 0.0, {}}); + Stats.push(BenchmarkMeasure{"a", -1.0, 0.0, {}}); + Stats.push(BenchmarkMeasure{"a", 0.0, 0.0, {}}); EXPECT_EQ(Stats.min(), -1.0); EXPECT_EQ(Stats.max(), 1.5); EXPECT_EQ(Stats.avg(), 0.25); // (0.5+1.5-1.0+0.0) / 4