727417c140
Change-Id: I367bb88c0acc22c0f57255403319d50c4ece849c Signed-off-by: Michael Meeks <michael.meeks@collabora.com>
464 lines
12 KiB
C++
464 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* Copyright the Collabora Online contributors.
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
#include <config.h>
|
|
|
|
#include "Util.hpp"
|
|
|
|
#ifdef __linux__
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#elif defined __FreeBSD__
|
|
#include <sys/resource.h>
|
|
#endif
|
|
|
|
#include <dirent.h>
|
|
#include <spawn.h>
|
|
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
|
|
#include <Poco/Exception.h>
|
|
#include "Log.hpp"
|
|
|
|
namespace Util
|
|
{
|
|
bool isMobileApp() { return false; }
|
|
|
|
DirectoryCounter::DirectoryCounter(const char* procPath)
|
|
: _tasks(opendir(procPath))
|
|
{
|
|
if (!_tasks)
|
|
LOG_ERR("No proc mounted, can't count threads");
|
|
}
|
|
|
|
DirectoryCounter::~DirectoryCounter() { closedir(reinterpret_cast<DIR*>(_tasks)); }
|
|
|
|
int DirectoryCounter::count()
|
|
{
|
|
auto dir = reinterpret_cast<DIR*>(_tasks);
|
|
|
|
if (!dir)
|
|
return -1;
|
|
|
|
rewinddir(dir);
|
|
|
|
int tasks = 0;
|
|
struct dirent* i;
|
|
while ((i = readdir(dir)))
|
|
{
|
|
if (i->d_name[0] != '.')
|
|
tasks++;
|
|
}
|
|
|
|
return tasks;
|
|
}
|
|
|
|
int spawnProcess(const std::string& cmd, const StringVector& args)
|
|
{
|
|
// Create a vector of zero-terminated strings.
|
|
std::vector<std::string> argStrings;
|
|
for (const auto& arg : args)
|
|
argStrings.push_back(args.getParam(arg));
|
|
|
|
std::vector<char*> params;
|
|
params.push_back(const_cast<char*>(cmd.c_str()));
|
|
for (const auto& i : argStrings)
|
|
params.push_back(const_cast<char*>(i.c_str()));
|
|
params.push_back(nullptr);
|
|
|
|
pid_t pid = -1;
|
|
int status = posix_spawn(&pid, params[0], nullptr, nullptr, params.data(), environ);
|
|
if (status < 0)
|
|
{
|
|
LOG_ERR("Failed to posix_spawn for command '" << cmd);
|
|
throw Poco::SystemException("Failed to fork posix_spawn command ", cmd);
|
|
}
|
|
|
|
return pid;
|
|
}
|
|
|
|
static const char* startsWith(const char* line, const char* tag, std::size_t tagLen)
|
|
{
|
|
assert(strlen(tag) == tagLen);
|
|
|
|
std::size_t len = tagLen;
|
|
if (!strncmp(line, tag, len))
|
|
{
|
|
while (!isdigit(line[len]) && line[len] != '\0')
|
|
++len;
|
|
|
|
return line + len;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::string getHumanizedBytes(unsigned long nBytes)
|
|
{
|
|
constexpr unsigned factor = 1024;
|
|
short count = 0;
|
|
float val = nBytes;
|
|
while (val >= factor && count < 4)
|
|
{
|
|
val /= factor;
|
|
count++;
|
|
}
|
|
std::string unit;
|
|
switch (count)
|
|
{
|
|
case 0:
|
|
unit = "";
|
|
break;
|
|
case 1:
|
|
unit = "ki";
|
|
break;
|
|
case 2:
|
|
unit = "Mi";
|
|
break;
|
|
case 3:
|
|
unit = "Gi";
|
|
break;
|
|
case 4:
|
|
unit = "Ti";
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
unit += 'B';
|
|
std::stringstream ss;
|
|
ss << std::fixed << std::setprecision(1) << val << ' ' << unit;
|
|
return ss.str();
|
|
}
|
|
|
|
std::size_t getTotalSystemMemoryKb()
|
|
{
|
|
std::size_t totalMemKb = 0;
|
|
FILE* file = fopen("/proc/meminfo", "r");
|
|
if (file != nullptr)
|
|
{
|
|
char line[4096] = { 0 };
|
|
// coverity[tainted_data_argument : FALSE] - we trust the kernel-provided data
|
|
while (fgets(line, sizeof(line), file))
|
|
{
|
|
const char* value;
|
|
if ((value = startsWith(line, "MemTotal:", 9)))
|
|
{
|
|
totalMemKb = atoll(value);
|
|
break;
|
|
}
|
|
}
|
|
fclose(file);
|
|
}
|
|
|
|
return totalMemKb;
|
|
}
|
|
|
|
std::size_t getFromCGroup(const std::string& group, const std::string& key)
|
|
{
|
|
std::size_t num = 0;
|
|
|
|
std::string groupPath;
|
|
FILE* cg = fopen("/proc/self/cgroup", "r");
|
|
if (cg != nullptr)
|
|
{
|
|
char line[4096] = { 0 };
|
|
while (fgets(line, sizeof(line), cg))
|
|
{
|
|
StringVector bits = StringVector::tokenize(line, strlen(line), ':');
|
|
if (bits.size() > 2 && bits[1] == group)
|
|
{
|
|
groupPath = "/sys/fs/cgroup/" + group + bits[2];
|
|
break;
|
|
}
|
|
}
|
|
LOG_TRC("control group path for " << group << " is " << groupPath);
|
|
fclose(cg);
|
|
}
|
|
|
|
if (groupPath.empty())
|
|
return 0;
|
|
|
|
std::string path = groupPath + "/" + key;
|
|
LOG_TRC("Read from " << path);
|
|
FILE* file = fopen(path.c_str(), "r");
|
|
if (file != nullptr)
|
|
{
|
|
char line[4096] = { 0 };
|
|
if (fgets(line, sizeof(line), file))
|
|
num = atoll(line);
|
|
fclose(file);
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
std::size_t getCGroupMemLimit()
|
|
{
|
|
#ifdef __linux__
|
|
return getFromCGroup("memory", "memory.limit_in_bytes");
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
std::size_t getCGroupMemSoftLimit()
|
|
{
|
|
#ifdef __linux__
|
|
return getFromCGroup("memory", "memory.soft_limit_in_bytes");
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
std::pair<std::size_t, std::size_t> getPssAndDirtyFromSMaps(FILE* file)
|
|
{
|
|
std::size_t numPSSKb = 0;
|
|
std::size_t numDirtyKb = 0;
|
|
if (file)
|
|
{
|
|
rewind(file);
|
|
char line[4096] = { 0 };
|
|
while (fgets(line, sizeof(line), file))
|
|
{
|
|
if (line[0] != 'P')
|
|
continue;
|
|
|
|
const char* value;
|
|
|
|
// Shared_Dirty is accounted for by forkit's RSS
|
|
if ((value = startsWith(line, "Private_Dirty:", 14)))
|
|
{
|
|
numDirtyKb += atoi(value);
|
|
}
|
|
else if ((value = startsWith(line, "Pss:", 4)))
|
|
{
|
|
numPSSKb += atoi(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return std::make_pair(numPSSKb, numDirtyKb);
|
|
}
|
|
|
|
std::string getMemoryStats(FILE* file)
|
|
{
|
|
const std::pair<std::size_t, std::size_t> pssAndDirtyKb = getPssAndDirtyFromSMaps(file);
|
|
std::ostringstream oss;
|
|
oss << "procmemstats: pid=" << getpid() << " pss=" << pssAndDirtyKb.first
|
|
<< " dirty=" << pssAndDirtyKb.second;
|
|
LOG_TRC("Collected " << oss.str());
|
|
return oss.str();
|
|
}
|
|
|
|
std::size_t getMemoryUsagePSS(const pid_t pid)
|
|
{
|
|
if (pid > 0)
|
|
{
|
|
// beautifully aggregated data in a single entry:
|
|
const auto cmd_rollup = "/proc/" + std::to_string(pid) + "/smaps_rollup";
|
|
FILE* fp = fopen(cmd_rollup.c_str(), "r");
|
|
if (!fp)
|
|
{
|
|
const auto cmd = "/proc/" + std::to_string(pid) + "/smaps";
|
|
fp = fopen(cmd.c_str(), "r");
|
|
}
|
|
|
|
if (fp != nullptr)
|
|
{
|
|
const std::size_t pss = getPssAndDirtyFromSMaps(fp).first;
|
|
fclose(fp);
|
|
return pss;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
std::size_t getMemoryUsageRSS(const pid_t pid)
|
|
{
|
|
static const int pageSizeBytes = getpagesize();
|
|
std::size_t rss = 0;
|
|
|
|
if (pid > 0)
|
|
{
|
|
rss = getStatFromPid(pid, 23);
|
|
rss *= pageSizeBytes;
|
|
rss /= 1024;
|
|
return rss;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
size_t getCurrentThreadCount()
|
|
{
|
|
DIR* dir = opendir("/proc/self/task");
|
|
if (!dir)
|
|
{
|
|
LOG_TRC("Failed to open /proc/self/task");
|
|
return 0;
|
|
}
|
|
|
|
size_t threads = 0;
|
|
struct dirent* it;
|
|
while ((it = readdir(dir)) != nullptr)
|
|
{
|
|
if (it->d_name[0] == '.')
|
|
continue;
|
|
threads++;
|
|
}
|
|
closedir(dir);
|
|
LOG_TRC("We have " << threads << " threads");
|
|
return threads;
|
|
}
|
|
|
|
std::size_t getCpuUsage(const pid_t pid)
|
|
{
|
|
if (pid > 0)
|
|
{
|
|
std::size_t totalJiffies = 0;
|
|
totalJiffies += getStatFromPid(pid, 13);
|
|
totalJiffies += getStatFromPid(pid, 14);
|
|
return totalJiffies;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::size_t getStatFromPid(const pid_t pid, int ind)
|
|
{
|
|
if (pid > 0)
|
|
{
|
|
const auto cmd = "/proc/" + std::to_string(pid) + "/stat";
|
|
FILE* fp = fopen(cmd.c_str(), "r");
|
|
if (fp != nullptr)
|
|
{
|
|
char line[4096] = { 0 };
|
|
if (fgets(line, sizeof(line), fp))
|
|
{
|
|
const std::string s(line);
|
|
int index = 1;
|
|
std::size_t pos = s.find(' ');
|
|
while (pos != std::string::npos)
|
|
{
|
|
if (index == ind)
|
|
{
|
|
fclose(fp);
|
|
return strtol(&s[pos], nullptr, 10);
|
|
}
|
|
++index;
|
|
pos = s.find(' ', pos + 1);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void setProcessAndThreadPriorities(const pid_t pid, int prio)
|
|
{
|
|
int res = setpriority(PRIO_PROCESS, pid, prio);
|
|
LOG_TRC("Lowered kit [" << (int)pid << "] priority: " << prio << " with result: " << res);
|
|
|
|
#ifdef __linux__
|
|
// rely on Linux thread-id priority setting to drop this thread' priority
|
|
pid_t tid = getThreadId();
|
|
res = setpriority(PRIO_PROCESS, tid, prio);
|
|
LOG_TRC("Lowered own thread [" << (int)tid << "] priority: " << prio
|
|
<< " with result: " << res);
|
|
#endif
|
|
}
|
|
// If OS is not mobile, it must be Linux.
|
|
std::string getLinuxVersion()
|
|
{
|
|
// Read operating system info. We can read "os-release" file, located in /etc.
|
|
std::ifstream ifs("/etc/os-release");
|
|
std::string str(std::istreambuf_iterator<char>{ ifs }, {});
|
|
std::vector<std::string> infoList = Util::splitStringToVector(str, '\n');
|
|
std::map<std::string, std::string> releaseInfo = Util::stringVectorToMap(infoList, '=');
|
|
|
|
auto it = releaseInfo.find("PRETTY_NAME");
|
|
if (it != releaseInfo.end())
|
|
{
|
|
std::string name = it->second;
|
|
|
|
// See os-release(5). It says that the lines are "environment-like shell-compatible
|
|
// variable assignments". What that means, *exactly*, is up for debate, but probably
|
|
// of mainly academic interest. (It does say that variable expansion at least is not
|
|
// supported, that is a relief.)
|
|
|
|
// The value of PRETTY_NAME might be quoted with double-quotes or
|
|
// single-quotes.
|
|
|
|
// FIXME: In addition, it might contain backslash-escaped special
|
|
// characters, but we ignore that possibility for now.
|
|
|
|
// FIXME: In addition, if it really does support shell syntax (except variable
|
|
// expansion), it could for instance consist of multiple concatenated quoted strings (with no
|
|
// whitespace inbetween), as in:
|
|
// PRETTY_NAME="Foo "'bar'" mumble"
|
|
// But I guess that is a pretty remote possibility and surely no other code that
|
|
// reads /etc/os-release handles that like a proper shell, either.
|
|
|
|
if (name.length() >= 2 && ((name[0] == '"' && name[name.length() - 1] == '"') ||
|
|
(name[0] == '\'' && name[name.length() - 1] == '\'')))
|
|
name = name.substr(1, name.length() - 2);
|
|
return name;
|
|
}
|
|
else
|
|
{
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
#if defined(BUILDING_TESTS)
|
|
/// No-op implementation in the test programs
|
|
void alertAllUsers(const std::string&) {}
|
|
|
|
/// No-op implementation in the test programs
|
|
void alertAllUsers(const std::string&, const std::string&) {}
|
|
#endif
|
|
|
|
SysStopwatch::SysStopwatch()
|
|
{
|
|
restart();
|
|
}
|
|
|
|
void SysStopwatch::restart()
|
|
{
|
|
readTime(_startCPU, _startSys);
|
|
}
|
|
|
|
void SysStopwatch::readTime(uint64_t &cpu, uint64_t &sys)
|
|
{
|
|
cpu = 0;
|
|
sys = 0;
|
|
#if defined __linux__
|
|
struct rusage times;
|
|
if (!getrusage(RUSAGE_SELF, ×))
|
|
{
|
|
cpu = uint64_t(times.ru_utime.tv_sec) * 1000000 + times.ru_utime.tv_usec;
|
|
sys = uint64_t(times.ru_stime.tv_sec) * 1000000 + times.ru_stime.tv_usec;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
std::chrono::microseconds SysStopwatch::elapsedTime() const
|
|
{
|
|
uint64_t nowCPU, nowSys;
|
|
readTime(nowCPU, nowSys);
|
|
uint64_t totalUs = (nowCPU - _startCPU) + (nowSys - _startSys);
|
|
return std::chrono::microseconds(totalUs);
|
|
}
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|