diff --git a/media/libnblog/PerformanceAnalysis.cpp b/media/libnblog/PerformanceAnalysis.cpp index 46912d37e3..e91a5110d3 100644 --- a/media/libnblog/PerformanceAnalysis.cpp +++ b/media/libnblog/PerformanceAnalysis.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -51,23 +52,21 @@ namespace ReportPerformance { void Histogram::add(double value) { - // TODO Handle domain and range error exceptions? - const int binIndex = lround((value - mLow) / mBinSize); - if (binIndex < 0) { - mLowCount++; - } else if (binIndex >= mNumBins) { - mHighCount++; - } else { - mBins[binIndex]++; + if (mBinSize <= 0 || mBins.size() < 2) { + return; } + // TODO Handle domain and range error exceptions? + const int unboundedIndex = lround((value - mLow) / mBinSize) + 1; + // std::clamp is introduced in C++17 + //const int index = std::clamp(unboundedIndex, 0, (int)(mBins.size() - 1)); + const int index = std::max(0, std::min((int)(mBins.size() - 1), unboundedIndex)); + mBins[index]++; mTotalCount++; } void Histogram::clear() { std::fill(mBins.begin(), mBins.end(), 0); - mLowCount = 0; - mHighCount = 0; mTotalCount = 0; } @@ -81,27 +80,75 @@ std::string Histogram::toString() const { static constexpr char kDivider = '|'; ss << kVersion << "," << mBinSize << "," << mNumBins << "," << mLow << ",{"; bool first = true; - if (mLowCount != 0) { - ss << "-1" << kDivider << mLowCount; - first = false; - } - for (size_t i = 0; i < mNumBins; i++) { + for (size_t i = 0; i < mBins.size(); i++) { if (mBins[i] != 0) { if (!first) { ss << ","; } - ss << i << kDivider << mBins[i]; + ss << static_cast(i) - 1 << kDivider << mBins[i]; first = false; } } - if (mHighCount != 0) { - if (!first) { - ss << ","; + ss << "}"; + + return ss.str(); +} + +std::string Histogram::asciiArtString(size_t indent) const { + if (totalCount() == 0 || mBinSize <= 0 || mBins.size() < 2) { + return ""; + } + + static constexpr char kMarker = '-'; + // One increment is considered one step of a bin's height. + static constexpr size_t kMarkersPerIncrement = 2; + static constexpr size_t kMaxIncrements = 64 + 1; + static constexpr size_t kMaxNumberWidth = 7; + static const std::string kMarkers(kMarkersPerIncrement * kMaxIncrements, kMarker); + static const std::string kSpaces(kMarkersPerIncrement * kMaxIncrements, ' '); + // get the last n characters of s, or the whole string if it is shorter + auto getTail = [](const size_t n, const std::string &s) { + return s.c_str() + s.size() - std::min(n, s.size()); + }; + + // Since totalCount() > 0, mBins is not empty and maxCount > 0. + const unsigned maxCount = *std::max_element(mBins.begin(), mBins.end()); + const size_t maxIncrements = log2(maxCount) + 1; + + std::stringstream ss; + + // Non-zero bins must exist at this point because totalCount() > 0. + size_t firstNonZeroBin = 0; + // If firstNonZeroBin reaches mBins.size() - 1, then it must be a nonzero bin. + for (; firstNonZeroBin < mBins.size() - 1 && mBins[firstNonZeroBin] == 0; firstNonZeroBin++) {} + const size_t firstBinToPrint = firstNonZeroBin == 0 ? 0 : firstNonZeroBin - 1; + + size_t lastNonZeroBin = mBins.size() - 1; + // If lastNonZeroBin reaches 0, then it must be a nonzero bin. + for (; lastNonZeroBin > 0 && mBins[lastNonZeroBin] == 0; lastNonZeroBin--) {} + const size_t lastBinToPrint = lastNonZeroBin == mBins.size() - 1 ? lastNonZeroBin + : lastNonZeroBin + 1; + + for (size_t bin = firstBinToPrint; bin <= lastBinToPrint; bin++) { + ss << std::setw(indent + kMaxNumberWidth); + if (bin == 0) { + ss << "<"; + } else if (bin == mBins.size() - 1) { + ss << ">"; + } else { + ss << mLow + (bin - 1) * mBinSize; } - ss << mNumBins << kDivider << mHighCount; - first = false; + ss << " |"; + size_t increments = 0; + const uint64_t binCount = mBins[bin]; + if (binCount > 0) { + increments = log2(binCount) + 1; + ss << getTail(increments * kMarkersPerIncrement, kMarkers); + } + ss << getTail((maxIncrements - increments + 1) * kMarkersPerIncrement, kSpaces) + << binCount << "\n"; } - ss << "}"; + ss << "\n"; return ss.str(); } diff --git a/media/libnblog/include/media/nblog/PerformanceAnalysis.h b/media/libnblog/include/media/nblog/PerformanceAnalysis.h index 983b28a647..37a30aa3f5 100644 --- a/media/libnblog/include/media/nblog/PerformanceAnalysis.h +++ b/media/libnblog/include/media/nblog/PerformanceAnalysis.h @@ -58,16 +58,16 @@ public: /** * \brief Creates a Histogram object. * - * \param binSize the width of each bin of the histogram. + * \param binSize the width of each bin of the histogram, must be greater than 0. * Units are whatever data the caller decides to store. - * \param numBins the number of bins desired in the histogram range. + * \param numBins the number of bins desired in the histogram range, must be greater than 0. * \param low the lower bound of the histogram bucket values. * Units are whatever data the caller decides to store. * Note that the upper bound can be calculated by the following: * upper = lower + binSize * numBins. */ Histogram(double binSize, size_t numBins, double low = 0.) - : mBinSize(binSize), mNumBins(numBins), mLow(low), mBins(mNumBins) {} + : mBinSize(binSize), mNumBins(numBins), mLow(low), mBins(mNumBins + 2) {} Histogram(const Config &c) : Histogram(c.binSize, c.numBins, c.low) {} @@ -112,18 +112,24 @@ public: */ std::string toString() const; + // Draw log scale sideways histogram as ASCII art and store as a std::string. + // Empty string is returned if totalCount() == 0. + std::string asciiArtString(size_t indent = 0) const; + private: // Histogram version number. static constexpr int kVersion = 1; - const double mBinSize; // Size of each bucket - const size_t mNumBins; // Number of buckets in histogram range - const double mLow; // Lower bound of values - std::vector mBins; // Data structure to store the actual histogram + const double mBinSize; // Size of each bucket + const size_t mNumBins; // Number of buckets in range (excludes low and high) + const double mLow; // Lower bound of values + + // Data structure to store the actual histogram. Counts of bin values less than mLow + // are stored in mBins[0]. Bin index i corresponds to mBins[i+1]. Counts of bin values + // >= high are stored in mBins[mNumBins + 1]. + std::vector mBins; - int mLowCount = 0; // Number of values less than mLow - int mHighCount = 0; // Number of values >= mLow + mBinSize * mNumBins - uint64_t mTotalCount = 0; // Total number of values recorded + uint64_t mTotalCount = 0; // Total number of values recorded }; // This is essentially the same as class PerformanceAnalysis, but PerformanceAnalysis