v0.26.6
This commit is contained in:
1 parent
f07429c267
commit
5d2aaeabe6
11 files changed
+302
-14
No files matched your search
@@ -297,7 +297,8 @@ function renderDiagnostics(status, appendSample = true) {
|
||||
const coldLimit = Number(c.cold_limit_bytes), coldPeak = Number.isFinite(coldLimit) && coldLimit > 0 ? Math.max(coldLimit, ...cacheColdStorageSamples) : null;
|
||||
$("cache-cold-sparkline").setAttribute("points", sparklinePoints(cacheColdStorageSamples, 300, 60, 5, coldPeak));
|
||||
$("cache-cold-chart-value").textContent = coldEnabled ? occupancyText(c.cold_bytes, c.cold_limit_bytes) : t("notEnabled");
|
||||
setOptionalMetric("process-platform", !!d.platform && d.platform !== "Unsupported", d.platform || "");
|
||||
const processPlatform = [d.platform, d.environment].filter(Boolean).join(" / ");
|
||||
setOptionalMetric("process-platform", !!d.platform && d.platform !== "Unsupported", processPlatform);
|
||||
setOptionalMetric("process-cpu", d.process_cpu_percent != null, `${fmt(d.process_cpu_percent, 1)}%`);
|
||||
const fds = knownPair(d.open_fds, d.max_fds, value => fmt(value), t("unlimited"));
|
||||
setOptionalMetric("process-fds", fds != null, fds);
|
||||
|
||||
+208
-2
@@ -1,6 +1,31 @@
|
||||
use super::View;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum LinuxEnvironment {
|
||||
Lxc,
|
||||
Docker,
|
||||
Podman,
|
||||
SystemdNspawn,
|
||||
Wsl,
|
||||
Vm,
|
||||
Host,
|
||||
}
|
||||
|
||||
impl LinuxEnvironment {
|
||||
fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Lxc => "LXC",
|
||||
Self::Docker => "Docker",
|
||||
Self::Podman => "Podman",
|
||||
Self::SystemdNspawn => "systemd-nspawn",
|
||||
Self::Wsl => "WSL",
|
||||
Self::Vm => "VM",
|
||||
Self::Host => "Host",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_value(text: &str, name: &str) -> Option<u64> {
|
||||
text.lines().find_map(|line| {
|
||||
let (key, rest) = line.split_once(':')?;
|
||||
@@ -10,7 +35,180 @@ fn key_value(text: &str, name: &str) -> Option<u64> {
|
||||
})
|
||||
}
|
||||
|
||||
fn read_text(path: &str) -> String {
|
||||
fs::read(path)
|
||||
.ok()
|
||||
.map(|bytes| String::from_utf8_lossy(&bytes).replace('\0', "\n"))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn lxcfs_proc_override(mountinfo: &str) -> bool {
|
||||
mountinfo.lines().any(|line| {
|
||||
line.contains("lxcfs")
|
||||
&& (line.contains(" /proc/uptime ") || line.contains(" /proc/loadavg "))
|
||||
})
|
||||
}
|
||||
|
||||
fn classify_environment(
|
||||
systemd_container: &str,
|
||||
pid1_environment: &str,
|
||||
cgroup: &str,
|
||||
mountinfo: &str,
|
||||
dmi: &str,
|
||||
hypervisor: &str,
|
||||
osrelease: &str,
|
||||
dockerenv: bool,
|
||||
containerenv: bool,
|
||||
) -> LinuxEnvironment {
|
||||
let systemd = systemd_container.trim().to_ascii_lowercase();
|
||||
let pid1 = pid1_environment.to_ascii_lowercase();
|
||||
let cgroup = cgroup.to_ascii_lowercase();
|
||||
let dmi = dmi.to_ascii_lowercase();
|
||||
let hypervisor = hypervisor.trim().to_ascii_lowercase();
|
||||
let osrelease = osrelease.to_ascii_lowercase();
|
||||
|
||||
if systemd.starts_with("lxc")
|
||||
|| pid1.lines().any(|line| line.trim() == "container=lxc")
|
||||
|| cgroup.contains("lxc.payload")
|
||||
|| cgroup.contains("/lxc/")
|
||||
|| cgroup.contains("machine-lxc\\x2d")
|
||||
|| lxcfs_proc_override(mountinfo)
|
||||
{
|
||||
return LinuxEnvironment::Lxc;
|
||||
}
|
||||
if dockerenv
|
||||
|| systemd == "docker"
|
||||
|| pid1.lines().any(|line| line.trim() == "container=docker")
|
||||
|| cgroup.contains("/docker/")
|
||||
|| cgroup.contains("docker-")
|
||||
{
|
||||
return LinuxEnvironment::Docker;
|
||||
}
|
||||
if containerenv
|
||||
|| systemd == "podman"
|
||||
|| pid1.lines().any(|line| line.trim() == "container=podman")
|
||||
|| cgroup.contains("libpod")
|
||||
{
|
||||
return LinuxEnvironment::Podman;
|
||||
}
|
||||
if systemd == "systemd-nspawn" || cgroup.contains("machine.slice/machine-") {
|
||||
return LinuxEnvironment::SystemdNspawn;
|
||||
}
|
||||
if osrelease.contains("microsoft") || osrelease.contains("wsl") {
|
||||
return LinuxEnvironment::Wsl;
|
||||
}
|
||||
if !hypervisor.is_empty()
|
||||
|| [
|
||||
"kvm",
|
||||
"qemu",
|
||||
"vmware",
|
||||
"virtualbox",
|
||||
"virtual machine",
|
||||
"xen",
|
||||
"bochs",
|
||||
"bhyve",
|
||||
"parallels",
|
||||
"amazon ec2",
|
||||
"google compute engine",
|
||||
"openstack",
|
||||
"rhev",
|
||||
"nutanix",
|
||||
]
|
||||
.iter()
|
||||
.any(|marker| dmi.contains(marker))
|
||||
{
|
||||
return LinuxEnvironment::Vm;
|
||||
}
|
||||
LinuxEnvironment::Host
|
||||
}
|
||||
|
||||
fn detect_environment() -> LinuxEnvironment {
|
||||
let systemd_container = read_text("/run/systemd/container");
|
||||
let pid1_environment = read_text("/proc/1/environ");
|
||||
let cgroup = format!(
|
||||
"{}\n{}",
|
||||
read_text("/proc/1/cgroup"),
|
||||
read_text("/proc/self/cgroup")
|
||||
);
|
||||
let mountinfo = read_text("/proc/self/mountinfo");
|
||||
let dmi = [
|
||||
"/sys/class/dmi/id/product_name",
|
||||
"/sys/class/dmi/id/sys_vendor",
|
||||
"/sys/class/dmi/id/board_vendor",
|
||||
"/sys/class/dmi/id/bios_vendor",
|
||||
]
|
||||
.into_iter()
|
||||
.map(read_text)
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let hypervisor = read_text("/sys/hypervisor/type");
|
||||
let osrelease = read_text("/proc/sys/kernel/osrelease");
|
||||
classify_environment(
|
||||
&systemd_container,
|
||||
&pid1_environment,
|
||||
&cgroup,
|
||||
&mountinfo,
|
||||
&dmi,
|
||||
&hypervisor,
|
||||
&osrelease,
|
||||
std::path::Path::new("/.dockerenv").exists(),
|
||||
std::path::Path::new("/run/.containerenv").exists(),
|
||||
)
|
||||
}
|
||||
|
||||
fn parse_load_average(text: &str) -> Option<[f64; 3]> {
|
||||
let mut values = text.split_whitespace().take(3).map(str::parse::<f64>);
|
||||
let load = [
|
||||
values.next()?.ok()?,
|
||||
values.next()?.ok()?,
|
||||
values.next()?.ok()?,
|
||||
];
|
||||
load.iter()
|
||||
.all(|value| value.is_finite() && *value >= 0.0)
|
||||
.then_some(load)
|
||||
}
|
||||
|
||||
fn process_start_ticks(stat: &str) -> Option<u64> {
|
||||
let end_of_name = stat.rfind(')')?;
|
||||
stat.get(end_of_name + 1..)?
|
||||
.split_whitespace()
|
||||
.nth(19)?
|
||||
.parse()
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn boottime_seconds() -> Option<f64> {
|
||||
let mut value = std::mem::MaybeUninit::<libc::timespec>::uninit();
|
||||
let rc = unsafe { libc::clock_gettime(libc::CLOCK_BOOTTIME, value.as_mut_ptr()) };
|
||||
if rc != 0 {
|
||||
return None;
|
||||
}
|
||||
let value = unsafe { value.assume_init() };
|
||||
Some(value.tv_sec as f64 + value.tv_nsec as f64 / 1_000_000_000.0)
|
||||
}
|
||||
|
||||
fn clock_ticks_per_second() -> Option<f64> {
|
||||
let ticks = unsafe { libc::sysconf(libc::_SC_CLK_TCK) };
|
||||
(ticks > 0).then_some(ticks as f64)
|
||||
}
|
||||
|
||||
fn lxc_uptime_seconds() -> Option<u64> {
|
||||
let stat = fs::read_to_string("/proc/1/stat").ok()?;
|
||||
let started = process_start_ticks(&stat)? as f64 / clock_ticks_per_second()?;
|
||||
let age = boottime_seconds()? - started;
|
||||
(age.is_finite() && age >= 0.0).then_some(age.floor() as u64)
|
||||
}
|
||||
|
||||
fn lxc_load_average() -> Option<[f64; 3]> {
|
||||
fs::read_to_string("/proc/loadavg")
|
||||
.ok()
|
||||
.and_then(|text| parse_load_average(&text))
|
||||
}
|
||||
|
||||
pub(crate) fn augment(view: &mut View) {
|
||||
let environment = detect_environment();
|
||||
view.environment = Some(environment.label());
|
||||
|
||||
if let Ok(stat) = fs::read_to_string("/proc/stat") {
|
||||
view.logical_cpus = Some(
|
||||
stat.lines()
|
||||
@@ -23,8 +221,16 @@ pub(crate) fn augment(view: &mut View) {
|
||||
.filter(|count| *count > 0);
|
||||
}
|
||||
// Linux-specific augmentation preserves fields and units that existed before
|
||||
// the cross-platform library adapter. Generic CPU/load/network/process
|
||||
// metrics are deliberately collected by the shared API in mod.rs.
|
||||
// the cross-platform library adapter. LXC additionally overrides uptime/load
|
||||
// with namespace-visible data so the dashboard does not report the host values.
|
||||
if environment == LinuxEnvironment::Lxc {
|
||||
if let Some(uptime) = lxc_uptime_seconds() {
|
||||
view.system_uptime_seconds = Some(uptime);
|
||||
}
|
||||
if let Some(load) = lxc_load_average() {
|
||||
view.load_average = Some(load);
|
||||
}
|
||||
}
|
||||
if let Ok(mem) = fs::read_to_string("/proc/meminfo") {
|
||||
view.memory_total_bytes = key_value(&mem, "MemTotal").map(|n| n.saturating_mul(1024));
|
||||
view.memory_used_bytes = view
|
||||
|
||||
@@ -12,4 +12,60 @@ mod tests {
|
||||
);
|
||||
assert_eq!(key_value("", "VmRSS"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_load_average_from_proc() {
|
||||
assert_eq!(
|
||||
parse_load_average("0.25 1.50 2.75 1/100 123\n"),
|
||||
Some([0.25, 1.5, 2.75])
|
||||
);
|
||||
assert_eq!(parse_load_average("bad data"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_pid_one_start_time_with_spaces_in_name() {
|
||||
let prefix = "1 (init process) S";
|
||||
let middle = (4..22).map(|value| value.to_string()).collect::<Vec<_>>();
|
||||
let stat = format!("{prefix} {} 4242 0 0", middle.join(" "));
|
||||
assert_eq!(process_start_ticks(&stat), Some(4242));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lxc_detection_has_priority_over_vm_metadata() {
|
||||
assert_eq!(
|
||||
classify_environment(
|
||||
"lxc",
|
||||
"container=lxc\n",
|
||||
"0::/",
|
||||
"",
|
||||
"QEMU",
|
||||
"",
|
||||
"6.8.0",
|
||||
false,
|
||||
false,
|
||||
),
|
||||
LinuxEnvironment::Lxc
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_vm_and_plain_host_without_changing_metrics_policy() {
|
||||
assert_eq!(
|
||||
classify_environment("", "", "", "", "QEMU Standard PC", "", "", false, false),
|
||||
LinuxEnvironment::Vm
|
||||
);
|
||||
assert_eq!(
|
||||
classify_environment("", "", "", "", "Dell PowerEdge", "", "", false, false),
|
||||
LinuxEnvironment::Host
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lxcfs_proc_mount_identifies_lxc_environment() {
|
||||
let mountinfo = "100 99 0:55 / /proc/loadavg rw - fuse.lxcfs lxcfs rw\n";
|
||||
assert_eq!(
|
||||
classify_environment("", "", "", mountinfo, "", "", "", false, false),
|
||||
LinuxEnvironment::Lxc
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ pub struct NetworkInterfaceView {
|
||||
pub struct View {
|
||||
pub source: &'static str,
|
||||
pub platform: &'static str,
|
||||
pub environment: Option<&'static str>,
|
||||
pub sampled_at_ms: u64,
|
||||
pub cpu_percent: Option<f64>,
|
||||
pub process_cpu_percent: Option<f64>,
|
||||
|
||||
Reference in new issue
Block a user