// The Construct // // Copyright (C) The Construct Developers, Authors & Contributors // Copyright (C) 2016-2020 Jason Volk // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice is present in all copies. The // full license for this software is available in the LICENSE file. /// This unit is compiled for all targets, even though this is a linux-specific /// interface -- for now. An explicit support condition like this could be /// removed at some point. decltype(ircd::prof::psi::supported) ircd::prof::psi::supported { #if defined(__linux__) info::kernel_version[0] > 4 || (info::kernel_version[0] >= 4 && info::kernel_version[1] >= 20) #endif }; /// Position sensitive decltype(ircd::prof::psi::path) ircd::prof::psi::path { "/proc/pressure/cpu", "/proc/pressure/memory", "/proc/pressure/io", }; decltype(ircd::prof::psi::cpu) ircd::prof::psi::cpu { "cpu" }; decltype(ircd::prof::psi::mem) ircd::prof::psi::mem { "memory" }; decltype(ircd::prof::psi::io) ircd::prof::psi::io { "io" }; // // prof::psi::metric::refresh // ircd::prof::psi::file & ircd::prof::psi::wait(const vector_view &cmd) try { static const size_t max{3}; size_t trig_num {0}, trig_idx[max] { size_t(-1), size_t(-1), size_t(-1), }; // Associate all of the trigger inputs (cmd) with one of the files; the // cmds can be arranged any way and may not be for all files or any. for(size_t i(0); i < cmd.size(); ++i) { const auto it { std::find_if(begin(path), end(path), [&cmd, &i] (const auto &name) { return lstrip(name, "/proc/pressure/") == cmd[i].file.name; }) }; const auto pos { std::distance(begin(path), it) }; if(unlikely(size_t(pos) >= max)) throw error { "%s does not exist", cmd[i].file.name, }; trig_idx[pos] = i; trig_num++; } const fs::fd::opts opts { .mode = std::ios::in | std::ios::out }; // Open the fd's; if triggers were given we don't open files that were // not included in the cmd vector; otherwise we open all files. const fs::fd fd[max] { !trig_num || trig_idx[0] < max? fs::fd{path[0], opts}: fs::fd{}, !trig_num || trig_idx[1] < max? fs::fd{path[1], opts}: fs::fd{}, !trig_num || trig_idx[2] < max? fs::fd{path[2], opts}: fs::fd{}, }; // Write all triggers to their respective file for(size_t i(0); i < max; ++i) { if(trig_idx[i] >= max) continue; const auto &trig(cmd[trig_idx[i]]); try { // psi_write() in the kernel wants a write length of one greater // than the length of the string, but it places a \0 in its own // buffer unconditionally. This is noteworthy because our string // may not be null terminated and this length requirement smells. assert(trig.file.name == lstrip(path[i], "/proc/pressure/")); syscall(::write, fd[i], trig.string.c_str(), size(trig.string) + 1); } catch(const ctx::interrupted &) { throw; } catch(const std::exception &e) { log::error { "Failed to set pressure stall trigger [%s] on /proc/pressure/%s :%s", trig.string, trig.file.name, e.what(), }; throw; } } // Yield ircd::ctx until fd[n] has a result. const size_t n { fs::select(fd) }; switch(n) { case 0: return cpu; case 1: return mem; case 2: return io; default: always_assert(false); __builtin_unreachable(); } } catch(const ctx::interrupted &) { throw; } catch(const std::exception &e) { log::error { "Failed to poll pressure stall information :%s", e.what(), }; throw; } bool ircd::prof::psi::refresh(file &file) noexcept try { if(!supported) return false; if(unlikely(!file.name)) return false; const auto &path { fs::path(fs::path_scratch, vector_view { "/proc/pressure"_sv, file.name }) }; // Copy value into userspace char buf[256]; fs::read_opts opts; opts.aio = false; // can't read /proc through AIO opts.all = false; // don't need posix read-loop; make one read(2) only. const auto &result { fs::read(path, buf, opts) }; tokens(result, '\n', [&file] // Read each line (const string_view &line) { const auto &[type, vals] { split(line, ' ') }; // The first token tells us what the metric is; we have allocated // results for the following if(type != "full" && type != "some") return; auto &metric { type == "full"? file.full: file.some }; size_t i(0); tokens(vals, ' ', [&file, &metric, &i] // Read each key=value pair (const string_view &key_val) { const auto &[key, val] { split(key_val, '=') }; if(key == "total") { const auto total(lex_cast(val)); metric.stall.relative = total - metric.stall.total; metric.stall.window = duration_cast(now() - file.sampled); metric.stall.pct = metric.stall.window.count()? metric.stall.relative.count() / double(metric.stall.window.count()): 0.0; metric.stall.pct *= 100; metric.stall.total = total; return; } else if(startswith(key, "avg") && i < metric.avg.size()) { metric.avg.at(i).window = lex_cast(lstrip(key, "avg")); metric.avg.at(i).pct = lex_cast(val); ++i; } }); }); file.sampled = ircd::now(); return true; } catch(const std::exception &e) { log::error { "Failed to refresh pressure stall information '%s' :%s", file.name, e.what(), }; return false; }