// 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 #include #include #include #include #include #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 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& ref = time_distributions_[name]; if (ref == nullptr) { ref = std::make_unique(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(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