libreoffice-online/common/Util-desktop.cpp
Michael Meeks 884a841cde Logging: wrap poco and simplify logging.
Centralize more logging functionality in Log.cpp, simplify and
wrap underlying logging APIs better.

Code is much more generic, and hides implementation details
much more thoroughly, while keeping the same API / wrappers.

To do this we have to sub-class Poco::Logger to get access to
its generic 'log' method instead of a mess of in-line wrappers,
this lets us avoid lots of code.

Change-Id: I541d3aef49f99ce582655c5102a0041bf84cd56a
Signed-off-by: Michael Meeks <michael.meeks@collabora.com>
2024-04-24 10:59:51 +01:00

427 lines
11 KiB
C++

/*
* 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/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
}