fabric/utils/resourceinfo.cpp
fabric/utils/resourceinfo.cpp
Namespaces
| Name |
|---|
| Syntalos |
Source code
/*
* Copyright (C) 2019-2026 Matthias Klumpp <matthias@tenstral.net>
* Copyright (C) 2014 Jakob Unterwurzacher
*
* Licensed under the GNU Lesser General Public License Version 3
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the license, or
* (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* readMemInfo() is also licensed under the MIT License:
* Copyright (C) 2014 Jakob Unterwurzacher
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "resourceinfo.h"
#include <filesystem>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <algorithm>
#include "fabric/logging.h"
namespace fs = std::filesystem;
namespace Syntalos
{
/* Parse the contents of /proc/meminfo (in buf), return value of "name"
* (example: MemTotal)
* Returns -errno if the entry cannot be found. */
static long long get_entry(const char *name, const char *buf)
{
const char *hit = strstr(buf, name);
if (hit == NULL) {
return -ENODATA;
}
errno = 0;
const long long val = strtoll(hit + strlen(name), NULL, 10);
if (errno != 0) {
int strtoll_errno = errno;
perror("get_entry: strtol() failed");
return -strtoll_errno;
}
return val;
}
/* Like get_entry(), but exit if the value cannot be found */
static long long get_entry_fatal(const char *name, const char *buf)
{
const long long val = get_entry(name, buf);
if (val < 0) {
auto log = Syntalos::getLogger("meminfo");
LOG_CRITICAL(log, "could not find entry '{}' in /proc/meminfo: {}", name, strerror((int)-val));
log->flush_log();
std::abort();
}
return val;
}
/* If the kernel does not provide MemAvailable (introduced in Linux 3.14),
* approximate it using other data we can get */
static long long available_guesstimate(const char *buf)
{
long long Cached = get_entry_fatal("Cached:", buf);
long long MemFree = get_entry_fatal("MemFree:", buf);
long long Buffers = get_entry_fatal("Buffers:", buf);
long long Shmem = get_entry_fatal("Shmem:", buf);
return MemFree + Cached + Buffers - Shmem;
}
MemInfo readMemInfo()
{
auto log = Syntalos::getLogger("meminfo");
// Note that we do not need to close static FDs that we ensure to
// `fopen()` maximally once.
static FILE *fd;
// On Linux 5.3, "wc -c /proc/meminfo" counts 1391 bytes.
// 8192 should be enough for the foreseeable future.
char buf[8192] = {0};
MemInfo m = {0, 0, 0, 0, 0, 0};
if (fd == NULL)
fd = fopen("/proc/meminfo", "r");
if (fd == NULL) {
LOG_CRITICAL(log, "could not open /proc/meminfo: {}", strerror(errno));
return m;
}
rewind(fd);
size_t len = fread(buf, 1, sizeof(buf) - 1, fd);
if (ferror(fd)) {
LOG_CRITICAL(log, "could not read /proc/meminfo: {}", strerror(errno));
return m;
}
if (len == 0) {
LOG_CRITICAL(log, "could not read /proc/meminfo: 0 bytes returned");
return m;
}
m.memTotalKiB = get_entry_fatal("MemTotal:", buf);
long long MemAvailable = get_entry("MemAvailable:", buf);
if (MemAvailable < 0) {
// kernel was too old
MemAvailable = available_guesstimate(buf);
}
// Calculate percentages
m.memAvailableKiB = MemAvailable;
m.memAvailablePercent = (double)MemAvailable * 100 / (double)m.memTotalKiB;
// Convert kiB to MiB
m.memAvailableMiB = MemAvailable / 1024;
// swap is optional, the entries are 0 if none is configured
m.swapTotalKiB = std::max(get_entry("SwapTotal:", buf), 0LL);
m.swapFreeKiB = std::max(get_entry("SwapFree:", buf), 0LL);
return m;
}
MemPressure readMemPressure()
{
MemPressure p = {false, 0, 0, 0, 0};
// Do not keep the file open: unlike /proc/meminfo, a PSI file that was opened
// with psi=0 keeps failing, so we just retry cheaply each time.
FILE *f = fopen("/proc/pressure/memory", "r");
if (f == NULL)
return p;
char line[256];
bool haveSome = false;
while (fgets(line, sizeof(line), f) != NULL) {
double avg10 = 0, avg60 = 0, avg300 = 0;
if (sscanf(line, "some avg10=%lf avg60=%lf avg300=%lf", &avg10, &avg60, &avg300) == 3) {
p.someAvg10 = avg10;
p.someAvg60 = avg60;
haveSome = true;
} else if (sscanf(line, "full avg10=%lf avg60=%lf avg300=%lf", &avg10, &avg60, &avg300) == 3) {
p.fullAvg10 = avg10;
p.fullAvg60 = avg60;
}
}
fclose(f);
p.available = haveSome;
return p;
}
DiskSpaceInfo diskSpaceInfo(const QString &path)
{
DiskSpaceInfo info;
std::error_code ec;
// find the closest existing directory, so we can also check locations that will be created
fs::path p = fs::absolute(path.toStdString(), ec);
if (ec)
return info;
while (!p.empty() && !fs::exists(p, ec)) {
const auto parent = p.parent_path();
if (parent == p)
break;
p = parent;
}
struct stat st;
if (::stat(p.c_str(), &st) != 0)
return info;
const auto space = fs::space(p, ec);
if (ec)
return info;
info.valid = true;
info.deviceId = st.st_dev;
info.bytesAvailable = static_cast<qint64>(space.available);
info.bytesTotal = static_cast<qint64>(space.capacity);
// walk up until we cross a filesystem boundary to find the mount point
auto mount = fs::canonical(p, ec);
if (ec)
mount = p;
while (mount.has_parent_path() && mount.parent_path() != mount) {
struct stat pst;
if (::stat(mount.parent_path().c_str(), &pst) != 0 || pst.st_dev != st.st_dev)
break;
mount = mount.parent_path();
}
info.mountPoint = QString::fromStdString(mount.string());
return info;
}
qint64 directoryTotalSize(const QString &path)
{
qint64 total = 0;
std::error_code ec;
for (auto it =
fs::recursive_directory_iterator(path.toStdString(), fs::directory_options::skip_permission_denied, ec);
it != fs::recursive_directory_iterator();
it.increment(ec)) {
if (ec)
break;
if (it->is_regular_file(ec) && !it->is_symlink(ec))
total += static_cast<qint64>(it->file_size(ec));
}
return total;
}
ResourceTrend::ResourceTrend(double minRelevantRate, double smoothing)
: m_minRelevantRate(std::max(minRelevantRate, 0.0)),
m_smoothing(std::clamp(smoothing, 0.0, 1.0)),
m_lastRemaining(-1),
m_rate(-1.0)
{
}
void ResourceTrend::reset()
{
m_lastRemaining = -1;
m_rate = -1.0;
m_timer.invalidate();
}
void ResourceTrend::addSample(qint64 remaining)
{
const double elapsedSec = m_timer.isValid() ? m_timer.nsecsElapsed() / 1.0e9 : 0.0;
m_timer.start();
addSample(remaining, elapsedSec);
}
void ResourceTrend::addSample(qint64 remaining, double elapsedSec)
{
// ignore samples taken (almost) at the same time as the previous one, they only add noise
constexpr double minSampleIntervalSec = 0.1;
if (m_lastRemaining >= 0 && elapsedSec >= minSampleIntervalSec) {
const double rate = std::max(0.0, static_cast<double>(m_lastRemaining - remaining) / elapsedSec);
m_rate = (m_rate < 0.0) ? rate : (m_smoothing * rate + (1.0 - m_smoothing) * m_rate);
}
if (m_lastRemaining < 0 || elapsedSec >= minSampleIntervalSec)
m_lastRemaining = remaining;
}
bool ResourceTrend::hasRate() const
{
return m_rate >= 0.0 && m_rate >= m_minRelevantRate && m_rate > 0.0;
}
double ResourceTrend::consumptionRate() const
{
return m_rate;
}
double ResourceTrend::secondsUntilDepleted() const
{
if (!hasRate() || m_lastRemaining < 0)
return -1.0;
return static_cast<double>(m_lastRemaining) / m_rate;
}
qint64 ResourceTrend::lastRemaining() const
{
return m_lastRemaining;
}
} // namespace Syntalos
Updated on 2026-09-06 at 20:30:11 +0000