262 lines
8.1 KiB
C++
262 lines
8.1 KiB
C++
// Copyright 2010-2025 Google LLC
|
|
// 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 "ortools/util/stats.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "absl/log/check.h"
|
|
#include "absl/strings/str_format.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "ortools/base/logging.h"
|
|
#include "ortools/base/timer.h"
|
|
#include "ortools/port/sysinfo.h"
|
|
|
|
namespace operations_research {
|
|
|
|
std::string MemoryUsage() {
|
|
const int64_t mem = operations_research::sysinfo::MemoryUsageProcess();
|
|
static const int64_t kDisplayThreshold = 2;
|
|
static const int64_t kKiloByte = 1024;
|
|
static const int64_t kMegaByte = kKiloByte * kKiloByte;
|
|
static const int64_t kGigaByte = kMegaByte * kKiloByte;
|
|
if (mem > kDisplayThreshold * kGigaByte) {
|
|
return absl::StrFormat("%.2lf GB", mem * 1.0 / kGigaByte);
|
|
} else if (mem > kDisplayThreshold * kMegaByte) {
|
|
return absl::StrFormat("%.2lf MB", mem * 1.0 / kMegaByte);
|
|
} else if (mem > kDisplayThreshold * kKiloByte) {
|
|
return absl::StrFormat("%2lf KB", mem * 1.0 / kKiloByte);
|
|
} else {
|
|
return absl::StrFormat("%d", mem);
|
|
}
|
|
}
|
|
|
|
Stat::Stat(absl::string_view name, StatsGroup* group) : name_(name) {
|
|
group->Register(this);
|
|
}
|
|
|
|
std::string Stat::StatString() const { return name_ + ": " + ValueAsString(); }
|
|
|
|
void StatsGroup::Register(Stat* stat) { stats_.push_back(stat); }
|
|
|
|
void StatsGroup::Reset() {
|
|
for (int i = 0; i < stats_.size(); ++i) {
|
|
stats_[i]->Reset();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool CompareStatPointers(const Stat* s1, const Stat* s2) {
|
|
if (s1->Priority() == s2->Priority()) {
|
|
if (s1->Sum() == s2->Sum()) return s1->Name() < s2->Name();
|
|
return (s1->Sum() > s2->Sum());
|
|
} else {
|
|
return (s1->Priority() > s2->Priority());
|
|
}
|
|
}
|
|
|
|
// This function counts the number of codepoints in the string and assumes well
|
|
// formed UTF-8 strings. Codepoints can take up to 4 bytes.
|
|
// * 1-byte codepoint : 0yyyzzzz
|
|
// * 2-byte codepoint : 110xxxyy 10yyzzzz
|
|
// * 3-byte codepoint : 1110wwww 10xxxxyy 10yyzzzz
|
|
// * 4-byte codepoint : 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
|
|
// We count one codepoint for bytes where the two most significant bits are
|
|
// different of 0b10, effectively discarding all trailing bytes in multibyte
|
|
// codepoints.
|
|
int UTF8StrLen(absl::string_view str) {
|
|
int len = 0;
|
|
for (const char c : str) {
|
|
if ((c & 0b11'00'0000) != 0b10'00'0000) {
|
|
++len;
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string StatsGroup::StatString() const {
|
|
// Computes the longest name of all the stats we want to display.
|
|
// Also create a temporary vector so we can sort the stats by names.
|
|
int longest_name_size = 0;
|
|
std::vector<Stat*> sorted_stats;
|
|
for (int i = 0; i < stats_.size(); ++i) {
|
|
if (!stats_[i]->WorthPrinting()) continue;
|
|
// We support UTF8 characters in the stat names.
|
|
const int size = UTF8StrLen(stats_[i]->Name());
|
|
longest_name_size = std::max(longest_name_size, size);
|
|
sorted_stats.push_back(stats_[i]);
|
|
}
|
|
switch (print_order_) {
|
|
case SORT_BY_PRIORITY_THEN_VALUE:
|
|
std::sort(sorted_stats.begin(), sorted_stats.end(), CompareStatPointers);
|
|
break;
|
|
case SORT_BY_NAME:
|
|
std::sort(sorted_stats.begin(), sorted_stats.end(),
|
|
[](const Stat* s1, const Stat* s2) -> bool {
|
|
return s1->Name() < s2->Name();
|
|
});
|
|
break;
|
|
default:
|
|
LOG(FATAL) << "Unknown print order: " << print_order_;
|
|
}
|
|
|
|
// Do not display groups without print-worthy stats.
|
|
if (sorted_stats.empty()) return "";
|
|
|
|
// Pretty-print all the stats.
|
|
std::string result(name_ + " {\n");
|
|
for (int i = 0; i < sorted_stats.size(); ++i) {
|
|
result += " ";
|
|
result += sorted_stats[i]->Name();
|
|
result.append(longest_name_size - UTF8StrLen(sorted_stats[i]->Name()), ' ');
|
|
result += " : " + sorted_stats[i]->ValueAsString();
|
|
}
|
|
result += "}\n";
|
|
return result;
|
|
}
|
|
|
|
TimeDistribution* StatsGroup::LookupOrCreateTimeDistribution(
|
|
absl::string_view name) {
|
|
std::unique_ptr<TimeDistribution>& ref = time_distributions_[name];
|
|
if (ref == nullptr) {
|
|
ref = std::make_unique<TimeDistribution>(name);
|
|
Register(ref.get());
|
|
}
|
|
return ref.get();
|
|
}
|
|
|
|
DistributionStat::DistributionStat(absl::string_view name)
|
|
: Stat(name),
|
|
sum_(0.0),
|
|
average_(0.0),
|
|
sum_squares_from_average_(0.0),
|
|
min_(0.0),
|
|
max_(0.0),
|
|
num_(0) {}
|
|
|
|
DistributionStat::DistributionStat(absl::string_view name, StatsGroup* group)
|
|
: Stat(name, group),
|
|
sum_(0.0),
|
|
average_(0.0),
|
|
sum_squares_from_average_(0.0),
|
|
min_(0.0),
|
|
max_(0.0),
|
|
num_(0) {}
|
|
|
|
void DistributionStat::Reset() {
|
|
sum_ = 0.0;
|
|
average_ = 0.0;
|
|
sum_squares_from_average_ = 0.0;
|
|
min_ = 0.0;
|
|
max_ = 0.0;
|
|
num_ = 0;
|
|
}
|
|
|
|
void DistributionStat::AddToDistribution(double value) {
|
|
if (num_ == 0) {
|
|
min_ = value;
|
|
max_ = value;
|
|
sum_ = value;
|
|
average_ = value;
|
|
num_ = 1;
|
|
return;
|
|
}
|
|
min_ = std::min(min_, value);
|
|
max_ = std::max(max_, value);
|
|
sum_ += value;
|
|
++num_;
|
|
const double delta = value - average_;
|
|
average_ = sum_ / num_;
|
|
sum_squares_from_average_ += delta * (value - average_);
|
|
}
|
|
|
|
double DistributionStat::Average() const { return average_; }
|
|
|
|
double DistributionStat::StdDeviation() const {
|
|
if (num_ == 0) return 0.0;
|
|
return sqrt(sum_squares_from_average_ / num_);
|
|
}
|
|
|
|
double TimeDistribution::CyclesToSeconds(double cycles) {
|
|
const double seconds_per_cycles = CycleTimerBase::CyclesToSeconds(1);
|
|
return cycles * seconds_per_cycles;
|
|
}
|
|
|
|
std::string TimeDistribution::PrintCyclesAsTime(double cycles) {
|
|
DCHECK_GE(cycles, 0.0);
|
|
// This epsilon is just to avoid displaying 1000.00ms instead of 1.00s.
|
|
double eps1 = 1 + 1e-3;
|
|
double sec = CyclesToSeconds(cycles);
|
|
if (sec * eps1 >= 3600.0) return absl::StrFormat("%.2fh", sec / 3600.0);
|
|
if (sec * eps1 >= 60.0) return absl::StrFormat("%.2fm", sec / 60.0);
|
|
if (sec * eps1 >= 1.0) return absl::StrFormat("%.2fs", sec);
|
|
if (sec * eps1 >= 1e-3) return absl::StrFormat("%.2fms", sec * 1e3);
|
|
if (sec * eps1 >= 1e-6) return absl::StrFormat("%.2fus", sec * 1e6);
|
|
return absl::StrFormat("%.2fns", sec * 1e9);
|
|
}
|
|
|
|
void TimeDistribution::AddTimeInSec(double seconds) {
|
|
DCHECK_GE(seconds, 0.0);
|
|
const double cycles_per_seconds = 1.0 / CycleTimerBase::CyclesToSeconds(1);
|
|
AddToDistribution(seconds * cycles_per_seconds);
|
|
}
|
|
|
|
void TimeDistribution::AddTimeInCycles(double cycles) {
|
|
DCHECK_GE(cycles, 0.0);
|
|
AddToDistribution(cycles);
|
|
}
|
|
|
|
std::string TimeDistribution::ValueAsString() const {
|
|
return absl::StrFormat(
|
|
"%8u [%8s, %8s] %8s %8s %8s\n", num_, PrintCyclesAsTime(min_),
|
|
PrintCyclesAsTime(max_), PrintCyclesAsTime(Average()),
|
|
PrintCyclesAsTime(StdDeviation()), PrintCyclesAsTime(sum_));
|
|
}
|
|
|
|
void RatioDistribution::Add(double value) {
|
|
DCHECK_GE(value, 0.0);
|
|
AddToDistribution(value);
|
|
}
|
|
|
|
std::string RatioDistribution::ValueAsString() const {
|
|
return absl::StrFormat("%8u [%7.2f%%, %7.2f%%] %7.2f%% %7.2f%%\n", num_,
|
|
100.0 * min_, 100.0 * max_, 100.0 * Average(),
|
|
100.0 * StdDeviation());
|
|
}
|
|
|
|
void DoubleDistribution::Add(double value) { AddToDistribution(value); }
|
|
|
|
std::string DoubleDistribution::ValueAsString() const {
|
|
return absl::StrFormat("%8u [%8.1e, %8.1e] %8.1e %8.1e\n", num_, min_, max_,
|
|
Average(), StdDeviation());
|
|
}
|
|
|
|
void IntegerDistribution::Add(int64_t value) {
|
|
AddToDistribution(static_cast<double>(value));
|
|
}
|
|
|
|
std::string IntegerDistribution::ValueAsString() const {
|
|
return absl::StrFormat("%8u [%8.f, %8.f] %8.2f %8.2f %8.f\n", num_, min_,
|
|
max_, Average(), StdDeviation(), sum_);
|
|
}
|
|
|
|
} // namespace operations_research
|