1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4
5#[allow(dead_code)]
6mod c_headers;
7#[cfg(feature = "format")]
8pub mod format;
9mod model;
10pub(crate) mod netlink;
11pub use model::*;
12
13pub use c_headers::taskstats;
14use c_headers::{
15 __u16, __u32, __u64, __u8, TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK, TASKSTATS_CMD_ATTR_PID,
16 TASKSTATS_CMD_ATTR_REGISTER_CPUMASK, TASKSTATS_CMD_ATTR_TGID, TASKSTATS_CMD_GET,
17 TASKSTATS_GENL_NAME, TASKSTATS_TYPE_AGGR_PID, TASKSTATS_TYPE_AGGR_TGID, TASKSTATS_TYPE_NULL,
18 TASKSTATS_TYPE_PID, TASKSTATS_TYPE_STATS, TASKSTATS_TYPE_TGID,
19};
20use log::{debug, warn};
21use netlink::Netlink;
22use netlink::NlPayload;
23use std::{mem, slice};
24use thiserror::Error;
25
26#[derive(Debug, Error)]
28pub enum Error {
29 #[error("error in netlink communication with kernel: {0}")]
31 Netlink(#[from] netlink::Error),
32 #[error("no family id corresponding to taskstats found")]
34 NoFamilyId,
35 #[error("unknown error: {0}")]
37 Unknown(String),
38}
39
40pub type Result<T> = std::result::Result<T, Error>;
41
42pub struct Client {
44 netlink: Netlink,
45 ts_family_id: u16,
46}
47
48impl Client {
49 pub fn open() -> Result<Self> {
55 let netlink = Netlink::open()?;
56 let ts_family_id = Self::lookup_family_id(&netlink)?;
57 debug!("Found taskstats family id: {}", ts_family_id);
58 Ok(Self {
59 netlink,
60 ts_family_id,
61 })
62 }
63
64 fn lookup_family_id(netlink: &Netlink) -> Result<u16> {
65 netlink.send_cmd(
66 libc::GENL_ID_CTRL as u16,
67 libc::CTRL_CMD_GETFAMILY as u8,
68 libc::CTRL_ATTR_FAMILY_NAME as u16,
69 TASKSTATS_GENL_NAME,
70 )?;
71
72 let resp = netlink.recv_response()?;
73 for na in resp.payload_as_nlattrs() {
74 debug!("Family lookup: got nla_type: {}", na.header.nla_type);
75 if na.header.nla_type == libc::CTRL_ATTR_FAMILY_ID as u16 {
76 return Ok(*na.payload_as());
77 }
78 }
79 Err(Error::NoFamilyId)
80 }
81
82 pub fn pid_stats(&self, tid: u32) -> Result<TaskStats> {
96 self.send(TASKSTATS_CMD_ATTR_PID as u16, tid.as_buf())?;
97
98 let resp = self.netlink.recv_response()?;
99 for na in resp.payload_as_nlattrs() {
100 match na.header.nla_type as u32 {
101 TASKSTATS_TYPE_NULL => break,
102 TASKSTATS_TYPE_AGGR_PID => {
103 for inner in na.payload_as_nlattrs() {
104 match inner.header.nla_type as u32 {
105 TASKSTATS_TYPE_PID => debug!("Received TASKSTATS_TYPE_PID"),
106 TASKSTATS_TYPE_TGID => warn!("Received TASKSTATS_TYPE_TGID"),
107 TASKSTATS_TYPE_STATS => {
108 return Ok(TaskStats::from(inner.payload()));
109 }
110 unknown => warn!("Skipping unknown nla_type: {}", unknown),
111 }
112 }
113 }
114 unknown => warn!("Skipping unknown nla_type: {}", unknown),
115 }
116 }
117 Err(Error::Unknown(
118 "no TASKSTATS_TYPE_STATS found in response".to_string(),
119 ))
120 }
121
122 pub fn tgid_stats(&self, tgid: u32) -> Result<TaskStats> {
136 self.send(TASKSTATS_CMD_ATTR_TGID as u16, tgid.as_buf())?;
137
138 let resp = self.netlink.recv_response()?;
139 for na in resp.payload_as_nlattrs() {
140 match na.header.nla_type as u32 {
141 TASKSTATS_TYPE_NULL => break,
142 TASKSTATS_TYPE_AGGR_TGID => {
143 for inner in na.payload_as_nlattrs() {
144 match inner.header.nla_type as u32 {
145 TASKSTATS_TYPE_PID => warn!("Received TASKSTATS_TYPE_PID"),
146 TASKSTATS_TYPE_TGID => debug!("Received TASKSTATS_TYPE_TGID"),
147 TASKSTATS_TYPE_STATS => {
148 return Ok(TaskStats::from(inner.payload()));
149 }
150 unknown => warn!("Skipping unknown nla_type: {}", unknown),
151 }
152 }
153 }
154 unknown => warn!("Skipping unknown nla_type: {}", unknown),
155 }
156 }
157 Err(Error::Unknown(
158 "no TASKSTATS_TYPE_STATS found in response".to_string(),
159 ))
160 }
161
162 pub fn register_cpumask(&self, cpu_mask: &str) -> Result<()> {
168 self.send(
169 TASKSTATS_CMD_ATTR_REGISTER_CPUMASK as u16,
170 cpu_mask.as_bytes(),
171 )?;
172 Ok(())
173 }
174
175 pub fn deregister_cpumask(&self, cpu_mask: &str) -> Result<()> {
184 self.send(
185 TASKSTATS_CMD_ATTR_DEREGISTER_CPUMASK as u16,
186 cpu_mask.as_bytes(),
187 )?;
188 Ok(())
189 }
190
191 pub fn listen_registered(&self) -> Result<Vec<TaskStats>> {
202 let resp = self.netlink.recv_response()?;
203 let mut stats_vec = Vec::new();
204
205 for na in resp.payload_as_nlattrs() {
206 match na.header.nla_type as u32 {
207 TASKSTATS_TYPE_NULL => break,
208 TASKSTATS_TYPE_AGGR_PID | TASKSTATS_TYPE_AGGR_TGID => {
209 for inner in na.payload_as_nlattrs() {
210 match inner.header.nla_type as u32 {
211 TASKSTATS_TYPE_PID => debug!("Received TASKSTATS_TYPE_PID"),
212 TASKSTATS_TYPE_TGID => debug!("Received TASKSTATS_TYPE_TGID"),
213 TASKSTATS_TYPE_STATS => {
214 stats_vec.push(TaskStats::from(inner.payload()));
215 }
216 unknown => println!("Skipping unknown nla_type: {}", unknown),
217 }
218 }
219 }
220 unknown => println!("Skipping unknown nla_type: {}", unknown),
221 }
222 }
223 if !stats_vec.is_empty() {
224 return Ok(stats_vec);
225 }
226 Err(Error::Unknown(
227 "no TASKSTATS_TYPE_STATS found in response".to_string(),
228 ))
229 }
230
231 pub fn set_rx_buf_sz<T>(&self, payload: T) -> Result<()> {
239 self.netlink
240 .set_rx_buf_sz(payload)
241 .map_err(|err| err.into())
242 }
243
244 pub fn get_rx_buf_sz(&self) -> Result<usize> {
250 self.netlink.get_rx_buf_sz().map_err(|err| err.into())
251 }
252
253 pub fn send(&self, taskstats_cmd: u16, data: &[u8]) -> Result<()> {
254 self.netlink.send_cmd(
255 self.ts_family_id,
256 TASKSTATS_CMD_GET as u8,
257 taskstats_cmd,
258 data,
259 )?;
260 Ok(())
261 }
262}
263
264trait AsBuf<T> {
265 fn as_buf(&self) -> &[u8];
266
267 fn as_buf_mut(&mut self) -> &mut [u8];
268}
269
270impl<T> AsBuf<T> for T {
271 #[inline]
272 fn as_buf(&self) -> &[u8] {
273 unsafe { slice::from_raw_parts(self as *const T as *const u8, mem::size_of::<T>()) }
274 }
275
276 #[inline]
277 fn as_buf_mut(&mut self) -> &mut [u8] {
278 unsafe { slice::from_raw_parts_mut(self as *mut T as *mut u8, mem::size_of::<T>()) }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[cfg(test_priv)]
287 #[test]
288 fn test_pid_stats() {
289 let client = Client::open().unwrap();
290 let ts = client.pid_stats(std::process::id()).unwrap();
291
292 assert!(ts.delays.cpu.delay_total.as_nanos() > 0);
294 assert!(ts.cpu.virtual_time_total.as_nanos() > 0);
295 }
296
297 #[cfg(test_priv)]
298 #[test]
299 fn test_tgid_stats() {
300 let client = Client::open().unwrap();
301 let ts = client.tgid_stats(std::process::id()).unwrap();
302
303 assert!(ts.delays.cpu.delay_total.as_nanos() > 0);
305 assert!(ts.cpu.virtual_time_total.as_nanos() > 0);
306 }
307}