blob: c58f3cb78fb9cc8aea88acc49d45e7c1c2121762 [file] [log] [blame]
// Copyright 2018 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "time_util.h"
#include <algorithm>
#include <array>
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "glog/logging.h"
namespace devtools_goma {
namespace {
// See explanation of this value in comments of ComputeDataRateInKBps().
const int64_t kMsToNsRatio = absl::Milliseconds(1) / absl::Nanoseconds(1);
// Rounds an absl::Duration to the nearest unit indicated by |unit|. Can round
// up or down, depending on which is closer.
absl::Duration RoundDuration(absl::Duration time, absl::Duration unit) {
DCHECK_GT(unit, absl::ZeroDuration());
return absl::Trunc(time + unit / 2, unit);
}
// Same as absl::FormatDuration, but adds a space before the "ms", "us", "ns",
// or "s".
// |unit| is the nearest unit to which to round |time|.
std::string FormatDurationWithSpace(absl::Duration time, absl::Duration unit) {
std::string result = absl::FormatDuration(RoundDuration(time, unit));
const size_t len = result.size();
DCHECK_GT(len, 0);
// Do not modify the string if it was not properly formed, too short, or "0".
if (result[len - 1] != 's' || len < 2) {
return result;
}
// Insert a space if it was "ms", "us", or "ns".
if (result[len - 2] == 'm' || result[len - 2] == 'u' ||
result[len - 2] == 'n') {
return result.insert(len - 2, 1, ' ');
}
// Otherwise, make sure that the digit before the 's' was a digit.
if (absl::ascii_isdigit(result[len - 2])) {
return result.insert(len - 1, 1, ' ');
}
return result;
}
} // namespace
int64_t ComputeDataRateInKBps(int64_t num_bytes, absl::Duration time) {
if (time <= absl::ZeroDuration()) {
return -1;
}
// Explanation of the computation, where N = |num_bytes| and T = |time|:
//
// Computation of bytes per second: N / ToSeconds(T)
//
// N 1 kB N
// Kilobytes per second: ------------ x ---------- = -------------------
// ToSeconds(T) 1000 bytes ToSeconds(T) * 1000
//
// ToSeconds(T) * 1000 = ToMillisec(T). Thus "N bytes per millisecond" is
// equivalent to "N kilobytes per second."
//
// So the computation becomes: N / ToMillisec(T).
//
// But if |T| < 1 ms, this value will be rounded down to 0, resulting in a
// division by 0. To avoid this, do the following:
//
// N 1000000 N * 1000000
// ------------- * ------- = -----------------------
// ToMillisec(T) 1000000 ToMillisec(T) * 1000000
//
// Once again, note that ToMillisec(T) * 1000000 = ToNanosec(T).
//
// Now kilobytes per second is calculated as: N * 1000000 / ToNanosec(T).
//
// The "1000000" is just the ratio of a millisecond to a nanosecond.
return num_bytes * kMsToNsRatio / ToInt64Nanoseconds(time);
}
int DurationToIntMs(absl::Duration duration) {
return static_cast<int>(absl::ToInt64Milliseconds(
RoundDuration(duration, absl::Milliseconds(1))));
}
std::string FormatDurationInMilliseconds(absl::Duration time) {
return absl::StrCat(
absl::ToInt64Milliseconds(RoundDuration(time, absl::Milliseconds(1))),
" ms");
}
std::string FormatDurationInMicroseconds(absl::Duration time) {
return absl::StrCat(
absl::ToInt64Microseconds(RoundDuration(time, absl::Microseconds(1))),
" us");
}
std::string FormatDurationToThreeDigits(absl::Duration time) {
// This code assumes that absl::Duration does not have resolution less than
// nanoseconds.
// An array of durations that contains increasing time resolution units, in
// powers of 10 starting from 1 ns. This must be in sorted order because it is
// searched by std::upper_bound().
constexpr std::array<absl::Duration, 9> kTimeResolutions = {
absl::Nanoseconds(1),
absl::Nanoseconds(10),
absl::Nanoseconds(100),
absl::Microseconds(1),
absl::Microseconds(10),
absl::Microseconds(100),
absl::Milliseconds(1),
absl::Milliseconds(10),
absl::Milliseconds(100),
};
// Find the resolution required to print |time| with no more than three
// significant figures.
auto iterator = std::upper_bound(kTimeResolutions.begin(),
kTimeResolutions.end(), time / 1000);
const absl::Duration resolution =
iterator != kTimeResolutions.end() ? *iterator : absl::Seconds(1);
if (time < absl::Minutes(1)) {
return FormatDurationWithSpace(time, resolution);
}
// If the formatted time includes minutes or anything larger, just use the
// regular FormatDuration().
return absl::FormatDuration(RoundDuration(time, resolution));
}
} // namespace devtools_goma