1
0
mirror of https://github.com/kata-containers/kata-containers.git synced 2025-05-06 23:47:31 +00:00

Merge pull request from teawater/ma

Add mem-agent to kata
This commit is contained in:
Fupan Li 2024-12-24 14:11:36 +08:00 committed by GitHub
commit 2068801b80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 7326 additions and 8 deletions

26
src/agent/Cargo.lock generated
View File

@ -3026,6 +3026,7 @@ dependencies = [
"libc",
"log",
"logging",
"mem-agent",
"netlink-packet-utils",
"netlink-sys",
"nix 0.24.3",
@ -3483,6 +3484,21 @@ dependencies = [
"digest",
]
[[package]]
name = "mem-agent"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"lazy_static",
"nix 0.23.2",
"page_size",
"slog",
"slog-scope",
"tokio",
]
[[package]]
name = "memchr"
version = "2.7.4"
@ -4108,6 +4124,16 @@ dependencies = [
"sha2",
]
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parking"
version = "2.2.0"

View File

@ -7,6 +7,7 @@ license = "Apache-2.0"
[dependencies]
runtime-spec = { path = "../libs/runtime-spec" }
mem-agent = { path = "../mem-agent" }
oci-spec = { version = "0.6.8", features = ["runtime"] }
rustjail = { path = "rustjail" }
protocols = { path = "../libs/protocols", features = ["async", "with-serde"] }

View File

@ -45,6 +45,27 @@ const IMAGE_POLICY_FILE: &str = "agent.image_policy_file";
const HTTPS_PROXY: &str = "agent.https_proxy";
const NO_PROXY: &str = "agent.no_proxy";
const MEM_AGENT_ENABLE: &str = "agent.mem_agent_enable";
const MEM_AGENT_MEMCG_DISABLE: &str = "agent.mem_agent_memcg_disable";
const MEM_AGENT_MEMCG_SWAP: &str = "agent.mem_agent_memcg_swap";
const MEM_AGENT_MEMCG_SWAPPINESS_MAX: &str = "agent.mem_agent_memcg_swappiness_max";
const MEM_AGENT_MEMCG_PERIOD_SECS: &str = "agent.mem_agent_memcg_period_secs";
const MEM_AGENT_MEMCG_PERIOD_PSI_PERCENT_LIMIT: &str =
"agent.mem_agent_memcg_period_psi_percent_limit";
const MEM_AGENT_MEMCG_EVICTION_PSI_PERCENT_LIMIT: &str =
"agent.mem_agent_memcg_eviction_psi_percent_limit";
const MEM_AGENT_MEMCG_EVICTION_RUN_AGING_COUNT_MIN: &str =
"agent.mem_agent_memcg_eviction_run_aging_count_min";
const MEM_AGENT_COMPACT_DISABLE: &str = "agent.mem_agent_compact_disable";
const MEM_AGENT_COMPACT_PERIOD_SECS: &str = "agent.mem_agent_compact_period_secs";
const MEM_AGENT_COMPACT_PERIOD_PSI_PERCENT_LIMIT: &str =
"agent.mem_agent_compact_period_psi_percent_limit";
const MEM_AGENT_COMPACT_PSI_PERCENT_LIMIT: &str = "agent.mem_agent_compact_psi_percent_limit";
const MEM_AGENT_COMPACT_SEC_MAX: &str = "agent.mem_agent_compact_sec_max";
const MEM_AGENT_COMPACT_ORDER: &str = "agent.mem_agent_compact_order";
const MEM_AGENT_COMPACT_THRESHOLD: &str = "agent.mem_agent_compact_threshold";
const MEM_AGENT_COMPACT_FORCE_TIMES: &str = "agent.mem_agent_compact_force_times";
const DEFAULT_LOG_LEVEL: slog::Level = slog::Level::Info;
const DEFAULT_HOTPLUG_TIMEOUT: time::Duration = time::Duration::from_secs(3);
const DEFAULT_CDH_API_TIMEOUT: time::Duration = time::Duration::from_secs(50);
@ -131,6 +152,13 @@ pub struct AgentConfig {
pub image_policy_file: String,
#[cfg(feature = "agent-policy")]
pub policy_file: String,
pub mem_agent: Option<MemAgentConfig>,
}
#[derive(Debug, Default, PartialEq)]
pub struct MemAgentConfig {
pub memcg_config: mem_agent::memcg::Config,
pub compact_config: mem_agent::compact::Config,
}
#[derive(Debug, Deserialize)]
@ -160,6 +188,22 @@ pub struct AgentConfigBuilder {
pub image_policy_file: Option<String>,
#[cfg(feature = "agent-policy")]
pub policy_file: Option<String>,
pub mem_agent_enable: Option<bool>,
pub mem_agent_memcg_disable: Option<bool>,
pub mem_agent_memcg_swap: Option<bool>,
pub mem_agent_memcg_swappiness_max: Option<u8>,
pub mem_agent_memcg_period_secs: Option<u64>,
pub mem_agent_memcg_period_psi_percent_limit: Option<u8>,
pub mem_agent_memcg_eviction_psi_percent_limit: Option<u8>,
pub mem_agent_memcg_eviction_run_aging_count_min: Option<u64>,
pub mem_agent_compact_disable: Option<bool>,
pub mem_agent_compact_period_secs: Option<u64>,
pub mem_agent_compact_period_psi_percent_limit: Option<u8>,
pub mem_agent_compact_psi_percent_limit: Option<u8>,
pub mem_agent_compact_sec_max: Option<i64>,
pub mem_agent_compact_order: Option<u8>,
pub mem_agent_compact_threshold: Option<u64>,
pub mem_agent_compact_force_times: Option<u64>,
}
macro_rules! config_override {
@ -176,6 +220,14 @@ macro_rules! config_override {
};
}
macro_rules! mem_agent_config_override {
($builder_v:expr, $mac_v:expr) => {
if let Some(v) = $builder_v {
$mac_v = v;
}
};
}
// parse_cmdline_param parse commandline parameters.
macro_rules! parse_cmdline_param {
// commandline flags, without func to parse the option values
@ -235,6 +287,7 @@ impl Default for AgentConfig {
image_policy_file: String::from(""),
#[cfg(feature = "agent-policy")]
policy_file: String::from(""),
mem_agent: None,
}
}
}
@ -287,6 +340,75 @@ impl FromStr for AgentConfig {
#[cfg(feature = "agent-policy")]
config_override!(agent_config_builder, agent_config, policy_file);
if agent_config_builder.mem_agent_enable.unwrap_or(false) {
let mut mac = MemAgentConfig::default();
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_disable,
mac.memcg_config.disabled
);
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_swap,
mac.memcg_config.swap
);
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_swappiness_max,
mac.memcg_config.swappiness_max
);
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_period_secs,
mac.memcg_config.period_secs
);
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_period_psi_percent_limit,
mac.memcg_config.period_psi_percent_limit
);
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_eviction_psi_percent_limit,
mac.memcg_config.eviction_psi_percent_limit
);
mem_agent_config_override!(
agent_config_builder.mem_agent_memcg_eviction_run_aging_count_min,
mac.memcg_config.eviction_run_aging_count_min
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_disable,
mac.compact_config.disabled
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_period_secs,
mac.compact_config.period_secs
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_period_psi_percent_limit,
mac.compact_config.period_psi_percent_limit
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_psi_percent_limit,
mac.compact_config.compact_psi_percent_limit
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_sec_max,
mac.compact_config.compact_sec_max
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_order,
mac.compact_config.compact_order
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_threshold,
mac.compact_config.compact_threshold
);
mem_agent_config_override!(
agent_config_builder.mem_agent_compact_force_times,
mac.compact_config.compact_force_times
);
agent_config.mem_agent = Some(mac);
}
Ok(agent_config)
}
}
@ -311,6 +433,8 @@ impl AgentConfig {
let mut config: AgentConfig = Default::default();
let cmdline = fs::read_to_string(file)?;
let params: Vec<&str> = cmdline.split_ascii_whitespace().collect();
let mut mem_agent_enable = false;
let mut mac = MemAgentConfig::default();
for param in params.iter() {
// If we get a configuration file path from the command line, we
// generate our config from it.
@ -367,21 +491,21 @@ impl AgentConfig {
param,
DEBUG_CONSOLE_VPORT_OPTION,
config.debug_console_vport,
get_vsock_port,
get_number_value,
|port| port > 0
);
parse_cmdline_param!(
param,
LOG_VPORT_OPTION,
config.log_vport,
get_vsock_port,
get_number_value,
|port| port > 0
);
parse_cmdline_param!(
param,
PASSFD_LISTENER_PORT,
config.passfd_listener_port,
get_vsock_port,
get_number_value,
|port| port > 0
);
parse_cmdline_param!(
@ -437,6 +561,105 @@ impl AgentConfig {
config.secure_storage_integrity,
get_bool_value
);
parse_cmdline_param!(param, MEM_AGENT_ENABLE, mem_agent_enable, get_bool_value);
if mem_agent_enable {
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_DISABLE,
mac.memcg_config.disabled,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_SWAP,
mac.memcg_config.swap,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_SWAPPINESS_MAX,
mac.memcg_config.swappiness_max,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_PERIOD_SECS,
mac.memcg_config.period_secs,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_PERIOD_PSI_PERCENT_LIMIT,
mac.memcg_config.period_psi_percent_limit,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_EVICTION_PSI_PERCENT_LIMIT,
mac.memcg_config.eviction_psi_percent_limit,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_MEMCG_EVICTION_RUN_AGING_COUNT_MIN,
mac.memcg_config.eviction_run_aging_count_min,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_DISABLE,
mac.compact_config.disabled,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_PERIOD_SECS,
mac.compact_config.period_secs,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_PERIOD_PSI_PERCENT_LIMIT,
mac.compact_config.period_psi_percent_limit,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_PSI_PERCENT_LIMIT,
mac.compact_config.compact_psi_percent_limit,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_SEC_MAX,
mac.compact_config.compact_sec_max,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_ORDER,
mac.compact_config.compact_order,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_THRESHOLD,
mac.compact_config.compact_threshold,
get_number_value
);
parse_cmdline_param!(
param,
MEM_AGENT_COMPACT_FORCE_TIMES,
mac.compact_config.compact_force_times,
get_number_value
);
}
}
if mem_agent_enable {
config.mem_agent = Some(mac);
}
config.override_config_from_envs();
@ -477,11 +700,19 @@ impl AgentConfig {
}
#[instrument]
fn get_vsock_port(p: &str) -> Result<i32> {
fn get_number_value<T>(p: &str) -> Result<T>
where
T: std::str::FromStr,
<T as std::str::FromStr>::Err: std::fmt::Debug,
{
let fields: Vec<&str> = p.split('=').collect();
ensure!(fields.len() == 2, "invalid port parameter");
if fields.len() != 2 {
return Err(anyhow!("format of {} is invalid", p));
}
Ok(fields[1].parse::<i32>()?)
fields[1]
.parse::<T>()
.map_err(|e| anyhow!("parse from {} failed: {:?}", &fields[1], e))
}
// Map logrus (https://godoc.org/github.com/sirupsen/logrus)
@ -682,6 +913,7 @@ mod tests {
image_policy_file: &'a str,
#[cfg(feature = "agent-policy")]
policy_file: &'a str,
mem_agent: Option<MemAgentConfig>,
}
impl Default for TestData<'_> {
@ -710,6 +942,7 @@ mod tests {
image_policy_file: "",
#[cfg(feature = "agent-policy")]
policy_file: "",
mem_agent: None,
}
}
}
@ -1204,6 +1437,40 @@ mod tests {
policy_file: "/tmp/policy.rego",
..Default::default()
},
TestData {
contents: "",
..Default::default()
},
TestData {
contents: "agent.mem_agent_enable=1",
mem_agent: Some(MemAgentConfig::default()),
..Default::default()
},
TestData {
contents: "agent.mem_agent_enable=1\nagent.mem_agent_memcg_period_secs=300",
mem_agent: Some(MemAgentConfig {
memcg_config: mem_agent::memcg::Config {
period_secs: 300,
..Default::default()
},
..Default::default()
}),
..Default::default()
},
TestData {
contents: "agent.mem_agent_enable=1\nagent.mem_agent_memcg_period_secs=300\nagent.mem_agent_compact_order=6",
mem_agent: Some(MemAgentConfig {
memcg_config: mem_agent::memcg::Config {
period_secs: 300,
..Default::default()
},
compact_config: mem_agent::compact::Config {
compact_order: 6,
..Default::default()
},
}),
..Default::default()
},
];
let dir = tempdir().expect("failed to create tmpdir");
@ -1281,6 +1548,8 @@ mod tests {
#[cfg(feature = "agent-policy")]
assert_eq!(d.policy_file, config.policy_file, "{}", msg);
assert_eq!(d.mem_agent, config.mem_agent, "{}", msg);
for v in vars_to_unset {
env::remove_var(v);
}
@ -1525,6 +1794,7 @@ Caused by:
server_addr = 'vsock://8:2048'
guest_components_procs = "api-server-rest"
guest_components_rest_api = "all"
mem_agent_enable = true
"#,
)
.unwrap();
@ -1543,5 +1813,7 @@ Caused by:
// Verify that the default values are valid
assert_eq!(config.hotplug_timeout, DEFAULT_HOTPLUG_TIMEOUT);
assert_eq!(config.mem_agent, Some(MemAgentConfig::default()),);
}
}

View File

@ -428,8 +428,23 @@ async fn start_sandbox(
init_attestation_components(logger, config).await?;
}
let mut oma = None;
let mut _ort = None;
if let Some(c) = &config.mem_agent {
let (ma, rt) =
mem_agent::agent::MemAgent::new(c.memcg_config.clone(), c.compact_config.clone())
.map_err(|e| {
error!(logger, "MemAgent::new fail: {}", e);
e
})
.context("start mem-agent")?;
oma = Some(ma);
_ort = Some(rt);
}
// vsock:///dev/vsock, port
let mut server = rpc::start(sandbox.clone(), config.server_addr.as_str(), init_mode).await?;
let mut server =
rpc::start(sandbox.clone(), config.server_addr.as_str(), init_mode, oma).await?;
server.start().await?;

View File

@ -181,6 +181,7 @@ impl<T> OptionToTtrpcResult<T> for Option<T> {
pub struct AgentService {
sandbox: Arc<Mutex<Sandbox>>,
init_mode: bool,
oma: Option<mem_agent::agent::MemAgent>,
}
impl AgentService {
@ -698,6 +699,37 @@ impl AgentService {
}
}
fn mem_agent_memcgconfig_to_memcg_optionconfig(
mc: &protocols::agent::MemAgentMemcgConfig,
) -> mem_agent::memcg::OptionConfig {
mem_agent::memcg::OptionConfig {
disabled: mc.disabled,
swap: mc.swap,
swappiness_max: mc.swappiness_max.map(|x| x as u8),
period_secs: mc.period_secs,
period_psi_percent_limit: mc.period_psi_percent_limit.map(|x| x as u8),
eviction_psi_percent_limit: mc.eviction_psi_percent_limit.map(|x| x as u8),
eviction_run_aging_count_min: mc.eviction_run_aging_count_min,
..Default::default()
}
}
fn mem_agent_compactconfig_to_compact_optionconfig(
cc: &protocols::agent::MemAgentCompactConfig,
) -> mem_agent::compact::OptionConfig {
mem_agent::compact::OptionConfig {
disabled: cc.disabled,
period_secs: cc.period_secs,
period_psi_percent_limit: cc.period_psi_percent_limit.map(|x| x as u8),
compact_psi_percent_limit: cc.compact_psi_percent_limit.map(|x| x as u8),
compact_sec_max: cc.compact_sec_max,
compact_order: cc.compact_order.map(|x| x as u8),
compact_threshold: cc.compact_threshold,
compact_force_times: cc.compact_force_times,
..Default::default()
}
}
#[async_trait]
impl agent_ttrpc::AgentService for AgentService {
async fn create_container(
@ -1513,6 +1545,54 @@ impl agent_ttrpc::AgentService for AgentService {
Ok(Empty::new())
}
async fn mem_agent_memcg_set(
&self,
_ctx: &::ttrpc::r#async::TtrpcContext,
config: protocols::agent::MemAgentMemcgConfig,
) -> ::ttrpc::Result<Empty> {
if let Some(ma) = &self.oma {
ma.memcg_set_config_async(mem_agent_memcgconfig_to_memcg_optionconfig(&config))
.await
.map_err(|e| {
let estr = format!("ma.memcg_set_config_async fail: {}", e);
error!(sl(), "{}", estr);
ttrpc::Error::RpcStatus(ttrpc::get_status(ttrpc::Code::INTERNAL, estr))
})?;
} else {
let estr = "mem-agent is disabled";
error!(sl(), "{}", estr);
return Err(ttrpc::Error::RpcStatus(ttrpc::get_status(
ttrpc::Code::INTERNAL,
estr,
)));
}
Ok(Empty::new())
}
async fn mem_agent_compact_set(
&self,
_ctx: &::ttrpc::r#async::TtrpcContext,
config: protocols::agent::MemAgentCompactConfig,
) -> ::ttrpc::Result<Empty> {
if let Some(ma) = &self.oma {
ma.compact_set_config_async(mem_agent_compactconfig_to_compact_optionconfig(&config))
.await
.map_err(|e| {
let estr = format!("ma.compact_set_config_async fail: {}", e);
error!(sl(), "{}", estr);
ttrpc::Error::RpcStatus(ttrpc::get_status(ttrpc::Code::INTERNAL, estr))
})?;
} else {
let estr = "mem-agent is disabled";
error!(sl(), "{}", estr);
return Err(ttrpc::Error::RpcStatus(ttrpc::get_status(
ttrpc::Code::INTERNAL,
estr,
)));
}
Ok(Empty::new())
}
}
#[derive(Clone)]
@ -1656,10 +1736,12 @@ pub async fn start(
s: Arc<Mutex<Sandbox>>,
server_address: &str,
init_mode: bool,
oma: Option<mem_agent::agent::MemAgent>,
) -> Result<TtrpcServer> {
let agent_service = Box::new(AgentService {
sandbox: s,
init_mode,
oma,
}) as Box<dyn agent_ttrpc::AgentService + Send + Sync>;
let aservice = agent_ttrpc::create_agent_service(Arc::new(agent_service));
@ -2294,6 +2376,7 @@ mod tests {
let agent_service = Box::new(AgentService {
sandbox: Arc::new(Mutex::new(sandbox)),
init_mode: true,
oma: None,
});
let req = protocols::agent::UpdateInterfaceRequest::default();
@ -2311,6 +2394,7 @@ mod tests {
let agent_service = Box::new(AgentService {
sandbox: Arc::new(Mutex::new(sandbox)),
init_mode: true,
oma: None,
});
let req = protocols::agent::UpdateRoutesRequest::default();
@ -2328,6 +2412,7 @@ mod tests {
let agent_service = Box::new(AgentService {
sandbox: Arc::new(Mutex::new(sandbox)),
init_mode: true,
oma: None,
});
let req = protocols::agent::AddARPNeighborsRequest::default();
@ -2466,6 +2551,7 @@ mod tests {
let agent_service = Box::new(AgentService {
sandbox: Arc::new(Mutex::new(sandbox)),
init_mode: true,
oma: None,
});
let result = agent_service
@ -2956,6 +3042,7 @@ OtherField:other
let agent_service = Box::new(AgentService {
sandbox: Arc::new(Mutex::new(sandbox)),
init_mode: true,
oma: None,
});
let ctx = mk_ttrpc_context();

View File

@ -18,6 +18,44 @@ use crate::eother;
/// agent name of Kata agent.
pub const AGENT_NAME_KATA: &str = "kata";
#[derive(Default, Debug, Deserialize, Serialize, Clone)]
pub struct MemAgent {
#[serde(default, alias = "mem_agent_enable")]
pub enable: bool,
#[serde(default)]
pub memcg_disable: Option<bool>,
#[serde(default)]
pub memcg_swap: Option<bool>,
#[serde(default)]
pub memcg_swappiness_max: Option<u8>,
#[serde(default)]
pub memcg_period_secs: Option<u64>,
#[serde(default)]
pub memcg_period_psi_percent_limit: Option<u8>,
#[serde(default)]
pub memcg_eviction_psi_percent_limit: Option<u8>,
#[serde(default)]
pub memcg_eviction_run_aging_count_min: Option<u64>,
#[serde(default)]
pub compact_disable: Option<bool>,
#[serde(default)]
pub compact_period_secs: Option<u64>,
#[serde(default)]
pub compact_period_psi_percent_limit: Option<u8>,
#[serde(default)]
pub compact_psi_percent_limit: Option<u8>,
#[serde(default)]
pub compact_sec_max: Option<i64>,
#[serde(default)]
pub compact_order: Option<u8>,
#[serde(default)]
pub compact_threshold: Option<u64>,
#[serde(default)]
pub compact_force_times: Option<u64>,
}
/// Kata agent configuration information.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Agent {
@ -98,6 +136,10 @@ pub struct Agent {
/// container pipe size
#[serde(default)]
pub container_pipe_size: u32,
/// Memory agent configuration
#[serde(default)]
pub mem_agent: MemAgent,
}
impl std::default::Default for Agent {
@ -116,6 +158,7 @@ impl std::default::Default for Agent {
health_check_request_timeout_ms: 90_000,
kernel_modules: Default::default(),
container_pipe_size: 0,
mem_agent: MemAgent::default(),
}
}
}

View File

@ -115,6 +115,14 @@ pub struct TomlConfig {
pub runtime: Runtime,
}
macro_rules! mem_agent_kv_insert {
($ma_cfg:expr, $key:expr, $map:expr) => {
if let Some(n) = $ma_cfg {
$map.insert($key.to_string(), n.to_string());
}
};
}
impl TomlConfig {
/// Load Kata configuration information from configuration files.
///
@ -204,6 +212,83 @@ impl TomlConfig {
DEFAULT_AGENT_DBG_CONSOLE_PORT.to_string(),
);
}
if cfg.mem_agent.enable {
kv.insert("psi".to_string(), "1".to_string());
kv.insert("agent.mem_agent_enable".to_string(), "1".to_string());
mem_agent_kv_insert!(
cfg.mem_agent.memcg_disable,
"agent.mem_agent_memcg_disable",
kv
);
mem_agent_kv_insert!(cfg.mem_agent.memcg_swap, "agent.mem_agent_memcg_swap", kv);
mem_agent_kv_insert!(
cfg.mem_agent.memcg_swappiness_max,
"agent.mem_agent_memcg_swappiness_max",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.memcg_period_secs,
"agent.mem_agent_memcg_period_secs",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.memcg_period_psi_percent_limit,
"agent.mem_agent_memcg_period_psi_percent_limit",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.memcg_eviction_psi_percent_limit,
"agent.mem_agent_memcg_eviction_psi_percent_limit",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.memcg_eviction_run_aging_count_min,
"agent.mem_agent_memcg_eviction_run_aging_count_min",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_disable,
"agent.mem_agent_compact_disable",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_period_secs,
"agent.mem_agent_compact_period_secs",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_period_psi_percent_limit,
"agent.mem_agent_compact_period_psi_percent_limit",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_psi_percent_limit,
"agent.mem_agent_compact_psi_percent_limit",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_sec_max,
"agent.mem_agent_compact_sec_max",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_order,
"agent.mem_agent_compact_order",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_threshold,
"agent.mem_agent_compact_threshold",
kv
);
mem_agent_kv_insert!(
cfg.mem_agent.compact_force_times,
"agent.mem_agent_compact_force_times",
kv
);
}
}
Ok(kv)
}

View File

@ -59,6 +59,10 @@ service AgentService {
// observability
rpc GetMetrics(GetMetricsRequest) returns (Metrics);
// mem-agent
rpc MemAgentMemcgSet(MemAgentMemcgConfig) returns (google.protobuf.Empty);
rpc MemAgentCompactSet(MemAgentCompactConfig) returns (google.protobuf.Empty);
// misc (TODO: some rpcs can be replaced by hyperstart-exec)
rpc CreateSandbox(CreateSandboxRequest) returns (google.protobuf.Empty);
rpc DestroySandbox(DestroySandboxRequest) returns (google.protobuf.Empty);
@ -611,3 +615,24 @@ message ResizeVolumeRequest {
message SetPolicyRequest {
string policy = 1;
}
message MemAgentMemcgConfig {
optional bool disabled = 1;
optional bool swap = 2;
optional uint32 swappiness_max = 3;
optional uint64 period_secs = 4;
optional uint32 period_psi_percent_limit = 5;
optional uint32 eviction_psi_percent_limit = 6;
optional uint64 eviction_run_aging_count_min = 7;
}
message MemAgentCompactConfig {
optional bool disabled = 1;
optional uint64 period_secs = 2;
optional uint32 period_psi_percent_limit = 3;
optional uint32 compact_psi_percent_limit = 4;
optional int64 compact_sec_max = 5;
optional uint32 compact_order = 6;
optional uint64 compact_threshold = 7;
optional uint64 compact_force_times = 8;
}

5
src/mem-agent/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/target
/example/target
/.vscode
.vscode-ctags

943
src/mem-agent/Cargo.lock generated Normal file
View File

@ -0,0 +1,943 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
[[package]]
name = "arc-swap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f"
[[package]]
name = "async-trait"
version = "0.1.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bumpalo"
version = "3.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
[[package]]
name = "bytes"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
[[package]]
name = "cc"
version = "1.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.4",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "crossbeam-channel"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "deranged"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
dependencies = [
"powerfmt",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "is-terminal"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
dependencies = [
"hermit-abi 0.4.0",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.167"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.6.0",
"libc",
]
[[package]]
name = "lock_api"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "mem-agent"
version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"lazy_static",
"maplit",
"nix",
"once_cell",
"page_size",
"slog",
"slog-async",
"slog-scope",
"slog-term",
"tokio",
]
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "nix"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags 1.3.2",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "num-conv"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-traits"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.3.9",
"libc",
]
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.48.5",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustversion"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slog"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06"
[[package]]
name = "slog-async"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72c8038f898a2c79507940990f05386455b3a317d8f18d4caea7cbc3d5096b84"
dependencies = [
"crossbeam-channel",
"slog",
"take_mut",
"thread_local",
]
[[package]]
name = "slog-scope"
version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786"
dependencies = [
"arc-swap",
"lazy_static",
"slog",
]
[[package]]
name = "slog-term"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8"
dependencies = [
"is-terminal",
"slog",
"term",
"thread_local",
"time",
]
[[package]]
name = "smallvec"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "syn"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "take_mut"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "thiserror"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "time"
version = "0.3.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tokio"
version = "1.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.4",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
name = "windows-targets"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b"
dependencies = [
"windows_aarch64_gnullvm 0.52.4",
"windows_aarch64_msvc 0.52.4",
"windows_i686_gnu 0.52.4",
"windows_i686_msvc 0.52.4",
"windows_x86_64_gnu 0.52.4",
"windows_x86_64_gnullvm 0.52.4",
"windows_x86_64_msvc 0.52.4",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8"

22
src/mem-agent/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "mem-agent"
version = "0.1.0"
edition = "2018"
[dependencies]
slog = "2.5.2"
slog-scope = "4.1.2"
anyhow = "1.0"
page_size = "0.6"
chrono = "0.4"
tokio = { version = "1.33", features = ["full"] }
async-trait = "0.1"
lazy_static = "1.4"
nix = "0.23.2"
[dev-dependencies]
maplit = "1.0"
slog-term = "2.9.0"
slog-async = "2.7"
once_cell = "1.9.0"

6
src/mem-agent/Makefile Normal file
View File

@ -0,0 +1,6 @@
# Copyright (C) 2024 Ant group. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0
default:
cd example; cargo build --examples --target x86_64-unknown-linux-musl

1658
src/mem-agent/example/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
[package]
name = "mem-agent-bin"
version = "0.1.0"
edition = "2018"
[dependencies]
slog = "2.5.2"
slog-scope = "4.1.2"
slog-term = "2.9.0"
slog-async = "2.7"
structopt = "0.3"
anyhow = "1.0"
libc = "0.2"
page_size = "0.6"
chrono = "0.4"
maplit = "1.0"
ttrpc = { version = "0.8", features = ["async"] }
tokio = { version = "1.33", features = ["full"] }
async-trait = "0.1"
byteorder = "1.5"
protobuf = "3.1"
lazy_static = "1.4"
# Rust 1.68 doesn't support 0.5.9
home = "=0.5.5"
mem-agent = { path = "../" }
[[example]]
name = "mem-agent-srv"
path = "./srv.rs"
[[example]]
name = "mem-agent-ctl"
path = "./ctl.rs"
[build-dependencies]
ttrpc-codegen = "0.4"

View File

@ -0,0 +1,29 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use ttrpc_codegen::{Codegen, Customize, ProtobufCustomize};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let protos = vec![
"protocols/protos/mem-agent.proto",
"protocols/protos/google/protobuf/empty.proto",
"protocols/protos/google/protobuf/timestamp.proto",
];
let protobuf_customized = ProtobufCustomize::default().gen_mod_rs(false);
Codegen::new()
.out_dir("protocols/")
.inputs(&protos)
.include("protocols/protos/")
.rust_protobuf()
.customize(Customize {
async_all: true,
..Default::default()
})
.rust_protobuf_customize(protobuf_customized.clone())
.run()?;
Ok(())
}

View File

@ -0,0 +1,79 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
mod protocols;
mod share;
use anyhow::{anyhow, Result};
use protocols::empty;
use protocols::mem_agent_ttrpc;
use share::option::{CompactSetOption, MemcgSetOption};
use structopt::StructOpt;
use ttrpc::r#async::Client;
#[derive(Debug, StructOpt)]
enum Command {
#[structopt(name = "memcgstatus", about = "get memory cgroup status")]
MemcgStatus,
#[structopt(name = "memcgset", about = "set memory cgroup")]
MemcgSet(MemcgSetOption),
#[structopt(name = "compactset", about = "set compact")]
CompactSet(CompactSetOption),
}
#[derive(StructOpt, Debug)]
#[structopt(name = "mem-agent-ctl", about = "Memory agent controler")]
struct Opt {
#[structopt(long, default_value = "unix:///var/run/mem-agent.sock")]
addr: String,
#[structopt(subcommand)]
command: Command,
}
#[tokio::main]
async fn main() -> Result<()> {
let opt = Opt::from_args();
// setup client
let c = Client::connect(&opt.addr).unwrap();
let client = mem_agent_ttrpc::ControlClient::new(c.clone());
match opt.command {
Command::MemcgStatus => {
let mss = client
.memcg_status(ttrpc::context::with_timeout(0), &empty::Empty::new())
.await
.map_err(|e| anyhow!("client.memcg_status fail: {}", e))?;
for mcg in mss.mem_cgroups {
println!("{:?}", mcg);
for (numa_id, n) in mcg.numa {
if let Some(t) = n.last_inc_time.into_option() {
println!("{} {:?}", numa_id, share::misc::timestamp_to_datetime(t)?);
}
}
}
}
Command::MemcgSet(c) => {
let config = c.to_rpc_memcg_config();
client
.memcg_set(ttrpc::context::with_timeout(0), &config)
.await
.map_err(|e| anyhow!("client.memcg_status fail: {}", e))?;
}
Command::CompactSet(c) => {
let config = c.to_rpc_compact_config();
client
.compact_set(ttrpc::context::with_timeout(0), &config)
.await
.map_err(|e| anyhow!("client.memcg_status fail: {}", e))?;
}
}
Ok(())
}

View File

@ -0,0 +1,8 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
pub mod empty;
pub mod mem_agent;
pub mod mem_agent_ttrpc;
pub mod timestamp;

View File

@ -0,0 +1,52 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option go_package = "types";
option java_package = "com.google.protobuf";
option java_outer_classname = "EmptyProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
option cc_enable_arenas = true;
// A generic empty message that you can re-use to avoid defining duplicated
// empty messages in your APIs. A typical example is to use it as the request
// or the response type of an API method. For instance:
//
// service Foo {
// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
// }
//
// The JSON representation for `Empty` is empty JSON object `{}`.
message Empty {}

View File

@ -0,0 +1,138 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package google.protobuf;
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
option cc_enable_arenas = true;
option go_package = "github.com/golang/protobuf/ptypes/timestamp";
option java_package = "com.google.protobuf";
option java_outer_classname = "TimestampProto";
option java_multiple_files = true;
option objc_class_prefix = "GPB";
// A Timestamp represents a point in time independent of any time zone or local
// calendar, encoded as a count of seconds and fractions of seconds at
// nanosecond resolution. The count is relative to an epoch at UTC midnight on
// January 1, 1970, in the proleptic Gregorian calendar which extends the
// Gregorian calendar backwards to year one.
//
// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
// second table is needed for interpretation, using a [24-hour linear
// smear](https://developers.google.com/time/smear).
//
// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
// restricting to that range, we ensure that we can convert to and from [RFC
// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
//
// # Examples
//
// Example 1: Compute Timestamp from POSIX `time()`.
//
// Timestamp timestamp;
// timestamp.set_seconds(time(NULL));
// timestamp.set_nanos(0);
//
// Example 2: Compute Timestamp from POSIX `gettimeofday()`.
//
// struct timeval tv;
// gettimeofday(&tv, NULL);
//
// Timestamp timestamp;
// timestamp.set_seconds(tv.tv_sec);
// timestamp.set_nanos(tv.tv_usec * 1000);
//
// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
//
// FILETIME ft;
// GetSystemTimeAsFileTime(&ft);
// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
//
// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
// Timestamp timestamp;
// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
//
// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
//
// long millis = System.currentTimeMillis();
//
// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
// .setNanos((int) ((millis % 1000) * 1000000)).build();
//
//
// Example 5: Compute Timestamp from current time in Python.
//
// timestamp = Timestamp()
// timestamp.GetCurrentTime()
//
// # JSON Mapping
//
// In JSON format, the Timestamp type is encoded as a string in the
// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
// where {year} is always expressed using four digits while {month}, {day},
// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
// is required. A proto3 JSON serializer should always use UTC (as indicated by
// "Z") when printing the Timestamp type and a proto3 JSON parser should be
// able to accept both UTC and other timezones (as indicated by an offset).
//
// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
// 01:30 UTC on January 15, 2017.
//
// In JavaScript, one can convert a Date object to this format using the
// standard
// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
// method. In Python, a standard `datetime.datetime` object can be converted
// to this format using
// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
// the Joda Time's [`ISODateTimeFormat.dateTime()`](
// http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D
// ) to obtain a formatter capable of generating timestamps in this format.
//
//
message Timestamp {
// Represents seconds of UTC time since Unix epoch
// 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
// 9999-12-31T23:59:59Z inclusive.
int64 seconds = 1;
// Non-negative fractions of a second at nanosecond resolution. Negative
// second values with fractions must still have non-negative nanos values
// that count forward in time. Must be from 0 to 999,999,999
// inclusive.
int32 nanos = 2;
}

View File

@ -0,0 +1,66 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
syntax = "proto3";
package MemAgent;
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
service Control {
rpc MemcgStatus(google.protobuf.Empty) returns (MemcgStatusReply);
rpc MemcgSet(MemcgConfig) returns (google.protobuf.Empty);
rpc CompactSet(CompactConfig) returns (google.protobuf.Empty);
}
message EvictionCount {
uint64 page = 1;
uint64 no_min_lru_file = 2;
uint64 min_lru_inc = 3;
uint64 other_error = 4;
uint64 error = 5;
uint64 psi_exceeds_limit = 6;
}
message StatusNuma {
google.protobuf.Timestamp last_inc_time = 1;
uint64 max_seq = 2;
uint64 min_seq = 3;
uint64 run_aging_count = 4;
EvictionCount eviction_count = 5;
}
message MemCgroup {
uint32 id = 1;
uint64 ino = 2;
string path = 3;
uint64 sleep_psi_exceeds_limit = 4;
map<uint32, StatusNuma> numa = 5;
}
message MemcgStatusReply {
repeated MemCgroup mem_cgroups = 1;
}
message MemcgConfig {
optional bool disabled = 1;
optional bool swap = 2;
optional uint32 swappiness_max = 3;
optional uint64 period_secs = 4;
optional uint32 period_psi_percent_limit = 5;
optional uint32 eviction_psi_percent_limit = 6;
optional uint64 eviction_run_aging_count_min = 7;
}
message CompactConfig {
optional bool disabled = 1;
optional uint64 period_secs = 2;
optional uint32 period_psi_percent_limit = 3;
optional uint32 compact_psi_percent_limit = 4;
optional int64 compact_sec_max = 5;
optional uint32 compact_order = 6;
optional uint64 compact_threshold = 7;
optional uint64 compact_force_times = 8;
}

View File

@ -0,0 +1,29 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use anyhow::{anyhow, Result};
use chrono::{DateTime, LocalResult, TimeZone, Utc};
use protobuf::well_known_types::timestamp::Timestamp;
pub fn datatime_to_timestamp(dt: DateTime<Utc>) -> Timestamp {
let seconds = dt.timestamp();
let nanos = dt.timestamp_subsec_nanos();
Timestamp {
seconds,
nanos: nanos as i32,
..Default::default()
}
}
#[allow(dead_code)]
pub fn timestamp_to_datetime(timestamp: Timestamp) -> Result<DateTime<Utc>> {
let seconds = timestamp.seconds;
let nanos = timestamp.nanos;
match Utc.timestamp_opt(seconds, nanos as u32) {
LocalResult::Single(t) => Ok(t),
_ => Err(anyhow!("Utc.timestamp_opt {} fail", timestamp)),
}
}

View File

@ -0,0 +1,7 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
pub mod misc;
pub mod option;
pub mod rpc;

View File

@ -0,0 +1,146 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::protocols::mem_agent as rpc;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
pub struct MemcgSetOption {
#[structopt(long)]
memcg_disabled: Option<bool>,
#[structopt(long)]
memcg_swap: Option<bool>,
#[structopt(long)]
memcg_swappiness_max: Option<u8>,
#[structopt(long)]
memcg_period_secs: Option<u64>,
#[structopt(long)]
memcg_period_psi_percent_limit: Option<u8>,
#[structopt(long)]
memcg_eviction_psi_percent_limit: Option<u8>,
#[structopt(long)]
memcg_eviction_run_aging_count_min: Option<u64>,
}
impl MemcgSetOption {
#[allow(dead_code)]
pub fn to_rpc_memcg_config(&self) -> rpc::MemcgConfig {
let config = rpc::MemcgConfig {
disabled: self.memcg_disabled,
swap: self.memcg_swap,
swappiness_max: self.memcg_swappiness_max.map(|v| v as u32),
period_secs: self.memcg_period_secs,
period_psi_percent_limit: self.memcg_period_psi_percent_limit.map(|v| v as u32),
eviction_psi_percent_limit: self.memcg_eviction_psi_percent_limit.map(|v| v as u32),
eviction_run_aging_count_min: self.memcg_eviction_run_aging_count_min,
..Default::default()
};
config
}
#[allow(dead_code)]
pub fn to_mem_agent_memcg_config(&self) -> mem_agent::memcg::Config {
let mut config = mem_agent::memcg::Config {
..Default::default()
};
if let Some(v) = self.memcg_disabled {
config.disabled = v;
}
if let Some(v) = self.memcg_swap {
config.swap = v;
}
if let Some(v) = self.memcg_swappiness_max {
config.swappiness_max = v;
}
if let Some(v) = self.memcg_period_secs {
config.period_secs = v;
}
if let Some(v) = self.memcg_period_psi_percent_limit {
config.period_psi_percent_limit = v;
}
if let Some(v) = self.memcg_eviction_psi_percent_limit {
config.eviction_psi_percent_limit = v;
}
if let Some(v) = self.memcg_eviction_run_aging_count_min {
config.eviction_run_aging_count_min = v;
}
config
}
}
#[derive(Debug, StructOpt)]
pub struct CompactSetOption {
#[structopt(long)]
compact_disabled: Option<bool>,
#[structopt(long)]
compact_period_secs: Option<u64>,
#[structopt(long)]
compact_period_psi_percent_limit: Option<u8>,
#[structopt(long)]
compact_psi_percent_limit: Option<u8>,
#[structopt(long)]
compact_sec_max: Option<i64>,
#[structopt(long)]
compact_order: Option<u8>,
#[structopt(long)]
compact_threshold: Option<u64>,
#[structopt(long)]
compact_force_times: Option<u64>,
}
impl CompactSetOption {
#[allow(dead_code)]
pub fn to_rpc_compact_config(&self) -> rpc::CompactConfig {
let config = rpc::CompactConfig {
disabled: self.compact_disabled,
period_secs: self.compact_period_secs,
period_psi_percent_limit: self.compact_period_psi_percent_limit.map(|v| v as u32),
compact_psi_percent_limit: self.compact_psi_percent_limit.map(|v| v as u32),
compact_sec_max: self.compact_sec_max,
compact_order: self.compact_order.map(|v| v as u32),
compact_threshold: self.compact_threshold,
compact_force_times: self.compact_force_times,
..Default::default()
};
config
}
#[allow(dead_code)]
pub fn to_mem_agent_compact_config(&self) -> mem_agent::compact::Config {
let mut config = mem_agent::compact::Config {
..Default::default()
};
if let Some(v) = self.compact_disabled {
config.disabled = v;
}
if let Some(v) = self.compact_period_secs {
config.period_secs = v;
}
if let Some(v) = self.compact_period_psi_percent_limit {
config.period_psi_percent_limit = v;
}
if let Some(v) = self.compact_psi_percent_limit {
config.compact_psi_percent_limit = v;
}
if let Some(v) = self.compact_sec_max {
config.compact_sec_max = v;
}
if let Some(v) = self.compact_order {
config.compact_order = v;
}
if let Some(v) = self.compact_threshold {
config.compact_threshold = v;
}
if let Some(v) = self.compact_force_times {
config.compact_force_times = v;
}
config
}
}

View File

@ -0,0 +1,221 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::protocols::mem_agent as rpc_mem_agent;
use crate::protocols::{empty, mem_agent_ttrpc};
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use mem_agent::{agent, compact, memcg};
use slog_scope::{error, info};
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::sync::Arc;
use tokio::signal::unix::{signal, SignalKind};
use ttrpc::asynchronous::Server;
use ttrpc::error::Error;
use ttrpc::proto::Code;
#[derive(Debug)]
pub struct MyControl {
agent: agent::MemAgent,
}
impl MyControl {
#[allow(dead_code)]
pub fn new(agent: agent::MemAgent) -> Self {
Self { agent }
}
}
fn mem_cgroup_to_mem_cgroup_rpc(mcg: &memcg::MemCgroup) -> rpc_mem_agent::MemCgroup {
rpc_mem_agent::MemCgroup {
id: mcg.id as u32,
ino: mcg.ino as u64,
path: mcg.path.clone(),
sleep_psi_exceeds_limit: mcg.sleep_psi_exceeds_limit,
numa: mcg
.numa
.iter()
.map(|(numa_id, n)| {
(
*numa_id,
rpc_mem_agent::StatusNuma {
last_inc_time: protobuf::MessageField::some(
crate::share::misc::datatime_to_timestamp(n.last_inc_time),
),
max_seq: n.max_seq,
min_seq: n.min_seq,
run_aging_count: n.run_aging_count,
eviction_count: protobuf::MessageField::some(
rpc_mem_agent::EvictionCount {
page: n.eviction_count.page,
no_min_lru_file: n.eviction_count.no_min_lru_file,
min_lru_inc: n.eviction_count.min_lru_inc,
other_error: n.eviction_count.other_error,
error: n.eviction_count.error,
psi_exceeds_limit: n.eviction_count.psi_exceeds_limit,
..Default::default()
},
),
..Default::default()
},
)
})
.collect(),
..Default::default()
}
}
fn mem_cgroups_to_memcg_status_reply(
mgs: Vec<memcg::MemCgroup>,
) -> rpc_mem_agent::MemcgStatusReply {
let mem_cgroups: Vec<rpc_mem_agent::MemCgroup> = mgs
.iter()
.map(|x| mem_cgroup_to_mem_cgroup_rpc(&x))
.collect();
rpc_mem_agent::MemcgStatusReply {
mem_cgroups,
..Default::default()
}
}
fn memcgconfig_to_memcg_optionconfig(mc: &rpc_mem_agent::MemcgConfig) -> memcg::OptionConfig {
let moc = memcg::OptionConfig {
disabled: mc.disabled,
swap: mc.swap,
swappiness_max: mc.swappiness_max.map(|val| val as u8),
period_secs: mc.period_secs,
period_psi_percent_limit: mc.period_psi_percent_limit.map(|val| val as u8),
eviction_psi_percent_limit: mc.eviction_psi_percent_limit.map(|val| val as u8),
eviction_run_aging_count_min: mc.eviction_run_aging_count_min,
..Default::default()
};
moc
}
fn compactconfig_to_compact_optionconfig(
cc: &rpc_mem_agent::CompactConfig,
) -> compact::OptionConfig {
let coc = compact::OptionConfig {
disabled: cc.disabled,
period_secs: cc.period_secs,
period_psi_percent_limit: cc.period_psi_percent_limit.map(|val| val as u8),
compact_psi_percent_limit: cc.compact_psi_percent_limit.map(|val| val as u8),
compact_sec_max: cc.compact_sec_max,
compact_order: cc.compact_order.map(|val| val as u8),
compact_threshold: cc.compact_threshold,
compact_force_times: cc.compact_force_times,
..Default::default()
};
coc
}
#[async_trait]
impl mem_agent_ttrpc::Control for MyControl {
async fn memcg_status(
&self,
_ctx: &::ttrpc::r#async::TtrpcContext,
_: empty::Empty,
) -> ::ttrpc::Result<rpc_mem_agent::MemcgStatusReply> {
Ok(mem_cgroups_to_memcg_status_reply(
self.agent.memcg_status_async().await.map_err(|e| {
let estr = format!("agent.memcg_status_async fail: {}", e);
error!("{}", estr);
Error::RpcStatus(ttrpc::get_status(Code::INTERNAL, estr))
})?,
))
}
async fn memcg_set(
&self,
_ctx: &::ttrpc::r#async::TtrpcContext,
mc: rpc_mem_agent::MemcgConfig,
) -> ::ttrpc::Result<empty::Empty> {
self.agent
.memcg_set_config_async(memcgconfig_to_memcg_optionconfig(&mc))
.await
.map_err(|e| {
let estr = format!("agent.memcg_set_config_async fail: {}", e);
error!("{}", estr);
Error::RpcStatus(ttrpc::get_status(Code::INTERNAL, estr))
})?;
Ok(empty::Empty::new())
}
async fn compact_set(
&self,
_ctx: &::ttrpc::r#async::TtrpcContext,
cc: rpc_mem_agent::CompactConfig,
) -> ::ttrpc::Result<empty::Empty> {
self.agent
.compact_set_config_async(compactconfig_to_compact_optionconfig(&cc))
.await
.map_err(|e| {
let estr = format!("agent.compact_set_config_async fail: {}", e);
error!("{}", estr);
Error::RpcStatus(ttrpc::get_status(Code::INTERNAL, estr))
})?;
Ok(empty::Empty::new())
}
}
#[allow(dead_code)]
#[tokio::main]
pub async fn rpc_loop(agent: agent::MemAgent, addr: String) -> Result<()> {
let path = addr
.strip_prefix("unix://")
.ok_or(anyhow!("format of addr {} is not right", addr))?;
if std::path::Path::new(path).exists() {
return Err(anyhow!("addr {} is exist", addr));
}
let control = MyControl::new(agent);
let c = Box::new(control) as Box<dyn mem_agent_ttrpc::Control + Send + Sync>;
let c = Arc::new(c);
let service = mem_agent_ttrpc::create_control(c);
let mut server = Server::new().bind(&addr).unwrap().register_service(service);
let metadata = fs::metadata(path).map_err(|e| anyhow!("fs::metadata {} fail: {}", path, e))?;
let mut permissions = metadata.permissions();
permissions.set_mode(0o600);
fs::set_permissions(path, permissions)
.map_err(|e| anyhow!("fs::set_permissions {} fail: {}", path, e))?;
let mut interrupt = signal(SignalKind::interrupt())
.map_err(|e| anyhow!("signal(SignalKind::interrupt()) fail: {}", e))?;
let mut quit = signal(SignalKind::quit())
.map_err(|e| anyhow!("signal(SignalKind::quit()) fail: {}", e))?;
let mut terminate = signal(SignalKind::terminate())
.map_err(|e| anyhow!("signal(SignalKind::terminate()) fail: {}", e))?;
server
.start()
.await
.map_err(|e| anyhow!("server.start() fail: {}", e))?;
tokio::select! {
_ = interrupt.recv() => {
info!("mem-agent: interrupt shutdown");
}
_ = quit.recv() => {
info!("mem-agent: quit shutdown");
}
_ = terminate.recv() => {
info!("mem-agent: terminate shutdown");
}
};
server
.shutdown()
.await
.map_err(|e| anyhow!("server.shutdown() fail: {}", e))?;
fs::remove_file(&path).map_err(|e| anyhow!("fs::remove_file {} fail: {}", path, e))?;
Ok(())
}

View File

@ -0,0 +1,95 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use anyhow::{anyhow, Result};
use share::option::{CompactSetOption, MemcgSetOption};
use slog::{Drain, Level, Logger};
use slog_async;
use slog_scope::set_global_logger;
use slog_scope::{error, info};
use slog_term;
use std::fs::OpenOptions;
use std::io::BufWriter;
use structopt::StructOpt;
mod protocols;
mod share;
#[derive(StructOpt, Debug)]
#[structopt(name = "mem-agent", about = "Memory agent")]
struct Opt {
#[structopt(long, default_value = "unix:///var/run/mem-agent.sock")]
addr: String,
#[structopt(long)]
log_file: Option<String>,
#[structopt(long, default_value = "trace", parse(try_from_str = parse_slog_level))]
log_level: Level,
#[structopt(flatten)]
memcg: MemcgSetOption,
#[structopt(flatten)]
compact: CompactSetOption,
}
fn parse_slog_level(src: &str) -> Result<Level, String> {
match src.to_lowercase().as_str() {
"trace" => Ok(Level::Trace),
"debug" => Ok(Level::Debug),
"info" => Ok(Level::Info),
"warning" => Ok(Level::Warning),
"warn" => Ok(Level::Warning),
"error" => Ok(Level::Error),
_ => Err(format!("Invalid log level: {}", src)),
}
}
fn setup_logging(opt: &Opt) -> Result<slog_scope::GlobalLoggerGuard> {
let drain = if let Some(f) = &opt.log_file {
let log_file = OpenOptions::new()
.create(true)
.write(true)
.append(true)
.open(f)
.map_err(|e| anyhow!("Open log file {} fail: {}", f, e))?;
let buffered = BufWriter::new(log_file);
let decorator = slog_term::PlainDecorator::new(buffered);
let drain = slog_term::CompactFormat::new(decorator)
.build()
.filter_level(opt.log_level)
.fuse();
slog_async::Async::new(drain).build().fuse()
} else {
let decorator = slog_term::TermDecorator::new().stderr().build();
let drain = slog_term::CompactFormat::new(decorator)
.build()
.filter_level(opt.log_level)
.fuse();
slog_async::Async::new(drain).build().fuse()
};
let logger = Logger::root(drain, slog::o!());
Ok(set_global_logger(logger.clone()))
}
fn main() -> Result<()> {
// Check opt
let opt = Opt::from_args();
let _ = setup_logging(&opt).map_err(|e| anyhow!("setup_logging fail: {}", e))?;
let memcg_config = opt.memcg.to_mem_agent_memcg_config();
let compact_config = opt.compact.to_mem_agent_compact_config();
let (ma, _rt) = mem_agent::agent::MemAgent::new(memcg_config, compact_config)
.map_err(|e| anyhow!("MemAgent::new fail: {}", e))?;
info!("MemAgent started");
share::rpc::rpc_loop(ma, opt.addr).map_err(|e| {
let estr = format!("rpc::rpc_loop fail: {}", e);
error!("{}", estr);
anyhow!("{}", estr)
})?;
Ok(())
}

378
src/mem-agent/src/agent.rs Normal file
View File

@ -0,0 +1,378 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::compact;
use crate::memcg::{self, MemCgroup};
use crate::{error, info};
use anyhow::{anyhow, Result};
use std::thread;
use tokio::runtime::{Builder, Runtime};
use tokio::select;
use tokio::sync::mpsc;
use tokio::sync::oneshot;
use tokio::time::{sleep, Duration, Instant};
const AGENT_WORK_ERROR_SLEEP_SECS: u64 = 5 * 60;
#[derive(Debug)]
enum AgentCmd {
MemcgStatus,
MemcgSet(memcg::OptionConfig),
CompactSet(compact::OptionConfig),
}
#[allow(dead_code)]
#[derive(Debug)]
enum AgentReturn {
Ok,
Err(anyhow::Error),
MemcgStatus(Vec<memcg::MemCgroup>),
}
async fn handle_agent_cmd(
cmd: AgentCmd,
ret_tx: oneshot::Sender<AgentReturn>,
memcg: &mut memcg::MemCG,
comp: &mut compact::Compact,
) -> Result<bool> {
#[allow(unused_assignments)]
let mut ret_msg = AgentReturn::Ok;
let need_reset_mas = match cmd {
AgentCmd::MemcgStatus => {
ret_msg = AgentReturn::MemcgStatus(memcg.get_status().await);
false
}
AgentCmd::MemcgSet(opt) => memcg.set_config(opt).await,
AgentCmd::CompactSet(opt) => comp.set_config(opt).await,
};
ret_tx
.send(ret_msg)
.map_err(|e| anyhow!("ret_tx.send failed: {:?}", e))?;
Ok(need_reset_mas)
}
fn get_remaining_tokio_duration(memcg: &memcg::MemCG, comp: &compact::Compact) -> Duration {
let memcg_d = memcg.get_remaining_tokio_duration();
let comp_d = comp.get_remaining_tokio_duration();
if memcg_d > comp_d {
comp_d
} else {
memcg_d
}
}
async fn async_get_remaining_tokio_duration(
memcg: &memcg::MemCG,
comp: &compact::Compact,
) -> Duration {
let memcg_f = memcg.async_get_remaining_tokio_duration();
let comp_f = comp.async_get_remaining_tokio_duration();
let memcg_d = memcg_f.await;
let comp_d = comp_f.await;
if memcg_d > comp_d {
comp_d
} else {
memcg_d
}
}
fn agent_work(mut memcg: memcg::MemCG, mut comp: compact::Compact) -> Result<Duration> {
let memcg_need_reset = if memcg.need_work() {
info!("memcg.work start");
memcg
.work()
.map_err(|e| anyhow!("memcg.work failed: {}", e))?;
info!("memcg.work stop");
true
} else {
false
};
let compact_need_reset = if comp.need_work() {
info!("compact.work start");
comp.work()
.map_err(|e| anyhow!("comp.work failed: {}", e))?;
info!("compact.work stop");
true
} else {
false
};
if memcg_need_reset {
memcg.reset_timer();
}
if compact_need_reset {
comp.reset_timer();
}
Ok(get_remaining_tokio_duration(&memcg, &comp))
}
struct MemAgentSleep {
duration: Duration,
start_wait_time: Instant,
timeout: bool,
}
impl MemAgentSleep {
fn new() -> Self {
Self {
duration: Duration::MAX,
start_wait_time: Instant::now(),
timeout: true,
}
}
fn set_timeout(&mut self) {
self.duration = Duration::MAX;
self.timeout = true;
}
fn set_sleep(&mut self, d: Duration) {
self.duration = d;
self.start_wait_time = Instant::now();
}
/* Return true if timeout */
fn refresh(&mut self) -> bool {
if self.duration != Duration::MAX {
let elapsed = self.start_wait_time.elapsed();
if self.duration > elapsed {
self.duration -= elapsed;
} else {
/* timeout */
self.set_timeout();
return true;
}
}
false
}
}
async fn mem_agent_loop(
mut cmd_rx: mpsc::Receiver<(AgentCmd, oneshot::Sender<AgentReturn>)>,
mut memcg: memcg::MemCG,
mut comp: compact::Compact,
) -> Result<()> {
let (work_ret_tx, mut work_ret_rx) = mpsc::channel(2);
// the time that wait to next.
let mut mas = MemAgentSleep::new();
loop {
if mas.timeout {
let thread_memcg = memcg.clone();
let thread_comp = comp.clone();
let thread_work_ret_tx = work_ret_tx.clone();
thread::spawn(move || {
info!("agent work thread start");
let d = agent_work(thread_memcg, thread_comp).unwrap_or_else(|err| {
error!("agent work thread fail {}", err);
Duration::from_secs(AGENT_WORK_ERROR_SLEEP_SECS)
});
if let Err(e) = thread_work_ret_tx.blocking_send(d) {
error!("work_ret_tx.blocking_send failed: {}", e);
}
});
mas.timeout = false;
} else {
if mas.refresh() {
continue;
}
}
info!("mem_agent_loop wait timeout {:?}", mas.duration);
select! {
Some((cmd, ret_tx)) = cmd_rx.recv() => {
if handle_agent_cmd(cmd, ret_tx, &mut memcg, &mut comp).await.map_err(|e| anyhow!("handle_agent_cmd failed: {}", e))? && !mas.timeout{
mas.set_sleep(async_get_remaining_tokio_duration(&memcg, &comp).await);
}
}
d = work_ret_rx.recv() => {
info!("agent work thread stop");
mas.set_sleep(d.unwrap_or(Duration::from_secs(AGENT_WORK_ERROR_SLEEP_SECS)));
}
_ = async {
sleep(mas.duration).await;
} => {
mas.set_timeout();
}
}
}
}
#[derive(Clone, Debug)]
pub struct MemAgent {
cmd_tx: mpsc::Sender<(AgentCmd, oneshot::Sender<AgentReturn>)>,
}
impl MemAgent {
pub fn new(
memcg_config: memcg::Config,
compact_config: compact::Config,
) -> Result<(Self, Runtime)> {
let mg = memcg::MemCG::new(memcg_config)
.map_err(|e| anyhow!("memcg::MemCG::new fail: {}", e))?;
let comp = compact::Compact::new(compact_config)
.map_err(|e| anyhow!("compact::Compact::new fail: {}", e))?;
let (cmd_tx, cmd_rx) = mpsc::channel(10);
let runtime = Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.map_err(|e| anyhow!("Builder::new_multi_threa failed: {}", e))?;
runtime.spawn(async move {
info!("mem-agent start");
match mem_agent_loop(cmd_rx, mg, comp).await {
Err(e) => error!("mem-agent error {}", e),
Ok(()) => info!("mem-agent stop"),
}
});
Ok((Self { cmd_tx }, runtime))
}
async fn send_cmd_async(&self, cmd: AgentCmd) -> Result<AgentReturn> {
let (ret_tx, ret_rx) = oneshot::channel();
self.cmd_tx
.send((cmd, ret_tx))
.await
.map_err(|e| anyhow!("cmd_tx.send cmd failed: {}", e))?;
let ret = ret_rx
.await
.map_err(|e| anyhow!("ret_rx.recv failed: {}", e))?;
Ok(ret)
}
pub async fn memcg_set_config_async(&self, opt: memcg::OptionConfig) -> Result<()> {
let ret = self
.send_cmd_async(AgentCmd::MemcgSet(opt))
.await
.map_err(|e| anyhow!("send_cmd failed: {}", e))?;
match ret {
AgentReturn::Err(e) => Err(anyhow!(
"mem_agent thread memcg_set_config_async failed: {}",
e
)),
AgentReturn::Ok => Ok(()),
_ => Err(anyhow!(
"mem_agent thread memcg_set_config_async return wrong value"
)),
}
}
pub async fn compact_set_config_async(&self, opt: compact::OptionConfig) -> Result<()> {
let ret = self
.send_cmd_async(AgentCmd::CompactSet(opt))
.await
.map_err(|e| anyhow!("send_cmd failed: {}", e))?;
match ret {
AgentReturn::Err(e) => Err(anyhow!(
"mem_agent thread compact_set_config_async failed: {}",
e
)),
AgentReturn::Ok => Ok(()),
_ => Err(anyhow!(
"mem_agent thread compact_set_config_async return wrong value"
)),
}
}
pub async fn memcg_status_async(&self) -> Result<Vec<MemCgroup>> {
let ret = self
.send_cmd_async(AgentCmd::MemcgStatus)
.await
.map_err(|e| anyhow!("send_cmd failed: {}", e))?;
let status = match ret {
AgentReturn::Err(e) => {
return Err(anyhow!("mem_agent thread memcg_status_async failed: {}", e))
}
AgentReturn::Ok => {
return Err(anyhow!(
"mem_agent thread memcg_status_async return wrong value"
))
}
AgentReturn::MemcgStatus(s) => s,
};
Ok(status)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent() {
let memcg_config = memcg::Config {
disabled: true,
..Default::default()
};
let compact_config = compact::Config {
disabled: true,
..Default::default()
};
let (ma, _rt) = MemAgent::new(memcg_config, compact_config).unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on({
let memcg_config = memcg::OptionConfig {
period_secs: Some(120),
..Default::default()
};
ma.memcg_set_config_async(memcg_config)
})
.unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on({
let compact_config = compact::OptionConfig {
period_secs: Some(280),
..Default::default()
};
ma.compact_set_config_async(compact_config)
})
.unwrap();
}
#[test]
fn test_agent_memcg_status() {
let memcg_config = memcg::Config {
disabled: true,
..Default::default()
};
let compact_config = compact::Config {
disabled: true,
..Default::default()
};
let (ma, _rt) = MemAgent::new(memcg_config, compact_config).unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(ma.memcg_status_async())
.unwrap();
}
}

View File

@ -0,0 +1,446 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::proc;
use crate::psi;
use crate::timer::Timeout;
use crate::{debug, error, info, trace};
use anyhow::{anyhow, Result};
use nix::sched::sched_yield;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::Command;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::Duration as TokioDuration;
const PAGE_REPORTING_MIN_ORDER: u8 = 9;
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub disabled: bool,
pub psi_path: PathBuf,
pub period_secs: u64,
pub period_psi_percent_limit: u8,
pub compact_psi_percent_limit: u8,
pub compact_sec_max: i64,
// the order that want to get from compaction
pub compact_order: u8,
// compact_threshold is the pages number.
// When examining the /proc/pagetypeinfo, if there's an increase in the
// number of movable pages of orders smaller than the compact_order
// compared to the amount following the previous compaction,
// and this increase surpasses a certain threshold—specifically,
// more than 'compact_threshold' number of pages.
// Or the number of free pages has decreased by 'compact_threshold'
// since the previous compaction.
// then the system should initiate another round of memory compaction.
pub compact_threshold: u64,
// After one compaction, if there has not been a compaction within
// the next compact_force_times times, a compaction will be forced
// regardless of the system's memory situation.
// If compact_force_times is set to 0, will do force compaction each time.
// If compact_force_times is set to std::u64::MAX, will never do force compaction.
pub compact_force_times: u64,
}
impl Default for Config {
fn default() -> Self {
Self {
disabled: false,
psi_path: PathBuf::from(""),
period_secs: 10 * 60,
period_psi_percent_limit: 1,
compact_psi_percent_limit: 5,
compact_sec_max: 30 * 60,
compact_order: PAGE_REPORTING_MIN_ORDER,
compact_threshold: 2 << PAGE_REPORTING_MIN_ORDER,
compact_force_times: std::u64::MAX,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct OptionConfig {
pub disabled: Option<bool>,
pub psi_path: Option<PathBuf>,
pub period_secs: Option<u64>,
pub period_psi_percent_limit: Option<u8>,
pub compact_psi_percent_limit: Option<u8>,
pub compact_sec_max: Option<i64>,
pub compact_order: Option<u8>,
pub compact_threshold: Option<u64>,
pub compact_force_times: Option<u64>,
}
#[derive(Debug, Clone)]
struct CompactCore {
timeout: Timeout,
config: Config,
psi: psi::Period,
force_counter: u64,
prev_free_movable_pages_after_compact: u64,
prev_memfree_kb: u64,
}
impl CompactCore {
fn new(config: Config) -> Self {
Self {
timeout: Timeout::new(config.period_secs),
psi: psi::Period::new(&config.psi_path, true),
force_counter: 0,
prev_free_movable_pages_after_compact: 0,
prev_memfree_kb: 0,
config,
}
}
fn psi_ok(&mut self) -> bool {
if crate::misc::is_test_environment() {
return false;
}
let percent = match self.psi.get_percent() {
Ok(v) => v,
Err(e) => {
debug!("psi.get_percent failed: {}", e);
return false;
}
};
if percent > self.config.period_psi_percent_limit as u64 {
info!(
"compact will not work because period psi {}% exceeds limit",
percent
);
false
} else {
true
}
}
fn need_force_compact(&self) -> bool {
if self.config.compact_force_times == std::u64::MAX {
return false;
}
self.force_counter >= self.config.compact_force_times
}
fn check_compact_threshold(&self, memfree_kb: u64, free_movable_pages: u64) -> bool {
if self.prev_memfree_kb > memfree_kb + (self.config.compact_threshold << 2) {
return true;
}
let threshold = self.config.compact_threshold + self.prev_free_movable_pages_after_compact;
if free_movable_pages > threshold {
true
} else {
info!(
"compact will not work because free movable pages {} less than threshold {} and prev_free {}kB current_free {}kB",
free_movable_pages, threshold, self.prev_memfree_kb, memfree_kb
);
false
}
}
fn get_special_psi(&self) -> psi::Period {
psi::Period::new(&self.config.psi_path, true)
}
fn set_prev(&mut self, memfree_kb: u64, free_movable_pages: u64) {
self.prev_memfree_kb = memfree_kb;
self.prev_free_movable_pages_after_compact = free_movable_pages;
}
fn set_disabled(&mut self, disabled: bool) {
if !disabled {
self.timeout.reset();
}
self.config.disabled = disabled;
}
// return if MemAgentSleep need be reset
fn set_config(&mut self, new_config: OptionConfig) -> bool {
let mut need_reset_mas = false;
if let Some(d) = new_config.disabled {
if self.config.disabled != d {
self.set_disabled(d);
need_reset_mas = true;
}
}
if let Some(p) = new_config.psi_path {
self.config.psi_path = p.clone();
}
if let Some(p) = new_config.period_psi_percent_limit {
self.config.period_psi_percent_limit = p;
}
if let Some(p) = new_config.compact_psi_percent_limit {
self.config.compact_psi_percent_limit = p;
}
if let Some(p) = new_config.compact_sec_max {
self.config.compact_sec_max = p;
}
if let Some(p) = new_config.compact_order {
self.config.compact_order = p;
}
if let Some(p) = new_config.compact_threshold {
self.config.compact_threshold = p;
}
if let Some(p) = new_config.compact_force_times {
self.config.compact_force_times = p;
}
if let Some(p) = new_config.period_secs {
self.config.period_secs = p;
self.timeout.set_sleep_duration(p);
if !self.config.disabled {
need_reset_mas = true;
}
}
info!("new compact config: {:#?}", self.config);
if need_reset_mas {
info!("need reset mem-agent sleep");
}
need_reset_mas
}
fn need_work(&self) -> bool {
if self.config.disabled {
return false;
}
self.timeout.is_timeout()
}
pub fn get_remaining_tokio_duration(&self) -> TokioDuration {
if self.config.disabled {
return TokioDuration::MAX;
}
self.timeout.remaining_tokio_duration()
}
}
#[derive(Debug, Clone)]
pub struct Compact {
core: Arc<RwLock<CompactCore>>,
}
impl Compact {
pub fn new(mut config: Config) -> Result<Self> {
config.psi_path =
psi::check(&config.psi_path).map_err(|e| anyhow!("psi::check failed: {}", e))?;
let c = Self {
core: Arc::new(RwLock::new(CompactCore::new(config))),
};
Ok(c)
}
fn calculate_free_movable_pages(&self) -> Result<u64> {
let file = File::open("/proc/pagetypeinfo")?;
let reader = BufReader::new(file);
let order_limit = self.core.blocking_read().config.compact_order as usize;
let mut total_free_movable_pages = 0;
for line in reader.lines() {
let line = line?;
if line.contains("Movable") {
let parts: Vec<&str> = line.split_whitespace().collect();
if let Some(index) = parts.iter().position(|&element| element == "Movable") {
for (order, &count_str) in parts[(index + 1)..].iter().enumerate() {
if order < order_limit {
if let Ok(count) = count_str.parse::<u64>() {
total_free_movable_pages += count * 1 << order;
}
}
}
}
}
}
Ok(total_free_movable_pages)
}
fn check_compact_threshold(&self) -> bool {
let memfree_kb = match proc::get_memfree_kb() {
Ok(v) => v,
Err(e) => {
error!("get_memfree_kb failed: {}", e);
return false;
}
};
let free_movable_pages = match self.calculate_free_movable_pages() {
Ok(v) => v,
Err(e) => {
error!("calculate_free_movable_pages failed: {}", e);
return false;
}
};
self.core
.blocking_read()
.check_compact_threshold(memfree_kb, free_movable_pages)
}
fn set_prev(&mut self) -> Result<()> {
let memfree_kb =
proc::get_memfree_kb().map_err(|e| anyhow!("get_memfree_kb failed: {}", e))?;
let free_movable_pages = self
.calculate_free_movable_pages()
.map_err(|e| anyhow!("calculate_free_movable_pages failed: {}", e))?;
self.core
.blocking_write()
.set_prev(memfree_kb, free_movable_pages);
Ok(())
}
fn do_compact(&self) -> Result<()> {
let compact_psi_percent_limit = self.core.blocking_read().config.compact_psi_percent_limit;
let mut compact_psi = self.core.blocking_read().get_special_psi();
let mut rest_sec = self.core.blocking_read().config.compact_sec_max;
if let Err(e) = sched_yield() {
error!("sched_yield failed: {:?}", e);
}
info!("compact start");
let mut child = Command::new("sh")
.arg("-c")
.arg("echo 1 > /proc/sys/vm/compact_memory")
.spawn()
.map_err(|e| anyhow!("Command::new failed: {}", e))?;
debug!("compact pid {}", child.id());
let mut killed = false;
loop {
match child.try_wait() {
Ok(Some(status)) => {
debug!("compact done with status {}", status);
break;
}
Ok(None) => {
if killed {
if rest_sec <= 0 {
error!("compact killed but not quit");
break;
} else {
debug!("compact killed and keep wait");
}
} else {
if rest_sec <= 0 {
debug!("compact timeout");
child
.kill()
.map_err(|e| anyhow!("child.kill failed: {}", e))?;
killed = true;
}
}
let percent = compact_psi
.get_percent()
.map_err(|e| anyhow!("compact_psi.get_percent failed: {}", e))?;
if percent > compact_psi_percent_limit as u64 {
info!(
"compaction need stop because period psi {}% exceeds limit",
percent
);
child
.kill()
.map_err(|e| anyhow!("child.kill failed: {}", e))?;
killed = true;
}
}
Err(e) => {
// try_wait will fail with code 10 because some task will
// wait compact task before try_wait.
debug!("compact try_wait fail: {:?}", e);
break;
}
}
thread::sleep(Duration::from_secs(1));
rest_sec -= 1;
}
info!("compact stop");
Ok(())
}
pub fn need_work(&self) -> bool {
self.core.blocking_read().need_work()
}
pub fn reset_timer(&mut self) {
self.core.blocking_write().timeout.reset();
}
pub fn get_remaining_tokio_duration(&self) -> TokioDuration {
self.core.blocking_read().get_remaining_tokio_duration()
}
pub async fn async_get_remaining_tokio_duration(&self) -> TokioDuration {
self.core.read().await.get_remaining_tokio_duration()
}
pub fn work(&mut self) -> Result<()> {
let mut can_work = self.core.blocking_write().psi_ok();
if can_work {
if !self.core.blocking_read().need_force_compact() {
if !self.check_compact_threshold() {
trace!("not enough free movable pages");
can_work = false;
}
} else {
trace!("force compact");
}
}
if can_work {
self.do_compact()
.map_err(|e| anyhow!("do_compact failed: {}", e))?;
self.set_prev()?;
self.core.blocking_write().force_counter = 0;
} else {
self.core.blocking_write().force_counter += 1;
}
Ok(())
}
pub async fn set_config(&mut self, new_config: OptionConfig) -> bool {
self.core.write().await.set_config(new_config)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compact() {
let mut c = Compact::new(Config::default()).unwrap();
assert!(c.work().is_ok());
}
}

12
src/mem-agent/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
pub mod agent;
pub mod compact;
pub mod memcg;
mod mglru;
mod misc;
mod proc;
mod psi;
mod timer;

843
src/mem-agent/src/memcg.rs Normal file
View File

@ -0,0 +1,843 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::mglru::{self, MGenLRU};
use crate::timer::Timeout;
use crate::{debug, error, info, trace};
use crate::{proc, psi};
use anyhow::{anyhow, Context, Result};
use chrono::{DateTime, Utc};
use nix::sched::sched_yield;
use page_size;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::time::Duration as TokioDuration;
/* If last_inc_time to current_time small than IDLE_FRESH_IGNORE_SECS,
not do idle_fresh for this memcg. */
const IDLE_FRESH_IGNORE_SECS: i64 = 60;
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub disabled: bool,
pub swap: bool,
pub swappiness_max: u8,
pub psi_path: PathBuf,
pub period_secs: u64,
pub period_psi_percent_limit: u8,
pub eviction_psi_percent_limit: u8,
pub eviction_run_aging_count_min: u64,
}
impl Default for Config {
fn default() -> Self {
Self {
disabled: false,
swap: false,
swappiness_max: 50,
psi_path: PathBuf::from(""),
period_secs: 10 * 60,
period_psi_percent_limit: 1,
eviction_psi_percent_limit: 1,
eviction_run_aging_count_min: 3,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct OptionConfig {
pub disabled: Option<bool>,
pub swap: Option<bool>,
pub swappiness_max: Option<u8>,
pub psi_path: Option<PathBuf>,
pub period_secs: Option<u64>,
pub period_psi_percent_limit: Option<u8>,
pub eviction_psi_percent_limit: Option<u8>,
pub eviction_run_aging_count_min: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct EvictionCount {
pub page: u64,
pub no_min_lru_file: u64,
pub min_lru_inc: u64,
pub other_error: u64,
pub error: u64,
pub psi_exceeds_limit: u64,
}
#[derive(Debug, Clone)]
pub struct Numa {
pub max_seq: u64,
pub min_seq: u64,
pub last_inc_time: DateTime<Utc>,
pub min_lru_file: u64,
pub min_lru_anon: u64,
pub run_aging_count: u64,
pub eviction_count: EvictionCount,
}
impl Numa {
fn new(mglru: &MGenLRU) -> Self {
Self {
max_seq: mglru.max_seq,
min_seq: mglru.min_seq,
last_inc_time: mglru.last_birth,
min_lru_file: mglru.lru[mglru.min_lru_index].file,
min_lru_anon: mglru.lru[mglru.min_lru_index].anon,
run_aging_count: 0,
eviction_count: EvictionCount {
page: 0,
no_min_lru_file: 0,
min_lru_inc: 0,
other_error: 0,
error: 0,
psi_exceeds_limit: 0,
},
}
}
fn update(&mut self, mglru: &MGenLRU) {
self.max_seq = mglru.max_seq;
self.min_seq = mglru.min_seq;
self.last_inc_time = mglru.last_birth;
self.min_lru_file = mglru.lru[mglru.min_lru_index].file;
self.min_lru_anon = mglru.lru[mglru.min_lru_index].anon;
}
}
#[derive(Debug, Clone)]
pub struct MemCgroup {
/* get from Linux kernel static inline unsigned short mem_cgroup_id(struct mem_cgroup *memcg) */
pub id: u16,
pub ino: usize,
pub path: String,
pub numa: HashMap<u32, Numa>,
psi: psi::Period,
pub sleep_psi_exceeds_limit: u64,
}
impl MemCgroup {
fn new(
id: &usize,
ino: &usize,
path: &String,
hmg: &HashMap<usize, MGenLRU>,
psi_path: &PathBuf,
) -> Self {
let s = Self {
id: *id as u16,
ino: *ino,
path: path.to_string(),
numa: hmg
.iter()
.map(|(numa_id, mglru)| (*numa_id as u32, Numa::new(mglru)))
.collect(),
psi: psi::Period::new(&psi_path.join(path.trim_start_matches('/')), false),
sleep_psi_exceeds_limit: 0,
};
info!("MemCgroup::new {:?}", s);
s
}
fn update_from_hostmemcg(&mut self, hmg: &HashMap<usize, MGenLRU>) {
self.numa
.retain(|numa_id, _| hmg.contains_key(&(*numa_id as usize)));
for (numa_id, mglru) in hmg {
self.numa
.entry(*numa_id as u32)
.and_modify(|e| e.update(mglru))
.or_insert(Numa::new(mglru));
}
}
}
#[derive(Debug, Clone)]
enum EvictionStopReason {
None,
NoMinLru,
MinLruInc,
GetError,
PsiExceedsLimit,
}
#[derive(Debug, Clone)]
struct EvictionInfo {
psi: psi::Period,
// the min_lru_file and min_lru_anon before last time mglru::run_eviction
last_min_lru_file: u64,
last_min_lru_anon: u64,
// the evicted page count
file_page_count: u64,
anon_page_count: u64,
only_swap_mode: bool,
anon_eviction_max: u64,
stop_reason: EvictionStopReason,
}
#[derive(Debug, Clone)]
struct Info {
memcg_id: usize,
numa_id: usize,
path: String,
max_seq: u64,
min_seq: u64,
last_inc_time: DateTime<Utc>,
min_lru_file: u64,
min_lru_anon: u64,
eviction: Option<EvictionInfo>,
}
impl Info {
fn new(mcg: &MemCgroup, numa_id: usize, numa: &Numa) -> Self {
Self {
memcg_id: mcg.id as usize,
numa_id: numa_id,
path: mcg.path.clone(),
min_seq: numa.min_seq,
max_seq: numa.max_seq,
last_inc_time: numa.last_inc_time,
min_lru_file: numa.min_lru_file,
min_lru_anon: numa.min_lru_anon,
eviction: None,
}
}
fn update(&mut self, numa: &Numa) {
self.min_seq = numa.min_seq;
self.max_seq = numa.max_seq;
self.last_inc_time = numa.last_inc_time;
self.min_lru_file = numa.min_lru_file;
self.min_lru_anon = numa.min_lru_anon;
}
}
#[derive(Debug)]
struct MemCgroups {
timeout: Timeout,
config: Config,
id_map: HashMap<u16, MemCgroup>,
ino2id: HashMap<usize, u16>,
path2id: HashMap<String, u16>,
}
impl MemCgroups {
fn new(config: Config) -> Self {
Self {
timeout: Timeout::new(config.period_secs),
config,
id_map: HashMap::new(),
ino2id: HashMap::new(),
path2id: HashMap::new(),
}
}
/* Remove not exist in host or id, ino changed memcgroup */
fn remove_changed(
&mut self,
mg_hash: &HashMap<String, (usize, usize, HashMap<usize, MGenLRU>)>,
) {
/* Remove not exist in host or id, ino changed memcgroup */
let mut remove_target = Vec::new();
for (_, mg) in &self.id_map {
let mut should_remove = true;
if let Some((id, ino, _)) = mg_hash.get(&mg.path) {
if mg.id as usize == *id && mg.ino == *ino {
should_remove = false;
}
}
if should_remove {
remove_target.push((mg.id, mg.ino, mg.path.clone()));
}
}
for (id, ino, path) in remove_target {
self.id_map.remove(&id);
self.ino2id.remove(&ino);
self.path2id.remove(&path);
info!("Remove memcg {} {} {} because host changed.", id, ino, path)
}
}
fn update_and_add(
&mut self,
mg_hash: &HashMap<String, (usize, usize, HashMap<usize, MGenLRU>)>,
) {
for (path, (id, ino, hmg)) in mg_hash {
if *id == 0 {
info!(
"Not add {} {} {} because it is disabled.",
*id,
*ino,
path.to_string()
)
}
if let Some(mg) = self.id_map.get_mut(&(*id as u16)) {
mg.update_from_hostmemcg(&hmg);
} else {
self.id_map.insert(
*id as u16,
MemCgroup::new(id, ino, path, hmg, &self.config.psi_path),
);
self.ino2id.insert(*ino, *id as u16);
self.path2id.insert(path.to_string(), *id as u16);
}
}
}
fn check_psi_get_info(&mut self) -> Vec<Info> {
let mut info_ret = Vec::new();
for (_, mcg) in self.id_map.iter_mut() {
let percent = match mcg.psi.get_percent() {
Ok(p) => p,
Err(e) => {
debug!("mcg.psi.get_percent {} failed: {}", mcg.path, e);
continue;
}
};
if percent > self.config.period_psi_percent_limit as u64 {
mcg.sleep_psi_exceeds_limit += 1;
info!("{} period psi {}% exceeds limit", mcg.path, percent);
continue;
}
for (numa_id, numa) in &mcg.numa {
info_ret.push(Info::new(&mcg, *numa_id as usize, numa));
}
}
info_ret
}
fn update_info(&self, infov: &mut Vec<Info>) {
let mut i = 0;
while i < infov.len() {
if let Some(mg) = self.id_map.get(&(infov[i].memcg_id as u16)) {
if let Some(numa) = mg.numa.get(&(infov[i].numa_id as u32)) {
infov[i].update(numa);
i += 1;
continue;
}
}
infov.remove(i);
}
}
fn inc_run_aging_count(&mut self, infov: &mut Vec<Info>) {
let mut i = 0;
while i < infov.len() {
if let Some(mg) = self.id_map.get_mut(&(infov[i].memcg_id as u16)) {
if let Some(numa) = mg.numa.get_mut(&(infov[i].numa_id as u32)) {
numa.run_aging_count += 1;
if numa.run_aging_count >= self.config.eviction_run_aging_count_min {
i += 1;
continue;
}
}
}
infov.remove(i);
}
}
fn record_eviction(&mut self, infov: &Vec<Info>) {
for info in infov {
if let Some(mg) = self.id_map.get_mut(&(info.memcg_id as u16)) {
if let Some(numa) = mg.numa.get_mut(&(info.numa_id as u32)) {
if let Some(ei) = &info.eviction {
numa.eviction_count.page += ei.file_page_count + ei.anon_page_count;
match ei.stop_reason {
EvictionStopReason::None => numa.eviction_count.other_error += 1,
EvictionStopReason::NoMinLru => {
numa.eviction_count.no_min_lru_file += 1
}
EvictionStopReason::MinLruInc => numa.eviction_count.min_lru_inc += 1,
EvictionStopReason::GetError => numa.eviction_count.error += 1,
EvictionStopReason::PsiExceedsLimit => {
numa.eviction_count.psi_exceeds_limit += 1
}
}
}
}
}
}
}
fn set_disabled(&mut self, disabled: bool) {
if !disabled {
self.timeout.reset();
}
self.config.disabled = disabled;
}
// return if MemAgentSleep need be reset
fn set_config(&mut self, new_config: OptionConfig) -> bool {
let mut need_reset_mas = false;
if let Some(d) = new_config.disabled {
if self.config.disabled != d {
self.set_disabled(d);
need_reset_mas = true;
}
}
if let Some(s) = new_config.swap {
self.config.swap = s;
}
if let Some(s) = new_config.swappiness_max {
self.config.swappiness_max = s;
}
if let Some(p) = new_config.psi_path {
self.config.psi_path = p.clone();
}
if let Some(p) = new_config.period_psi_percent_limit {
self.config.period_psi_percent_limit = p;
}
if let Some(p) = new_config.eviction_psi_percent_limit {
self.config.eviction_psi_percent_limit = p;
}
if let Some(p) = new_config.eviction_run_aging_count_min {
self.config.eviction_run_aging_count_min = p;
}
if let Some(p) = new_config.period_secs {
self.config.period_secs = p;
self.timeout.set_sleep_duration(p);
if !self.config.disabled {
need_reset_mas = true;
}
}
info!("new memcg config: {:#?}", self.config);
if need_reset_mas {
info!("need reset mem-agent sleep");
}
need_reset_mas
}
fn need_work(&self) -> bool {
if self.config.disabled {
return false;
}
self.timeout.is_timeout()
}
pub fn get_remaining_tokio_duration(&self) -> TokioDuration {
if self.config.disabled {
return TokioDuration::MAX;
}
self.timeout.remaining_tokio_duration()
}
}
#[derive(Debug, Clone)]
pub struct MemCG {
memcgs: Arc<RwLock<MemCgroups>>,
}
impl MemCG {
pub fn new(mut config: Config) -> Result<Self> {
mglru::check().map_err(|e| anyhow!("mglru::check failed: {}", e))?;
config.psi_path =
psi::check(&config.psi_path).map_err(|e| anyhow!("psi::check failed: {}", e))?;
let memcg = Self {
memcgs: Arc::new(RwLock::new(MemCgroups::new(config))),
};
Ok(memcg)
}
/*
* If target_paths.len == 0,
* will remove the updated or not exist cgroup in the host from MemCgroups.
* If target_paths.len > 0, will not do that.
*/
fn refresh(&mut self, target_paths: &HashSet<String>) -> Result<()> {
let mg_hash = mglru::host_memcgs_get(target_paths, true)
.map_err(|e| anyhow!("lru_gen_parse::file_parse failed: {}", e))?;
let mut mgs = self.memcgs.blocking_write();
if target_paths.len() == 0 {
mgs.remove_changed(&mg_hash);
}
mgs.update_and_add(&mg_hash);
Ok(())
}
fn run_aging(&mut self, infov: &mut Vec<Info>, swap: bool) {
infov.retain(|info| {
let now = Utc::now();
if now.signed_duration_since(info.last_inc_time).num_seconds() < IDLE_FRESH_IGNORE_SECS
{
info!(
"{} not run aging because last_inc_time {}",
info.path, info.last_inc_time,
);
true
} else {
let res = if let Err(e) =
mglru::run_aging(info.memcg_id, info.numa_id, info.max_seq, swap, true)
{
error!(
"mglru::run_aging {} {} {} failed: {}",
info.path, info.memcg_id, info.numa_id, e
);
false
} else {
true
};
if let Err(e) = sched_yield() {
error!("sched_yield failed: {:?}", e);
}
res
}
});
self.memcgs.blocking_write().inc_run_aging_count(infov);
}
fn swap_not_available(&self) -> Result<bool> {
let freeswap_kb = proc::get_freeswap_kb().context("proc::get_freeswap_kb")?;
if freeswap_kb > (256 * page_size::get() as u64 / 1024) {
Ok(false)
} else {
Ok(true)
}
}
fn get_swappiness(&self, anon_count: u64, file_count: u64) -> u8 {
assert!(
anon_count != 0 && file_count != 0,
"anon and file must be non-zero"
);
let total = anon_count + file_count;
let c = 200 * anon_count / total;
c as u8
}
fn run_eviction(&mut self, infov: &mut Vec<Info>, mut swap: bool) -> Result<()> {
if self
.swap_not_available()
.context("self.swap_not_available")?
{
swap = false;
}
let psi_path = self.memcgs.blocking_read().config.psi_path.clone();
for info in infov.into_iter() {
info.eviction = Some(EvictionInfo {
psi: psi::Period::new(&psi_path.join(info.path.trim_start_matches('/')), false),
last_min_lru_file: 0,
last_min_lru_anon: 0,
file_page_count: 0,
anon_page_count: 0,
only_swap_mode: false,
anon_eviction_max: 0,
stop_reason: EvictionStopReason::None,
});
}
let mut removed_infov = Vec::new();
let mut ret = Ok(());
let eviction_psi_percent_limit = self
.memcgs
.blocking_read()
.config
.eviction_psi_percent_limit as u64;
let swappiness_max = self.memcgs.blocking_read().config.swappiness_max;
'main_loop: while infov.len() != 0 {
let mut i = 0;
while i < infov.len() {
let path_set: HashSet<String> =
infov.iter().map(|info| info.path.clone()).collect();
match self.refresh(&path_set) {
Ok(_) => {}
Err(e) => {
ret = Err(anyhow!("refresh failed: {}", e));
break 'main_loop;
}
};
self.update_info(infov);
let ci = infov[i].clone();
trace!("{} {} run_eviction single loop start", ci.path, ci.numa_id);
if let Some(ref mut ei) = infov[i].eviction {
if ei.last_min_lru_file == 0 && ei.last_min_lru_anon == 0 {
// First loop
trace!("{} {} run_eviction begin", ci.path, ci.numa_id,);
if ci.min_lru_file == 0 {
if !swap || ci.min_lru_anon == 0 {
info!(
"{} {} run_eviction stop because min_lru_file is 0 or min_lru_anon is 0, release {} {} pages",
ci.path, ci.numa_id, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::NoMinLru;
removed_infov.push(infov.remove(i));
continue;
} else {
ei.only_swap_mode = true;
ei.anon_eviction_max =
ci.min_lru_anon * swappiness_max as u64 / 200;
trace!(
"{} {} run_eviction only swap mode anon_eviction_max {}",
ci.path,
ci.numa_id,
ei.anon_eviction_max
);
}
}
ei.last_min_lru_file = ci.min_lru_file;
ei.last_min_lru_anon = ci.min_lru_anon;
} else {
if (!ei.only_swap_mode && ci.min_lru_file >= ei.last_min_lru_file)
|| (ei.only_swap_mode && ci.min_lru_file > 0)
|| (swap && ci.min_lru_anon > ei.last_min_lru_anon)
{
info!(
"{} {} run_eviction stop because min_lru_file {} last_min_lru_file {} min_lru_anon {} last_min_lru_anon {}, release {} {} pages",
ci.path, ci.numa_id, ci.min_lru_file, ei.last_min_lru_file, ci.min_lru_anon, ei.last_min_lru_anon, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::MinLruInc;
removed_infov.push(infov.remove(i));
continue;
}
let released = ei.last_min_lru_anon - ci.min_lru_anon;
trace!(
"{} {} run_eviction anon {} pages",
ci.path,
ci.numa_id,
released
);
ei.anon_page_count += released;
let released = ei.last_min_lru_file - ci.min_lru_file;
trace!(
"{} {} run_eviction file {} pages",
ci.path,
ci.numa_id,
released
);
ei.file_page_count += released;
if !ei.only_swap_mode {
if ci.min_lru_file == 0 {
info!(
"{} {} run_eviction stop because min_lru_file is 0, release {} {} pages",
ci.path, ci.numa_id, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::NoMinLru;
removed_infov.push(infov.remove(i));
continue;
}
} else {
if ei.anon_page_count >= ei.anon_eviction_max {
info!(
"{} {} run_eviction stop because anon_page_count is bigger than anon_eviction_max {}, release {} {} pages",
ci.path, ci.numa_id, ei.anon_eviction_max, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::NoMinLru;
removed_infov.push(infov.remove(i));
continue;
}
}
let percent = match ei.psi.get_percent() {
Ok(p) => p,
Err(e) => {
debug!(
"{} {} ei.psi.get_percent failed: {}, release {} {} pages",
ci.path, ci.numa_id, e, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::GetError;
removed_infov.push(infov.remove(i));
continue;
}
};
if percent > eviction_psi_percent_limit {
info!(
"{} {} run_eviction stop because period psi {}% exceeds limit, release {} {} pages",
ci.path, ci.numa_id, percent, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::PsiExceedsLimit;
removed_infov.push(infov.remove(i));
continue;
}
ei.last_min_lru_file = ci.min_lru_file;
ei.last_min_lru_anon = ci.min_lru_anon;
}
// get swapiness
let swappiness = if ei.only_swap_mode {
200
} else if !swap
|| ci.min_lru_anon == 0
|| match self.swap_not_available() {
Ok(b) => b,
Err(e) => {
ret = Err(anyhow!("swap_not_available failed: {:?}", e));
break 'main_loop;
}
}
{
0
} else {
let s = self.get_swappiness(ci.min_lru_anon, ci.min_lru_file);
if s > swappiness_max {
swappiness_max
} else {
s
}
};
trace!(
"{} {} run_eviction min_seq {} swappiness {}",
ci.path,
ci.numa_id,
ci.min_seq,
swappiness
);
match mglru::run_eviction(ci.memcg_id, ci.numa_id, ci.min_seq, swappiness, 1) {
Ok(_) => {}
Err(e) => {
error!(
"{} {} mglru::run_eviction failed: {}, release {} {} pages",
ci.path, ci.numa_id, e, ei.anon_page_count, ei.file_page_count,
);
ei.stop_reason = EvictionStopReason::GetError;
removed_infov.push(infov.remove(i));
continue;
}
}
if let Err(e) = sched_yield() {
error!("sched_yield failed: {:?}", e);
}
} else {
unreachable!();
}
i += 1;
}
}
let mut mgs = self.memcgs.blocking_write();
mgs.record_eviction(&infov);
mgs.record_eviction(&removed_infov);
ret
}
fn check_psi_get_info(&mut self) -> Vec<Info> {
self.memcgs.blocking_write().check_psi_get_info()
}
fn update_info(&self, infov: &mut Vec<Info>) {
self.memcgs.blocking_read().update_info(infov);
}
pub fn need_work(&self) -> bool {
self.memcgs.blocking_read().need_work()
}
pub fn reset_timer(&mut self) {
self.memcgs.blocking_write().timeout.reset();
}
pub fn get_remaining_tokio_duration(&self) -> TokioDuration {
self.memcgs.blocking_read().get_remaining_tokio_duration()
}
pub async fn async_get_remaining_tokio_duration(&self) -> TokioDuration {
self.memcgs.read().await.get_remaining_tokio_duration()
}
pub fn work(&mut self) -> Result<()> {
/* Refresh memcgroups info from host and store it to infov. */
self.refresh(&HashSet::new())
.map_err(|e| anyhow!("first refresh failed: {}", e))?;
let mut infov = self.check_psi_get_info();
let swap = self.memcgs.blocking_read().config.swap;
/* Run aging with infov. */
self.run_aging(&mut infov, swap);
self.run_eviction(&mut infov, swap)
.map_err(|e| anyhow!("run_eviction failed: {}", e))?;
Ok(())
}
pub async fn set_config(&mut self, new_config: OptionConfig) -> bool {
self.memcgs.write().await.set_config(new_config)
}
pub async fn get_status(&self) -> Vec<MemCgroup> {
let mut mcgs = Vec::new();
let mgs = self.memcgs.read().await;
for (_, m) in mgs.id_map.iter() {
mcgs.push((*m).clone());
}
mcgs
}
}
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_memcg_swap_not_available() {
let m = MemCG::new(Config::default()).unwrap();
assert!(m.swap_not_available().is_ok());
}
#[test]
fn test_memcg_get_swappiness() {
let m = MemCG::new(Config::default()).unwrap();
assert_eq!(m.get_swappiness(100, 50), 133);
}
#[test]
fn test_memcg_need_work() {
let m = MemCG::new(Config::default()).unwrap();
assert_eq!(m.need_work(), true);
}
}

924
src/mem-agent/src/mglru.rs Normal file
View File

@ -0,0 +1,924 @@
// Copyright (C) 2023 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::debug;
use crate::warn;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Duration, Utc};
use std::collections::HashMap;
use std::collections::HashSet;
use std::fs::{self, File, OpenOptions};
use std::io::{BufRead, BufReader};
use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
const WORKINGSET_ANON: usize = 0;
const WORKINGSET_FILE: usize = 1;
const LRU_GEN_ENABLED_PATH: &str = "/sys/kernel/mm/lru_gen/enabled";
const LRU_GEN_PATH: &str = "/sys/kernel/debug/lru_gen";
const MEMCGS_PATH: &str = "/sys/fs/cgroup/memory";
fn lru_gen_head_parse(line: &str) -> Result<(usize, String)> {
let words: Vec<&str> = line.split_whitespace().map(|word| word.trim()).collect();
if words.len() != 3 || words[0] != "memcg" {
return Err(anyhow!("line {} format is not right", line));
}
let id = usize::from_str_radix(words[1], 10)
.map_err(|e| anyhow!("parse line {} failed: {}", line, e))?;
Ok((id, words[2].to_string()))
}
#[derive(Debug, PartialEq)]
pub struct GenLRU {
pub seq: u64,
pub anon: u64,
pub file: u64,
pub birth: DateTime<Utc>,
}
impl GenLRU {
fn new() -> Self {
Self {
seq: 0,
anon: 0,
file: 0,
birth: Utc::now(),
}
}
}
#[derive(Debug, PartialEq)]
pub struct MGenLRU {
pub min_seq: u64,
pub max_seq: u64,
pub last_birth: DateTime<Utc>,
pub min_lru_index: usize,
pub lru: Vec<GenLRU>,
}
impl MGenLRU {
fn new() -> Self {
Self {
min_seq: 0,
max_seq: 0,
last_birth: Utc::now(),
min_lru_index: 0,
lru: Vec::new(),
}
}
}
//result:
// last_line, HashMap<node_id, MGenLRU>
fn lru_gen_lines_parse(reader: &mut BufReader<File>) -> Result<(String, HashMap<usize, MGenLRU>)> {
let mut line = String::new();
let mut ret_hash = HashMap::new();
while line.len() > 0
|| reader
.read_line(&mut line)
.map_err(|e| anyhow!("read file {} failed: {}", LRU_GEN_PATH, e))?
> 0
{
let words: Vec<&str> = line.split_whitespace().map(|word| word.trim()).collect();
if words.len() == 2 && words[0] == "node" {
// Got a new node
let node_id = usize::from_str_radix(words[1], 10)
.map_err(|e| anyhow!("parse line {} failed: {}", line, e))?;
let (ret_line, node_size) = lru_gen_seq_lines_parse(reader)
.map_err(|e| anyhow!("lru_gen_seq_lines_parse failed: {}", e))?;
if let Some(size) = node_size {
ret_hash.insert(node_id, size);
}
line = ret_line;
} else {
// Cannot get node, return the line let caller handle it.
break;
}
}
Ok((line, ret_hash))
}
fn str_to_u64(str: &str) -> Result<u64> {
if str.starts_with("-") {
warn!("{} format {} is not right", LRU_GEN_PATH, str);
return Ok(0);
}
Ok(u64::from_str_radix(str, 10)?)
}
//result:
// last_line, Option<MGenLRU>
fn lru_gen_seq_lines_parse(reader: &mut BufReader<File>) -> Result<(String, Option<MGenLRU>)> {
let mut line = String::new();
let mut ret = MGenLRU::new();
let mut got = false;
while reader
.read_line(&mut line)
.map_err(|e| anyhow!("read file {} failed: {}", LRU_GEN_PATH, e))?
> 0
{
let words: Vec<&str> = line.split_whitespace().map(|word| word.trim()).collect();
if words.len() != 4 {
//line is not format of seq line
break;
}
let msecs = i64::from_str_radix(words[1], 10)
.map_err(|e| anyhow!("parse line {} failed: {}", line, e))?;
// Use milliseconds because will got build error with try_milliseconds.
#[allow(deprecated)]
let birth = Utc::now() - Duration::milliseconds(msecs);
let mut gen = GenLRU::new();
gen.birth = birth;
gen.seq = u64::from_str_radix(words[0], 10)
.map_err(|e| anyhow!("parse line {} failed: {}", line, e))?;
gen.anon = str_to_u64(&words[2 + WORKINGSET_ANON])
.map_err(|e| anyhow!("parse line {} failed: {}", line, e))?;
gen.file = str_to_u64(&words[2 + WORKINGSET_FILE])
.map_err(|e| anyhow!("parse line {} failed: {}", line, e))?;
if !got {
ret.min_seq = gen.seq;
ret.max_seq = gen.seq;
ret.last_birth = birth;
got = true;
} else {
ret.min_seq = std::cmp::min(ret.min_seq, gen.seq);
ret.max_seq = std::cmp::max(ret.max_seq, gen.seq);
if ret.last_birth < birth {
ret.last_birth = birth;
}
}
if gen.seq == ret.min_seq {
ret.min_lru_index = ret.lru.len();
}
ret.lru.push(gen);
line.clear();
}
Ok((line, if got { Some(ret) } else { None }))
}
// Just handle the path in the target_patchs. But if len of target_patchs is 0, will handle all paths.
// if parse_line is false
// HashMap<node_id, MGenLRU> will be empty.
//result:
// HashMap<path, (id, HashMap<node_id, MGenLRU>)>
fn lru_gen_file_parse(
mut reader: &mut BufReader<File>,
target_patchs: &HashSet<String>,
parse_line: bool,
) -> Result<HashMap<String, (usize, HashMap<usize, MGenLRU>)>> {
let mut line = String::new();
let mut ret_hash = HashMap::new();
while line.len() > 0
|| reader
.read_line(&mut line)
.map_err(|e| anyhow!("read file {} failed: {}", LRU_GEN_PATH, e))?
> 0
{
let mut clear_line = true;
// Not handle the Err of lru_gen_head_parse because all lines of file will be checked.
if let Ok((id, path)) = lru_gen_head_parse(&line) {
if target_patchs.len() == 0 || target_patchs.contains(&path) {
let seq_data = if parse_line {
let (ret_line, data) = lru_gen_lines_parse(&mut reader).map_err(|e| {
anyhow!(
"lru_gen_seq_lines_parse file {} failed: {}",
LRU_GEN_PATH,
e
)
})?;
line = ret_line;
clear_line = false;
data
} else {
HashMap::new()
};
/*trace!(
"lru_gen_file_parse path {} id {} seq_data {:#?}",
path,
id,
seq_data
);*/
ret_hash.insert(path.clone(), (id, seq_data));
}
}
if clear_line {
line.clear();
}
}
Ok(ret_hash)
}
fn file_parse(
target_patchs: &HashSet<String>,
parse_line: bool,
) -> Result<HashMap<String, (usize, HashMap<usize, MGenLRU>)>> {
let file = File::open(LRU_GEN_PATH)
.map_err(|e| anyhow!("open file {} failed: {}", LRU_GEN_PATH, e))?;
let mut reader = BufReader::new(file);
lru_gen_file_parse(&mut reader, target_patchs, parse_line)
}
//result:
// HashMap<path, (id, ino, HashMap<node_id, MGenLRU>)>
pub fn host_memcgs_get(
target_patchs: &HashSet<String>,
parse_line: bool,
) -> Result<HashMap<String, (usize, usize, HashMap<usize, MGenLRU>)>> {
let mgs = file_parse(target_patchs, parse_line)
.map_err(|e| anyhow!("mglru file_parse failed: {}", e))?;
let mut host_mgs = HashMap::new();
for (path, (id, mglru)) in mgs {
let host_path = PathBuf::from(MEMCGS_PATH).join(path.trim_start_matches('/'));
let metadata = match fs::metadata(host_path.clone()) {
Err(e) => {
if id != 0 {
debug!("fs::metadata {:?} fail: {}", host_path, e);
}
continue;
}
Ok(m) => m,
};
host_mgs.insert(path, (id, metadata.ino() as usize, mglru));
}
Ok(host_mgs)
}
pub fn check() -> Result<()> {
if crate::misc::is_test_environment() {
return Ok(());
}
let content = fs::read_to_string(LRU_GEN_ENABLED_PATH)
.map_err(|e| anyhow!("open file {} failed: {}", LRU_GEN_ENABLED_PATH, e))?;
let content = content.trim();
let r = if content.starts_with("0x") {
u32::from_str_radix(&content[2..], 16)
} else {
content.parse()
};
let enabled = r.map_err(|e| anyhow!("parse file {} failed: {}", LRU_GEN_ENABLED_PATH, e))?;
if enabled != 7 {
fs::write(LRU_GEN_ENABLED_PATH, "7")
.map_err(|e| anyhow!("write file {} failed: {}", LRU_GEN_ENABLED_PATH, e))?;
}
let _ = OpenOptions::new()
.read(true)
.write(true)
.open(LRU_GEN_PATH)
.map_err(|e| anyhow!("open file {} failed: {}", LRU_GEN_PATH, e))?;
Ok(())
}
pub fn run_aging(
memcg_id: usize,
numa_id: usize,
max_seq: u64,
can_swap: bool,
force_scan: bool,
) -> Result<()> {
let cmd = format!(
"+ {} {} {} {} {}",
memcg_id, numa_id, max_seq, can_swap as i32, force_scan as i32
);
//trace!("send cmd {} to {}", cmd, LRU_GEN_PATH);
fs::write(LRU_GEN_PATH, &cmd)
.map_err(|e| anyhow!("write file {} cmd {} failed: {}", LRU_GEN_PATH, cmd, e))?;
Ok(())
}
pub fn run_eviction(
memcg_id: usize,
numa_id: usize,
min_seq: u64,
swappiness: u8,
nr_to_reclaim: usize,
) -> Result<()> {
let cmd = format!(
"- {} {} {} {} {}",
memcg_id, numa_id, min_seq, swappiness, nr_to_reclaim
);
//trace!("send cmd {} to {}", cmd, LRU_GEN_PATH);
fs::write(LRU_GEN_PATH, &cmd)
.map_err(|e| anyhow!("write file {} cmd {} failed: {}", LRU_GEN_PATH, cmd, e))?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use maplit::hashmap;
use once_cell::sync::OnceCell;
use slog::{Drain, Level, Logger};
use slog_async;
use slog_scope::set_global_logger;
use slog_term;
use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::Write;
use std::sync::Mutex;
lazy_static::lazy_static! {
static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
static LOGGER: OnceCell<slog_scope::GlobalLoggerGuard> = OnceCell::new();
impl GenLRU {
pub fn new_from_data(seq: u64, anon: u64, file: u64, birth: DateTime<Utc>) -> Self {
Self {
seq,
anon,
file,
birth,
}
}
}
pub fn init_logger() -> &'static slog_scope::GlobalLoggerGuard {
LOGGER.get_or_init(|| {
let decorator = slog_term::TermDecorator::new().stderr().build();
let drain = slog_term::CompactFormat::new(decorator)
.build()
.filter_level(Level::Trace)
.fuse();
let drain = slog_async::Async::new(drain).build().fuse();
let logger = Logger::root(drain, slog::o!());
set_global_logger(logger.clone())
})
}
#[test]
fn test_lru_gen_file_parse_single_no_parse() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
let _logger = init_logger();
let mut reader = setup_test_file();
let paths = ["/justto.slice/boot.mount".to_string()]
.iter()
.cloned()
.collect();
let ret = lru_gen_file_parse(&mut reader, &paths, false).unwrap();
assert_eq!(ret.len(), 1);
assert_eq!(
ret.get("/justto.slice/boot.mount"),
Some(&(16, HashMap::new()))
);
remove_test_file();
}
#[test]
fn test_lru_gen_file_parse_multi_no_parse() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
let _logger = init_logger();
let mut reader = setup_test_file();
let paths = [
"/aabbc/tea-loglogl".to_string(),
"/aabbc/staraabbc".to_string(),
"/aabbc/TEAE-iaabbc".to_string(),
"/justto.slice/cpupower.service".to_string(),
]
.iter()
.cloned()
.collect();
let ret = lru_gen_file_parse(&mut reader, &paths, false).unwrap();
assert_eq!(ret.len(), 4);
assert_eq!(ret.get("/justto.slice/boot.mount"), None);
assert_eq!(ret.get("/aabbc/tea-loglogl"), Some(&(30, hashmap![])));
assert_eq!(ret.get("/aabbc/staraabbc"), Some(&(22, hashmap![])));
assert_eq!(ret.get("/aabbc/TEAE-iaabbc"), Some(&(21, hashmap![])));
assert_eq!(
ret.get("/justto.slice/cpupower.service"),
Some(&(0, hashmap![]))
);
remove_test_file();
}
#[test]
fn test_lru_gen_file_parse_multi_parse() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
let _logger = init_logger();
let mut reader = setup_test_file();
let paths = [
"/aabbc/tea-loglogl".to_string(),
"/aabbc/staraabbc".to_string(),
"/aabbc/TEAE-iaabbc".to_string(),
"/justto.slice/cpupower.service".to_string(),
]
.iter()
.cloned()
.collect();
let ret = lru_gen_file_parse(&mut reader, &paths, true).unwrap();
assert_eq!(ret.len(), 4);
assert_eq!(ret.get("/justto.slice/boot.mount"), None);
let birth_vec: Vec<DateTime<Utc>> = ret["/aabbc/tea-loglogl"].1[&0]
.lru
.iter()
.map(|g| g.birth)
.collect();
assert_eq!(
ret.get("/aabbc/tea-loglogl"),
Some(&(
30,
hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0,
lru: vec![GenLRU::new_from_data(0, 20, 23, birth_vec[0]),
GenLRU::new_from_data(1, 9, 23, birth_vec[1]),
GenLRU::new_from_data(2, 20, 19, birth_vec[2]),
GenLRU::new_from_data(3, 3, 8, birth_vec[3])]}]
))
);
let birth_vec: Vec<DateTime<Utc>> = ret["/aabbc/staraabbc"].1[&1]
.lru
.iter()
.map(|g| g.birth)
.collect();
assert_eq!(
ret.get("/aabbc/staraabbc"),
Some(&(
22,
hashmap![1 => MGenLRU{min_seq: 2, max_seq: 5, last_birth: birth_vec[3], min_lru_index: 0,
lru: vec![GenLRU::new_from_data(2, 0, 86201, birth_vec[0]),
GenLRU::new_from_data(3, 253, 0, birth_vec[1]),
GenLRU::new_from_data(4, 0, 0, birth_vec[2]),
GenLRU::new_from_data(5, 2976, 41252, birth_vec[3])]}]
))
);
let birth_vec: Vec<DateTime<Utc>> = ret["/aabbc/TEAE-iaabbc"].1[&0]
.lru
.iter()
.map(|g| g.birth)
.collect();
let birth1_vec: Vec<DateTime<Utc>> = ret["/aabbc/TEAE-iaabbc"].1[&1]
.lru
.iter()
.map(|g| g.birth)
.collect();
assert_eq!(
ret.get("/aabbc/TEAE-iaabbc"),
Some(&(
21,
hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0, lru: vec![GenLRU::new_from_data(0, 0, 1, birth_vec[0]),
GenLRU::new_from_data(1, 2, 3, birth_vec[1]),
GenLRU::new_from_data(2, 6, 7, birth_vec[2]),
GenLRU::new_from_data(3, 8, 9, birth_vec[3])]},
1 => MGenLRU{min_seq: 3, max_seq: 6, last_birth: birth1_vec[3], min_lru_index: 0, lru: vec![GenLRU::new_from_data(3, 10, 11, birth1_vec[0]),
GenLRU::new_from_data(4, 12, 16, birth1_vec[1]),
GenLRU::new_from_data(5, 17, 18, birth1_vec[2]),
GenLRU::new_from_data(6, 19, 20, birth1_vec[3])]}]
))
);
let birth_vec: Vec<DateTime<Utc>> = ret["/justto.slice/cpupower.service"].1[&0]
.lru
.iter()
.map(|g| g.birth)
.collect();
assert_eq!(
ret.get("/justto.slice/cpupower.service"),
Some(&(
0,
hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0, lru: vec![GenLRU::new_from_data(0, 0, 33, birth_vec[0]),
GenLRU::new_from_data(1, 0, 0, birth_vec[1]),
GenLRU::new_from_data(2, 0, 0, birth_vec[2]),
GenLRU::new_from_data(3, 0, 115, birth_vec[3])]}]
))
);
remove_test_file();
}
#[test]
fn test_lru_gen_file_parse_no_target_no_parse() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
let _logger = init_logger();
let mut reader = setup_test_file();
let paths = [].iter().cloned().collect();
let ret = lru_gen_file_parse(&mut reader, &paths, false).unwrap();
assert_eq!(ret.len(), 55);
assert_eq!(ret.get("/justto.slice/boot.mount"), Some(&(16, hashmap![])));
assert_eq!(ret.get("/aabbc/tea-loglogl"), Some(&(30, hashmap![])));
assert_eq!(ret.get("/aabbc/staraabbc"), Some(&(22, hashmap![])));
assert_eq!(ret.get("/aabbc/TEAE-iaabbc"), Some(&(21, hashmap![])));
assert_eq!(
ret.get("/justto.slice/cpupower.service"),
Some(&(0, hashmap![]))
);
remove_test_file();
}
#[test]
fn test_lru_gen_file_parse_no_target_parse() {
let _lock = TEST_MUTEX.lock().unwrap_or_else(|e| e.into_inner());
let _logger = init_logger();
let mut reader = setup_test_file();
let paths = [].iter().cloned().collect();
let ret = lru_gen_file_parse(&mut reader, &paths, true).unwrap();
assert_eq!(ret.len(), 55);
let birth_vec: Vec<DateTime<Utc>> = ret["/aabbc/tea-loglogl"].1[&0]
.lru
.iter()
.map(|g| g.birth)
.collect();
assert_eq!(
ret.get("/aabbc/tea-loglogl"),
Some(&(
30,
hashmap![0 => MGenLRU{min_seq: 0, max_seq: 3, last_birth: birth_vec[3], min_lru_index: 0,
lru: vec![GenLRU::new_from_data(0, 20, 23, birth_vec[0]),
GenLRU::new_from_data(1, 9, 23, birth_vec[1]),
GenLRU::new_from_data(2, 20, 19, birth_vec[2]),
GenLRU::new_from_data(3, 3, 8, birth_vec[3])]}]
))
);
let birth_vec: Vec<DateTime<Utc>> = ret["/aabbc/staraabbc"].1[&1]
.lru
.iter()
.map(|g| g.birth)
.collect();
assert_eq!(
ret.get("/aabbc/staraabbc"),
Some(&(
22,
hashmap![1 => MGenLRU{min_seq: 2, max_seq: 5, last_birth: birth_vec[3], min_lru_index: 0,
lru: vec![GenLRU::new_from_data(2, 0, 86201, birth_vec[0]),
GenLRU::new_from_data(3, 253, 0, birth_vec[1]),
GenLRU::new_from_data(4, 0, 0, birth_vec[2]),
GenLRU::new_from_data(5, 2976, 41252, birth_vec[3])]}]
))
);
remove_test_file();
}
fn setup_test_file() -> BufReader<File> {
let data = r#"
memcg 1 /
node 0
0 589359037 0 -0
1 589359037 12 0
2 589359037 0 0
3 589359037 1265 2471
memcg 2 /justto.slice
node 0
0 589334424 0 0
1 589334424 0 0
2 589334424 0 0
3 589334424 0 0
memcg 3 /justto.slice/justtod-teawated.service
node 0
0 589334423 0 217
1 589334423 8 0
2 589334423 0 0
3 589334423 225 40293
memcg 0 /justto.slice/justtod-readahead-replay.service
node 0
0 589334411 0 266694
1 589334411 0 0
2 589334411 0 0
3 589334411 1 21
memcg 0 /justto.slice/tea0-domainname.service
node 0
0 589334410 0 6
1 589334410 0 0
2 589334410 0 0
3 589334410 0 198
memcg 6 /justto.slice/justto-serial\x2dgetty.slice
node 0
0 589334408 0 1
1 589334408 1 0
2 589334408 0 0
3 589334408 32 0
memcg 7 /justto.slice/justto-getty.slice
node 0
0 589334408 0 0
1 589334408 1 0
2 589334408 0 0
3 589334408 31 0
memcg 8 /justto.slice/sys-kernel-debug.mount
node 0
0 589334407 0 6
1 589334408 0 0
2 589334408 0 0
3 589334408 0 7
memcg 10 /justto.slice/dev-hugepages.mount
node 0
0 589334406 0 1
1 589334406 0 0
2 589334406 0 0
3 589334406 0 0
memcg 0 /justto.slice/justtod-readahead-collect.service
node 0
0 589334405 0 96
1 589334405 0 0
2 589334405 0 0
3 589334405 0 0
memcg 12 /justto.slice/justto-justtod\x2dfsck.slice
node 0
0 589334403 0 25
1 589334403 0 0
2 589334403 0 0
3 589334403 0 239
memcg 13 /justto.slice/justto-selinux\x2dpolicy\x2dmigrate\x2dlocal\x2dchanges.slice
node 0
0 589334403 0 0
1 589334403 0 0
2 589334403 0 0
3 589334403 0 0
memcg 14 /justto.slice/dev-mqueue.mount
node 0
0 589334402 0 0
1 589334402 0 0
2 589334402 0 0
3 589334402 0 0
memcg 0 /justto.slice/tea2-monitor.service
node 0
0 589334401 0 9
1 589334401 0 0
2 589334401 0 0
3 589334401 0 582
memcg 0 /justto.slice/kmod-static-nodes.service
node 0
0 589334399 0 4
1 589334399 1 0
2 589334399 0 0
3 589334399 0 33
memcg 0 /justto.slice/plymouth-start.service
node 0
0 589334397 0 1
1 589334397 0 0
2 589334397 0 0
3 589334397 0 0
memcg 18 /justto.slice/sys-kernel-config.mount
node 0
0 589334396 0 0
1 589334396 0 0
2 589334396 0 0
3 589334396 0 0
memcg 5 /justto.slice/tea2-teaetad.service
node 0
0 589334383 0 4
1 589334383 2 0
2 589334383 0 0
3 589334383 587 14
memcg 0 /justto.slice/justtod-remount-fs.service
node 0
0 589334381 0 4
1 589334381 0 0
2 589334381 0 0
3 589334381 0 11
memcg 0 /justto.slice/justtod-tmpfiles-setup-dev.service
node 0
0 589334380 0 28
1 589334380 0 0
2 589334380 0 0
3 589334380 0 32
memcg 0 /justto.slice/justtod-sysctl.service
node 0
0 589334378 0 42
1 589334378 0 0
2 589334378 0 0
3 589334378 0 13
memcg 0 /justto.slice/justtod-teawate-flush.service
node 0
0 589334367 0 8
1 589334367 0 0
2 589334367 0 0
3 589334367 0 141
memcg 0 /justto.slice/justtod-udev-trigger.service
node 0
0 589334365 0 5
1 589334365 0 0
2 589334365 0 0
3 589334365 0 103
memcg 0 /justto.slice/tea0-readonly.service
node 0
0 589334364 0 163
1 589334364 0 0
2 589334364 0 0
3 589334364 0 35
memcg 0 /justto.slice/justtod-random-seed.service
node 0
0 589334363 0 38
1 589334363 0 0
2 589334363 0 0
3 589334363 0 9
memcg 25 /justto.slice/justtod-udevd.service
node 0
0 589334362 0 12553
1 589334362 249 0
2 589334362 0 0
3 589334362 124 1415
memcg 15 /justto.slice/dev-disk-by\x2dlabel-SWAP.swap
node 0
0 589334085 0 5
1 589334085 0 0
2 589334085 0 0
3 589334085 0 10
memcg 16 /justto.slice/boot.mount
node 0
0 589334035 0 26
1 589334035 0 0
2 589334035 0 0
3 589334035 0 0
memcg 0 /justto.slice/plymouth-read-write.service
node 0
0 589334011 0 9
1 589334011 0 0
2 589334011 0 0
3 589334011 0 27
memcg 0 /justto.slice/tea0-import-state.service
node 0
0 589334008 0 5
1 589334008 0 0
2 589334008 0 0
3 589334008 0 45
memcg 0 /justto.slice/justtod-tmpfiles-setup.service
node 0
0 589333868 0 8
1 589333868 0 0
2 589333868 0 0
3 589333868 0 0
memcg 0 /justto.slice/justtod-update-utmp.service
node 0
0 589333772 0 5
1 589333772 1 0
2 589333772 0 0
3 589333772 0 74
memcg 0 /justto.slice/network.service
node 0
0 589333758 0 2480
1 589333758 0 0
2 589333758 0 0
3 589333758 0 542
memcg 0 /justto.slice/tea0-dmesg.service
node 0
0 589333757 0 35
1 589333757 0 0
2 589333757 0 0
3 589333757 0 37
memcg 0 /justto.slice/cpupower.service
node 0
0 589333755 0 33
1 589333755 0 0
2 589333755 0 0
3 589333755 0 115
memcg 0 /justto.slice/justtod-user-sessions.service
node 0
0 589333749 0 4
1 589333749 0 0
2 589333749 0 0
3 589333749 0 8
memcg 0 /justto.slice/sysstat.service
node 0
0 589333747 0 17
1 589333747 0 0
2 589333747 0 0
3 589333747 0 41
memcg 26 /justto.slice/mcelog.service
node 0
0 589333745 0 41
1 589333745 2 0
2 589333745 0 0
3 589333745 554 37
memcg 27 /justto.slice/dbus.service
node 0
0 589333743 0 82
1 589333743 1 0
2 589333743 0 0
3 589333743 119 216
memcg 20 /justto.slice/syslog-ng.service
node 0
0 589333722 0 5889
1 589333722 2 0
2 589333722 0 0
3 589333722 418 6488
memcg 0 /justto.slice/cpunoturbo.service
node 0
0 589333596 0 1
1 589333596 0 0
2 589333596 0 0
3 589333596 0 0
memcg 4 /justto.slice/staraabbcctl.service
node 0
0 589327556 0 7
1 589327556 2 0
2 589327556 0 0
3 589327556 69 11
memcg 19 /justto.slice/sshd.service
node 0
0 589327547 0 9670
1 589327547 11 0
2 589327547 0 0
3 589327547 2304 421
memcg 0 /justto.slice/vmcore-collect.service
node 0
0 589327544 0 3
1 589327544 0 0
2 589327544 0 0
3 589327544 0 0
memcg 0 /justto.slice/kdump.service
node 0
0 589327543 0 417259
1 589327543 2 0
2 589327543 0 0
3 589327543 0 0
memcg 31 /justto.slice/proc-sys-fs-binfmt_misc.mount
node 0
0 589311768 0 0
1 589311768 0 0
2 589311768 0 0
3 589311768 0 0
memcg 32 /justto.slice/ntpd.service
node 0
0 589297199 0 14
1 589297199 2 0
2 589297199 0 0
3 589297199 120 198
memcg 29 /justto.slice/crond.service
node 0
0 589297184 0 115157
1 589297184 2 0
2 589297184 0 0
3 589297184 195 324
memcg 0 /justto.slice/justtod-tmpfiles-clean.service
node 0
0 588459896 0 8
1 588459896 0 0
2 588459896 0 0
3 588459896 0 0
memcg 9 /docker.slice
node 0
0 589334407 0 13919
1 589334407 7 0
2 589334407 0 0
3 589334407 7254 146884
memcg 17 /aabbc
node 0
0 589327431 0 0
1 589327431 0 0
2 589327431 0 0
3 589327431 0 0
memcg 22 /aabbc/staraabbc
node 1
2 589327430 0 86201
3 589327430 253 0
4 589327430 0 0
5 589327430 2976 41252
memcg 21 /aabbc/TEAE-iaabbc
node 0
0 589324388 0 1
1 589324388 2 3
2 589324388 6 7
3 589324388 8 9
node 1
3 589324388 10 11
4 589324388 12 16
5 589324388 17 18
6 589324388 19 20
memcg 28 /aabbc/teawa_tea
node 0
0 589324387 0 69337
1 589324387 2 0
2 589324387 0 0
3 589324387 1892 6103
memcg 30 /aabbc/tea-loglogl
node 0
0 589324385 20 23
1 589324385 9 23
2 589324385 20 19
3 589324380 3 8
"#;
let mut file = File::create("test_lru_gen").unwrap();
file.write_all(data.as_bytes()).unwrap();
let file = File::open("test_lru_gen").unwrap();
BufReader::new(file)
}
fn remove_test_file() {
fs::remove_file("test_lru_gen").unwrap();
}
}

71
src/mem-agent/src/misc.rs Normal file
View File

@ -0,0 +1,71 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
pub fn sl() -> slog::Logger {
slog_scope::logger().new(slog::o!("subsystem" => "mem-agent"))
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*))
}
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*))
}
}
#[macro_export]
macro_rules! info {
($($arg:tt)*) => {
slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*))
}
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => {
slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*))
}
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => {
slog::info!(crate::misc::sl(), "{}", format_args!($($arg)*))
}
}
#[cfg(test)]
pub fn is_test_environment() -> bool {
true
}
#[cfg(not(test))]
pub fn is_test_environment() -> bool {
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_environment_check() {
assert!(is_test_environment());
}
#[test]
fn test_log_macro() {
error!("error");
warn!("warn");
info!("info");
trace!("trace");
debug!("debug");
}
}

44
src/mem-agent/src/proc.rs Normal file
View File

@ -0,0 +1,44 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use anyhow::{anyhow, Result};
use std::fs::File;
use std::io::{BufRead, BufReader};
fn get_meminfo(opt: &str) -> Result<u64> {
let file = File::open("/proc/meminfo")?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
if line.starts_with(opt) {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let kb = parts[1].parse::<u64>()?;
return Ok(kb);
}
}
}
Err(anyhow!("no {} found", opt))
}
pub fn get_memfree_kb() -> Result<u64> {
get_meminfo("MemFree:")
}
pub fn get_freeswap_kb() -> Result<u64> {
get_meminfo("SwapFree:")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_memfree_kb() {
let memfree_kb = get_memfree_kb().unwrap();
assert!(memfree_kb > 0);
}
}

260
src/mem-agent/src/psi.rs Normal file
View File

@ -0,0 +1,260 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use crate::info;
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use std::fs;
use std::fs::File;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
const CGROUP_PATH: &str = "/sys/fs/cgroup/";
const MEM_PSI: &str = "memory.pressure";
const IO_PSI: &str = "io.pressure";
fn find_psi_subdirs() -> Result<PathBuf> {
if PathBuf::from(CGROUP_PATH).is_dir() {
for entry in fs::read_dir(CGROUP_PATH)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
if path.join(MEM_PSI).is_file() && path.join(IO_PSI).is_file() {
return Ok(path.clone());
}
}
}
Err(anyhow!("cannot find cpuacct dir in {}", CGROUP_PATH))
} else {
Err(anyhow!("{} is not a directory", CGROUP_PATH))
}
}
pub fn check(psi_path: &PathBuf) -> Result<PathBuf> {
if crate::misc::is_test_environment() {
return Ok(psi_path.clone());
}
let p = if psi_path.as_os_str().is_empty() {
find_psi_subdirs().map_err(|e| anyhow!("find_psi_subdirs failed: {}", e))?
} else {
psi_path.clone()
};
let mem_psi_path = p.join(MEM_PSI);
let _ = OpenOptions::new()
.read(true)
.write(true)
.open(mem_psi_path.clone())
.map_err(|e| anyhow!("open file {:?} failed: {}", mem_psi_path, e))?;
info!("psi is available at {:?}", p);
Ok(p)
}
fn read_pressure_some_total(file_path: PathBuf) -> Result<u64> {
let file = File::open(file_path).map_err(|e| anyhow!("File::open failed: {}", e))?;
let mut reader = BufReader::new(file);
let mut first_line = String::new();
if reader
.read_line(&mut first_line)
.map_err(|e| anyhow!("reader.read_line failed: {}", e))?
<= 0
{
return Err(anyhow!("File is empty"));
}
let parts: Vec<&str> = first_line.split_whitespace().collect();
let total_str = parts.get(4).ok_or_else(|| anyhow!("format is not right"))?;
let val = total_str
.split('=')
.nth(1)
.ok_or_else(|| anyhow!("format is not right"))?;
let total_value = val
.parse::<u64>()
.map_err(|e| anyhow!("parse {} failed: {}", total_str, e))?;
Ok(total_value)
}
#[derive(Debug, Clone)]
pub struct Period {
path: PathBuf,
last_psi: u64,
last_update_time: DateTime<Utc>,
include_child: bool,
}
impl Period {
pub fn new(path: &PathBuf, include_child: bool) -> Self {
Self {
path: path.to_owned(),
last_psi: 0,
last_update_time: Utc::now(),
include_child,
}
}
fn get_path_pressure_us(&self, psi_name: &str) -> Result<u64> {
let cur_path = self.path.join(psi_name);
let mut parent_val = read_pressure_some_total(cur_path.clone())
.map_err(|e| anyhow!("read_pressure_some_total {:?} failed: {}", cur_path, e))?;
if !self.include_child {
let mut child_val = 0;
let entries = fs::read_dir(self.path.clone())
.map_err(|e| anyhow!("fs::read_dir failed: {}", e))?;
for entry in entries {
let entry = entry.map_err(|e| anyhow!("get path failed: {}", e))?;
let epath = entry.path();
if epath.is_dir() {
let full_path = self.path.join(entry.file_name()).join(psi_name);
child_val += read_pressure_some_total(full_path.clone()).map_err(|e| {
anyhow!("read_pressure_some_total {:?} failed: {}", full_path, e)
})?;
}
}
if parent_val < child_val {
parent_val = 0;
} else {
parent_val -= child_val;
}
}
Ok(parent_val)
}
pub fn get_percent(&mut self) -> Result<u64> {
let now = Utc::now();
let mut psi = self
.get_path_pressure_us(MEM_PSI)
.map_err(|e| anyhow!("get_path_pressure_us MEM_PSI {:?} failed: {}", self.path, e))?;
psi += self
.get_path_pressure_us(IO_PSI)
.map_err(|e| anyhow!("get_path_pressure_us IO_PSI {:?} failed: {}", self.path, e))?;
let mut percent = 0;
if self.last_psi != 0 && self.last_psi < psi && self.last_update_time < now {
let us = (now - self.last_update_time).num_milliseconds() as u64 * 1000;
if us != 0 {
percent = (psi - self.last_psi) * 100 / us;
}
}
self.last_psi = psi;
self.last_update_time = now;
Ok(percent)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
#[test]
fn test_read_pressure_some_total() {
remove_fake_file();
let val = read_pressure_some_total(PathBuf::from(setup_fake_file())).unwrap();
assert_eq!(val, 37820);
remove_fake_file();
}
#[test]
fn test_period() {
remove_fake_cgroup_dir();
let dir = setup_fake_cgroup_dir();
let period = Period::new(&dir, true);
let us = period.get_path_pressure_us(MEM_PSI).unwrap();
assert_eq!(us, 37820);
let us = period.get_path_pressure_us(IO_PSI).unwrap();
assert_eq!(us, 82345);
let period = Period::new(&dir, false);
let us = period.get_path_pressure_us(MEM_PSI).unwrap();
assert_eq!(us, 26688);
let us = period.get_path_pressure_us(IO_PSI).unwrap();
assert_eq!(us, 66879);
remove_fake_cgroup_dir();
}
fn write_fake_file(path: &PathBuf, data: &str) {
let mut file = File::create(path).unwrap();
file.write_all(data.as_bytes()).unwrap();
}
fn setup_fake_file() -> String {
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=37820
full avg10=0.00 avg60=0.00 avg300=0.00 total=28881
"#;
write_fake_file(&PathBuf::from("test_psi"), data);
"test_psi".to_string()
}
fn remove_fake_file() {
let _ = fs::remove_file("test_psi");
}
fn setup_fake_cgroup_dir() -> PathBuf {
let dir = PathBuf::from("fake_cgroup");
fs::create_dir(&dir).unwrap();
let mem_psi = dir.join(MEM_PSI);
let io_psi = dir.join(IO_PSI);
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=37820
full avg10=0.00 avg60=0.00 avg300=0.00 total=28881
"#;
write_fake_file(&mem_psi, data);
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=82345
full avg10=0.00 avg60=0.00 avg300=0.00 total=67890
"#;
write_fake_file(&io_psi, data);
let child_dir = dir.join("c1");
fs::create_dir(&child_dir).unwrap();
let child_mem_psi = child_dir.join(MEM_PSI);
let child_io_psi = child_dir.join(IO_PSI);
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=3344
full avg10=0.00 avg60=0.00 avg300=0.00 total=1234
"#;
write_fake_file(&child_mem_psi, data);
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=5566
full avg10=0.00 avg60=0.00 avg300=0.00 total=5678
"#;
write_fake_file(&child_io_psi, data);
let child_dir = dir.join("c2");
fs::create_dir(&child_dir).unwrap();
let child_mem_psi = child_dir.join(MEM_PSI);
let child_io_psi = child_dir.join(IO_PSI);
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=7788
full avg10=0.00 avg60=0.00 avg300=0.00 total=4321
"#;
write_fake_file(&child_mem_psi, data);
let data = r#"some avg10=0.00 avg60=0.00 avg300=0.00 total=9900
full avg10=0.00 avg60=0.00 avg300=0.00 total=8765
"#;
write_fake_file(&child_io_psi, data);
dir
}
fn remove_fake_cgroup_dir() {
let _ = fs::remove_dir_all("fake_cgroup");
}
}

View File

@ -0,0 +1,88 @@
// Copyright (C) 2024 Ant group. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
use chrono::Duration as ChronoDuration;
use chrono::{DateTime, Utc};
use tokio::time::Duration as TokioDuration;
fn chrono_to_tokio_duration(chrono_duration: ChronoDuration) -> TokioDuration {
if chrono_duration.num_nanoseconds().unwrap_or(0) >= 0 {
TokioDuration::new(
chrono_duration.num_seconds() as u64,
(chrono_duration.num_nanoseconds().unwrap_or(0) % 1_000_000_000) as u32,
)
} else {
TokioDuration::new(0, 0)
}
}
#[derive(Debug, Clone)]
pub struct Timeout {
sleep_duration: ChronoDuration,
start_wait_time: DateTime<Utc>,
}
impl Timeout {
pub fn new(secs: u64) -> Self {
Self {
sleep_duration: ChronoDuration::microseconds(secs as i64 * 1000000),
/* Make sure the first time to timeout */
start_wait_time: Utc::now() - ChronoDuration::microseconds(secs as i64 * 1000000) * 2,
}
}
pub fn is_timeout(&self) -> bool {
let now = Utc::now();
now >= self.start_wait_time + self.sleep_duration
}
pub fn reset(&mut self) {
self.start_wait_time = Utc::now();
}
pub fn remaining_tokio_duration(&self) -> TokioDuration {
let now = Utc::now();
if now >= self.start_wait_time + self.sleep_duration {
return TokioDuration::ZERO;
}
chrono_to_tokio_duration(self.start_wait_time + self.sleep_duration - now)
}
pub fn set_sleep_duration(&mut self, secs: u64) {
self.sleep_duration = ChronoDuration::microseconds(secs as i64 * 1000000);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
#[test]
fn test_timeout() {
let mut timeout = Timeout::new(1);
// timeout should be timeout at once.
assert_eq!(timeout.is_timeout(), true);
timeout.reset();
assert_eq!(timeout.is_timeout(), false);
thread::sleep(Duration::from_secs(2));
assert_eq!(timeout.is_timeout(), true);
timeout.set_sleep_duration(2);
timeout.reset();
assert_eq!(timeout.is_timeout(), false);
thread::sleep(Duration::from_secs(1));
assert_eq!(timeout.is_timeout(), false);
thread::sleep(Duration::from_secs(1));
assert_eq!(timeout.is_timeout(), true);
}
}

View File

@ -259,6 +259,102 @@ container_pipe_size=@PIPESIZE@
# (default: 3000)
#reconnect_timeout_ms = 3000
[agent.@PROJECT_TYPE@.mem_agent]
# Control the mem-agent function enable or disable.
# Default to false
#mem_agent_enable = true
# Control the mem-agent memcg function disable or enable
# Default to false
#memcg_disable = false
# Control the mem-agent function swap enable or disable.
# Default to false
#memcg_swap = false
# Control the mem-agent function swappiness max number.
# Default to 50
#memcg_swappiness_max = 50
# Control the mem-agent memcg function wait period seconds
# Default to 600
#memcg_period_secs = 600
# Control the mem-agent memcg wait period PSI percent limit.
# If the percentage of memory and IO PSI stall time within
# the memcg waiting period for a cgroup exceeds this value,
# then the aging and eviction for this cgroup will not be
# executed after this waiting period.
# Default to 1
#memcg_period_psi_percent_limit = 1
# Control the mem-agent memcg eviction PSI percent limit.
# If the percentage of memory and IO PSI stall time for a cgroup
# exceeds this value during an eviction cycle, the eviction for
# this cgroup will immediately stop and will not resume until
# the next memcg waiting period.
# Default to 1
#memcg_eviction_psi_percent_limit = 1
# Control the mem-agent memcg eviction run aging count min.
# A cgroup will only perform eviction when the number of aging cycles
# in memcg is greater than or equal to memcg_eviction_run_aging_count_min.
# Default to 3
#memcg_eviction_run_aging_count_min = 3
# Control the mem-agent compact function disable or enable
# Default to false
#compact_disable = false
# Control the mem-agent compaction function wait period seconds
# Default to 600
#compact_period_secs = 600
# Control the mem-agent compaction function wait period PSI percent limit.
# If the percentage of memory and IO PSI stall time within
# the compaction waiting period exceeds this value,
# then the compaction will not be executed after this waiting period.
# Default to 1
#compact_period_psi_percent_limit = 1
# Control the mem-agent compaction function compact PSI percent limit.
# During compaction, the percentage of memory and IO PSI stall time
# is checked every second. If this percentage exceeds
# compact_psi_percent_limit, the compaction process will stop.
# Default to 5
#compact_psi_percent_limit = 5
# Control the maximum number of seconds for each compaction of mem-agent compact function.
# Default to 180
#compact_sec_max = 180
# Control the mem-agent compaction function compact order.
# compact_order is use with compact_threshold.
# Default to 9
#compact_order = 9
# Control the mem-agent compaction function compact threshold.
# compact_threshold is the pages number.
# When examining the /proc/pagetypeinfo, if there's an increase in the
# number of movable pages of orders smaller than the compact_order
# compared to the amount following the previous compaction,
# and this increase surpasses a certain threshold—specifically,
# more than 'compact_threshold' number of pages.
# Or the number of free pages has decreased by 'compact_threshold'
# since the previous compaction.
# then the system should initiate another round of memory compaction.
# Default to 1024
#compact_threshold = 1024
# Control the mem-agent compaction function force compact times.
# After one compaction, if there has not been a compaction within
# the next compact_force_times times, a compaction will be forced
# regardless of the system's memory situation.
# If compact_force_times is set to 0, will do force compaction each time.
# If compact_force_times is set to 18446744073709551615, will never do force compaction.
# Default to 18446744073709551615
#compact_force_times = 18446744073709551615
[runtime]
# If enabled, the runtime will log additional debug messages to the
# system log

View File

@ -293,6 +293,16 @@ static AGENT_CMDS: &[AgentCmd] = &[
st: ServiceType::Agent,
fp: agent_cmd_sandbox_set_policy,
},
AgentCmd {
name: "MemAgentMemcgSet",
st: ServiceType::Agent,
fp: agent_cmd_mem_agent_memcg_set,
},
AgentCmd {
name: "MemAgentCompactSet",
st: ServiceType::Agent,
fp: agent_cmd_mem_agent_compact_set,
},
];
static BUILTIN_CMDS: & [BuiltinCmd] = &[
@ -2126,3 +2136,50 @@ fn agent_cmd_sandbox_set_policy(
Ok(())
}
fn agent_cmd_mem_agent_memcg_set(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
args: &str,
) -> Result<()> {
//let req = MemAgentMemcgConfig::default();
let req: MemAgentMemcgConfig = utils::make_request(args)?;
let ctx = clone_context(ctx);
info!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.mem_agent_memcg_set(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_mem_agent_compact_set(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
args: &str,
) -> Result<()> {
let req: MemAgentCompactConfig = utils::make_request(args)?;
let ctx = clone_context(ctx);
info!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.mem_agent_compact_set(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}

View File

@ -472,6 +472,7 @@ static_check_license_headers()
--exclude="tools/packaging/qemu/default-configs/*" \
--exclude="src/libs/protocols/protos/gogo/*.proto" \
--exclude="src/libs/protocols/protos/google/*.proto" \
--exclude="src/mem-agent/example/protocols/protos/google/protobuf/*.proto" \
--exclude="src/libs/*/test/texture/*" \
--exclude="*.dic" \
-EL $extra_args "\<${pattern}\>" \

View File

@ -0,0 +1,4 @@
CONFIG_LRU_GEN=y
CONFIG_DEBUG_FS=y
CONFIG_PSI=y
CONFIG_PSI_DEFAULT_DISABLED=y

View File

@ -1 +1 @@
140
141