403 lines
10 KiB
C++
403 lines
10 KiB
C++
///
|
|
/// \author John Farrier
|
|
///
|
|
/// \copyright Copyright 2015, 2016, 2017, 2018. 2019 John Farrier
|
|
///
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
/// you may not use this file except in compliance with the License.
|
|
/// You may obtain a copy of the License at
|
|
///
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
///
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
/// See the License for the specific language governing permissions and
|
|
/// limitations under the License.
|
|
///
|
|
|
|
#include <assert.h>
|
|
#include <celero/Archive.h>
|
|
#include <celero/Benchmark.h>
|
|
#include <celero/FileReader.h>
|
|
#include <celero/PimplImpl.h>
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
using namespace celero;
|
|
|
|
///
|
|
/// Structure to assist with archiving data during runtime and to a file.
|
|
///
|
|
struct ArchiveEntry
|
|
{
|
|
ArchiveEntry()
|
|
: GroupName(),
|
|
RunName(),
|
|
ExperimentValue(0),
|
|
ExperimentValueScale(0),
|
|
FirstRanDate(0),
|
|
TotalSamplesCollected(0),
|
|
AverageBaseline(0),
|
|
MinBaseline(0),
|
|
MinBaseline_TimeSinceEpoch(0),
|
|
MinStats(),
|
|
MaxBaseline(0),
|
|
MaxBaseline_TimeSinceEpoch(0),
|
|
MaxStats(),
|
|
CurrentBaseline(0),
|
|
CurrentBaseline_TimeSinceEpoch(0),
|
|
CurrentStats(),
|
|
Failure(false)
|
|
{
|
|
}
|
|
|
|
static void WriteHeader(std::ostream& str)
|
|
{
|
|
str << "GroupName,RunName,Failure,ExperimentValue,ExperimentValueScale,FirstRanDate,TotalSamplesCollected,AverageBaseline,";
|
|
str << "MinBaseline,MinBaselineTimeSinceEpoch,";
|
|
str << "MinStatSize,MinStatMean,MinStatVariance,MinStatStandardDeviation,MinStatSkewness,MinStatKurtosis,";
|
|
str << "MinStatMin,MinStatMax,";
|
|
str << "MaxBaseline,MaxBaselineTimeSinceEpoch,";
|
|
str << "MaxStatSize,MaxStatMean,MaxStatVariance,MaxStatStandardDeviation,MaxStatSkewness,MaxStatKurtosis,";
|
|
str << "MaxStatMin,MaxStatMax,";
|
|
str << "CurrentBaseline,CurrentBaselineTimeSinceEpoch,";
|
|
str << "CurrentStatSize,CurrentStatMean,CurrentStatVariance,CurrentStatStandardDeviation,CurrentStatSkewness,CurrentStatKurtosis,";
|
|
str << "CurrentStatMin,CurrentStatMax" << std::endl;
|
|
}
|
|
|
|
struct Stat
|
|
{
|
|
Stat() : Size(0), Mean(0), Variance(0), StandardDeviation(0), Skewness(0), Kurtosis(0), Min(0), Max(0)
|
|
{
|
|
}
|
|
|
|
Stat& operator=(const celero::Statistics<int64_t>& s)
|
|
{
|
|
this->Size = s.getSize();
|
|
this->Mean = s.getMean();
|
|
this->Variance = s.getVariance();
|
|
this->StandardDeviation = s.getStandardDeviation();
|
|
this->Skewness = s.getSkewness();
|
|
this->Kurtosis = s.getKurtosis();
|
|
this->Min = s.getMin();
|
|
this->Max = s.getMax();
|
|
|
|
return *this;
|
|
}
|
|
|
|
uint64_t Size;
|
|
double Mean;
|
|
double Variance;
|
|
double StandardDeviation;
|
|
double Skewness;
|
|
double Kurtosis;
|
|
uint64_t Min;
|
|
uint64_t Max;
|
|
};
|
|
|
|
std::string GroupName;
|
|
std::string RunName;
|
|
|
|
/// The data set size, if one was specified.
|
|
int64_t ExperimentValue;
|
|
double ExperimentValueScale;
|
|
|
|
uint64_t FirstRanDate;
|
|
uint32_t TotalSamplesCollected;
|
|
|
|
double AverageBaseline;
|
|
|
|
double MinBaseline;
|
|
uint64_t MinBaseline_TimeSinceEpoch;
|
|
Stat MinStats;
|
|
|
|
double MaxBaseline;
|
|
uint64_t MaxBaseline_TimeSinceEpoch;
|
|
Stat MaxStats;
|
|
|
|
double CurrentBaseline;
|
|
uint64_t CurrentBaseline_TimeSinceEpoch;
|
|
Stat CurrentStats;
|
|
|
|
bool Failure;
|
|
};
|
|
|
|
///
|
|
/// Overload operator<< to allow for easy output of result data to a human-readable text file.
|
|
///
|
|
std::ostream& operator<<(std::ostream& str, ArchiveEntry::Stat const& data)
|
|
{
|
|
str << data.Size << ",";
|
|
str << data.Mean << ",";
|
|
str << data.Variance << ",";
|
|
str << data.StandardDeviation << ",";
|
|
str << data.Skewness << ",";
|
|
str << data.Kurtosis << ",";
|
|
str << data.Min << ",";
|
|
str << data.Max;
|
|
return str;
|
|
}
|
|
|
|
///
|
|
/// Overload operator<< to allow for easy output of result data to a human-readable text file.
|
|
///
|
|
std::ostream& operator<<(std::ostream& str, ArchiveEntry const& data)
|
|
{
|
|
str << data.GroupName << ",";
|
|
str << data.RunName << ",";
|
|
str << data.Failure << ",";
|
|
str << data.ExperimentValue << ",";
|
|
str << data.ExperimentValueScale << ",";
|
|
str << data.FirstRanDate << ",";
|
|
str << data.TotalSamplesCollected << ",";
|
|
str << data.AverageBaseline << ",";
|
|
str << data.MinBaseline << ",";
|
|
str << data.MinBaseline_TimeSinceEpoch << ",";
|
|
str << data.MinStats << ",";
|
|
str << data.MaxBaseline << ",";
|
|
str << data.MaxBaseline_TimeSinceEpoch << ",";
|
|
str << data.MaxStats << ",";
|
|
str << data.CurrentBaseline << ",";
|
|
str << data.CurrentBaseline_TimeSinceEpoch << ",";
|
|
str << data.CurrentStats << std::endl;
|
|
return str;
|
|
}
|
|
|
|
///
|
|
/// Overload operator>> to allow for easy input of result data from a text file.
|
|
///
|
|
std::istream& operator>>(std::istream& str, ArchiveEntry::Stat& data)
|
|
{
|
|
// Use FieldReader to classify commas as whitespace.
|
|
str.imbue(std::locale(std::locale(), new celero::FieldReader));
|
|
|
|
str >> data.Size;
|
|
str >> data.Mean;
|
|
str >> data.Variance;
|
|
str >> data.StandardDeviation;
|
|
str >> data.Skewness;
|
|
str >> data.Kurtosis;
|
|
str >> data.Min;
|
|
str >> data.Max;
|
|
return str;
|
|
}
|
|
|
|
///
|
|
/// Overload operator>> to allow for easy input of result data from a text file.
|
|
///
|
|
std::istream& operator>>(std::istream& str, ArchiveEntry& data)
|
|
{
|
|
// Use FieldReader to classify commas as whitespace.
|
|
str.imbue(std::locale(std::locale(), new celero::FieldReader));
|
|
|
|
str >> data.GroupName;
|
|
str >> data.RunName;
|
|
str >> data.Failure;
|
|
str >> data.ExperimentValue;
|
|
str >> data.ExperimentValueScale;
|
|
str >> data.FirstRanDate;
|
|
str >> data.TotalSamplesCollected;
|
|
str >> data.AverageBaseline;
|
|
str >> data.MinBaseline;
|
|
str >> data.MinBaseline_TimeSinceEpoch;
|
|
str >> data.MinStats;
|
|
str >> data.MaxBaseline;
|
|
str >> data.MaxBaseline_TimeSinceEpoch;
|
|
str >> data.MaxStats;
|
|
str >> data.CurrentBaseline;
|
|
str >> data.CurrentBaseline_TimeSinceEpoch;
|
|
str >> data.CurrentStats;
|
|
return str;
|
|
}
|
|
|
|
///
|
|
/// \class Impl
|
|
///
|
|
class celero::Archive::Impl
|
|
{
|
|
public:
|
|
Impl() : results(), fileName()
|
|
{
|
|
}
|
|
|
|
/// Return milliseconds since epoch.
|
|
uint64_t now() const
|
|
{
|
|
return static_cast<uint64_t>(
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
|
|
}
|
|
|
|
void readExistingResults()
|
|
{
|
|
// Read in existing results?
|
|
std::ifstream is;
|
|
is.open(this->fileName, std::fstream::in);
|
|
|
|
if((is.is_open() == true) && (is.good() == true) && (is.fail() == false))
|
|
{
|
|
// Throw away the header.
|
|
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
|
|
|
// Read in existing results.
|
|
while((is.eof() == false) && (is.tellg() >= 0))
|
|
{
|
|
ArchiveEntry r;
|
|
is >> r;
|
|
|
|
if(r.GroupName.empty() == false)
|
|
{
|
|
this->results.push_back(r);
|
|
}
|
|
}
|
|
|
|
// Close the file for reading.
|
|
is.close();
|
|
}
|
|
}
|
|
|
|
std::vector<ArchiveEntry> results;
|
|
std::string fileName;
|
|
};
|
|
|
|
Archive::Archive() : pimpl()
|
|
{
|
|
}
|
|
|
|
Archive::~Archive()
|
|
{
|
|
}
|
|
|
|
Archive& Archive::Instance()
|
|
{
|
|
static Archive singleton;
|
|
return singleton;
|
|
}
|
|
|
|
void Archive::setFileName(const std::string& x)
|
|
{
|
|
if(x.empty() == false)
|
|
{
|
|
this->pimpl->fileName = x;
|
|
this->pimpl->readExistingResults();
|
|
}
|
|
}
|
|
|
|
void Archive::add(std::shared_ptr<celero::ExperimentResult> x)
|
|
{
|
|
if(x == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const auto found = std::find_if(std::begin(this->pimpl->results), std::end(this->pimpl->results), [x](const ArchiveEntry& r) -> bool {
|
|
return (r.GroupName == x->getExperiment()->getBenchmark()->getName()) && (r.RunName == x->getExperiment()->getName())
|
|
&& (r.ExperimentValue == x->getProblemSpaceValue());
|
|
});
|
|
|
|
if(found != std::end(this->pimpl->results))
|
|
{
|
|
if(x->getFailure() == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
found->CurrentBaseline = x->getBaselineMeasurement();
|
|
found->CurrentBaseline_TimeSinceEpoch = this->pimpl->now();
|
|
found->CurrentStats = *x->getTimeStatistics();
|
|
|
|
if(found->Failure || found->CurrentBaseline <= found->MinBaseline)
|
|
{
|
|
found->MinBaseline = found->CurrentBaseline;
|
|
found->MinBaseline_TimeSinceEpoch = found->CurrentBaseline_TimeSinceEpoch;
|
|
found->MinStats = found->CurrentStats;
|
|
}
|
|
|
|
if(found->Failure || found->CurrentBaseline >= found->MaxBaseline)
|
|
{
|
|
found->MaxBaseline = found->CurrentBaseline;
|
|
found->MaxBaseline_TimeSinceEpoch = found->CurrentBaseline_TimeSinceEpoch;
|
|
found->MaxStats = found->CurrentStats;
|
|
}
|
|
|
|
// This is not good IEEE math.
|
|
if(found->Failure == false)
|
|
{
|
|
found->AverageBaseline =
|
|
((found->AverageBaseline * found->TotalSamplesCollected) + found->CurrentBaseline) / (found->TotalSamplesCollected + 1);
|
|
}
|
|
else
|
|
{
|
|
found->AverageBaseline = found->CurrentBaseline;
|
|
}
|
|
|
|
found->TotalSamplesCollected++;
|
|
}
|
|
else
|
|
{
|
|
ArchiveEntry r;
|
|
|
|
const auto experiment = x->getExperiment();
|
|
|
|
if(experiment != nullptr)
|
|
{
|
|
r.GroupName = experiment->getBenchmark()->getName();
|
|
r.RunName = experiment->getName();
|
|
r.Failure = x->getFailure();
|
|
r.FirstRanDate = this->pimpl->now();
|
|
r.AverageBaseline = x->getBaselineMeasurement();
|
|
r.ExperimentValue = x->getProblemSpaceValue();
|
|
r.ExperimentValueScale = x->getProblemSpaceValueScale();
|
|
r.TotalSamplesCollected = x->getFailure() ? 0 : 1;
|
|
|
|
r.CurrentBaseline = x->getBaselineMeasurement();
|
|
r.CurrentBaseline_TimeSinceEpoch = r.FirstRanDate;
|
|
r.CurrentStats = *x->getTimeStatistics();
|
|
|
|
r.MaxBaseline = x->getBaselineMeasurement();
|
|
r.MaxBaseline_TimeSinceEpoch = r.FirstRanDate;
|
|
r.MaxStats = *x->getTimeStatistics();
|
|
|
|
r.MinBaseline = x->getBaselineMeasurement();
|
|
r.MinBaseline_TimeSinceEpoch = r.FirstRanDate;
|
|
r.MinStats = *x->getTimeStatistics();
|
|
|
|
this->pimpl->results.push_back(r);
|
|
}
|
|
}
|
|
|
|
this->save();
|
|
}
|
|
|
|
void Archive::save()
|
|
{
|
|
if(this->pimpl->fileName.empty() == false)
|
|
{
|
|
// Get ready to write out new results.
|
|
// We will write all known results every time, replacing file contents.
|
|
std::ofstream os;
|
|
os.open(this->pimpl->fileName.c_str(), std::fstream::out);
|
|
|
|
if(os.is_open() == true)
|
|
{
|
|
ArchiveEntry::WriteHeader(os);
|
|
|
|
for(auto& i : this->pimpl->results)
|
|
{
|
|
os << i;
|
|
}
|
|
|
|
os.flush();
|
|
os.close();
|
|
}
|
|
else
|
|
{
|
|
std::cerr << "Celero: Could not open result output file: \"" << this->pimpl->fileName << "\"" << std::endl;
|
|
}
|
|
}
|
|
}
|