-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathobserver.rs
More file actions
174 lines (160 loc) · 5.65 KB
/
Copy pathobserver.rs
File metadata and controls
174 lines (160 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! Manage the target observer
//!
//! The interogation that lading does of the target sub-process is intentionally
//! limited to in-process concerns, for the most part. The 'inspector' does
//! allow for a sub-process to do out-of-band inspection of the target but
//! cannot incorporate whatever it's doing into the capture data that lading
//! produces. This observer, on Linux, looks up the target process in procfs and
//! writes out key details about memory and CPU consumption into the capture
//! data. On non-Linux systems the observer, if enabled, will emit a warning.
use std::io;
use crate::target::TargetPidReceiver;
use serde::Deserialize;
#[cfg(target_os = "linux")]
mod linux;
#[derive(thiserror::Error, Debug)]
/// Errors produced by [`Server`]
pub enum Error {
/// Wrapper for [`nix::errno::Errno`]
#[error("erno: {0}")]
Errno(#[from] nix::errno::Errno),
/// Wrapper for [`std::io::Error`]
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[cfg(target_os = "linux")]
/// Wrapper for [`linux::Error`]
#[error("Linux error: {0}")]
Linux(#[from] linux::Error),
}
#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(default, rename_all = "snake_case", deny_unknown_fields)]
/// Configuration for [`Server`]
pub struct Config {
/// Enable per-mapping memory metrics from `/proc/{pid}/smaps`.
/// Sampled every 10th tick when enabled.
pub enable_smaps: bool,
/// Enable aggregate memory metrics from `/proc/{pid}/smaps_rollup`.
pub enable_smaps_rollup: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
enable_smaps: true,
enable_smaps_rollup: true,
}
}
}
#[derive(Debug)]
/// The inspector sub-process server.
///
/// This struct manages a sub-process that can be used to do further examination
/// of the [`crate::target::Server`] by means of operating system facilities. The
/// sub-process is not created until [`Server::run`] is called. It is assumed
/// that only one instance of this struct will ever exist at a time, although
/// there are no protections for that.
pub struct Server {
#[allow(dead_code)] // unused on non-Linux targets
config: Config,
#[allow(dead_code)] // this field is unused when target_os is not "linux"
shutdown: lading_signal::Watcher,
}
impl Server {
/// Create a new [`Server`] instance
///
/// The observer `Server` is responsible for investigating the
/// [`crate::target::Server`] sub-process.
///
/// # Errors
///
/// Function will error if the path to the sub-process is not valid or if
/// the path is valid but is not to file executable by this program.
pub fn new(config: Config, shutdown: lading_signal::Watcher) -> Result<Self, Error> {
Ok(Self { config, shutdown })
}
/// Run this [`Server`] to completion
///
/// This function runs the user supplied program to its completion, or until
/// a shutdown signal is received. Child exit status does not currently
/// propagate. This is less than ideal.
///
/// Target server will use the `TargetPidReceiver` passed here to transmit
/// its PID. This PID is passed to the sub-process as the first argument.
///
/// # Errors
///
/// Function will return an error if the underlying program cannot be waited
/// on or will not shutdown when signaled to.
///
/// # Panics
///
/// None are known.
#[allow(
clippy::similar_names,
clippy::too_many_lines,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
#[expect(
clippy::expect_used,
reason = "the observer requires the target PID to begin sampling; a missing PID at this point indicates an unrecoverable orchestration failure"
)]
#[cfg(target_os = "linux")]
pub async fn run(
self,
mut pid_snd: TargetPidReceiver,
sample_period: std::time::Duration,
) -> Result<(), Error> {
use crate::observer::linux::Sampler;
let target_pid = pid_snd
.recv()
.await
.expect("target failed to transmit PID, catastrophic failure");
drop(pid_snd);
let target_pid = target_pid.expect("observer cannot be used in no-target mode");
let mut sample_delay = tokio::time::interval(sample_period);
let mut sampler = Sampler::new(
target_pid,
vec![(String::from("focus"), String::from("target"))],
self.config.enable_smaps,
self.config.enable_smaps_rollup,
)?;
let shutdown_wait = self.shutdown.recv();
tokio::pin!(shutdown_wait);
loop {
tokio::select! {
_ = sample_delay.tick() => {
sampler.sample().await?;
}
() = &mut shutdown_wait => {
tracing::info!("shutdown signal received");
return Ok(());
}
}
}
}
/// "Run" this [`Server`] to completion
///
/// On non-Linux systems, this function is a no-op that logs a warning
/// indicating observer capabilities are unavailable on these systems.
///
/// # Errors
///
/// None are known.
///
/// # Panics
///
/// None are known.
#[expect(
clippy::unused_async,
reason = "signature must match the Linux async fn run for cross-platform callers"
)]
#[cfg(not(target_os = "linux"))]
pub async fn run(
self,
_pid_snd: TargetPidReceiver,
_sample_period: std::time::Duration,
) -> Result<(), Error> {
tracing::warn!("observer unavailable on non-Linux system");
Ok(())
}
}