mirror of
https://github.com/matrix-construct/construct
synced 2024-12-11 08:02:59 +01:00
273 lines
5.5 KiB
C++
273 lines
5.5 KiB
C++
|
// The Construct
|
||
|
//
|
||
|
// Copyright (C) The Construct Developers, Authors & Contributors
|
||
|
// Copyright (C) 2016-2020 Jason Volk <jason@zemos.net>
|
||
|
//
|
||
|
// 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<const trigger> &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
|
||
|
{
|
||
|
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<const string_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<microseconds>(val));
|
||
|
metric.stall.relative = total - metric.stall.total;
|
||
|
metric.stall.window = duration_cast<microseconds>(now<system_point>() - 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<seconds>(lstrip(key, "avg"));
|
||
|
metric.avg.at(i).pct = lex_cast<float>(val);
|
||
|
++i;
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
|
||
|
file.sampled = ircd::now<system_point>();
|
||
|
return true;
|
||
|
}
|
||
|
catch(const std::exception &e)
|
||
|
{
|
||
|
log::error
|
||
|
{
|
||
|
"Failed to refresh pressure stall information '%s' :%s",
|
||
|
file.name,
|
||
|
e.what(),
|
||
|
};
|
||
|
|
||
|
return false;
|
||
|
}
|