1#![deny(clippy::all)]
16#![allow(clippy::too_many_arguments)]
17
18use std::fs;
19use std::fs::File;
20use std::io;
21use std::io::Write;
22use std::path::PathBuf;
23use std::str::FromStr;
24use std::sync::mpsc::Receiver;
25use std::time::SystemTime;
26
27use anyhow::bail;
28use anyhow::Context;
29use anyhow::Error;
30use anyhow::Result;
31use common::cliutil;
32use common::util::get_belowrc_dump_section_key;
33use common::util::get_belowrc_filename;
34use common::util::timestamp_to_datetime;
35use model::Field;
36use model::FieldId;
37use model::Queriable;
38use serde_json::json;
39use serde_json::Value;
40use store::advance::new_advance_local;
41use store::advance::new_advance_remote;
42use store::Advance;
43use store::Direction;
44use tar::Archive;
45use tempfile::TempDir;
46use toml::value::Value as TValue;
47
48pub mod btrfs;
49pub mod cgroup;
50pub mod command;
51pub mod disk;
52pub mod ethtool;
53pub mod iface;
54pub mod network;
55pub mod print;
56pub mod process;
57pub mod system;
58pub mod tc;
59pub mod tmain;
60pub mod transport;
61
62#[cfg(test)]
63mod test;
64
65use command::expand_fields;
66pub use command::DumpCommand;
67use command::GeneralOpt;
68use command::OutputFormat;
69use render::HasRenderConfigForDump;
70use tmain::dump_timeseries;
71use tmain::Dumper;
72use tmain::IterExecResult;
73
74#[derive(
77 Clone,
78 Debug,
79 PartialEq,
80 below_derive::EnumFromStr,
81 below_derive::EnumToString,
82 enum_iterator::Sequence
83)]
84pub enum CommonField {
85 Timestamp,
86 Datetime,
87}
88
89pub struct CommonFieldContext {
91 pub timestamp: i64,
92 pub hostname: String,
93}
94
95impl CommonField {
96 pub fn get_field(&self, ctx: &CommonFieldContext) -> Option<Field> {
97 match self {
98 Self::Timestamp => Field::from(ctx.timestamp),
99 Self::Datetime => Field::from(timestamp_to_datetime(&ctx.timestamp)),
100 }
101 .into()
102 }
103}
104
105#[derive(Clone, Debug, PartialEq)]
109pub enum DumpField<F: FieldId> {
110 Common(CommonField),
111 FieldId(F),
112}
113
114pub type CgroupField = DumpField<model::SingleCgroupModelFieldId>;
115pub type ProcessField = DumpField<model::SingleProcessModelFieldId>;
116pub type SystemField = DumpField<model::SystemModelFieldId>;
117pub type DiskField = DumpField<model::SingleDiskModelFieldId>;
118pub type BtrfsField = DumpField<model::BtrfsModelFieldId>;
119pub type NetworkField = DumpField<model::NetworkModelFieldId>;
120pub type IfaceField = DumpField<model::SingleNetModelFieldId>;
121pub type TransportField = DumpField<model::NetworkModelFieldId>;
123pub type EthtoolQueueField = DumpField<model::SingleQueueModelFieldId>;
124pub type TcField = DumpField<model::SingleTcModelFieldId>;
125
126fn get_advance(
127 logger: slog::Logger,
128 dir: PathBuf,
129 host: Option<String>,
130 port: Option<u16>,
131 snapshot: Option<String>,
132 opts: &command::GeneralOpt,
133) -> Result<(SystemTime, SystemTime, Advance)> {
134 let (time_begin, time_end) = cliutil::system_time_range_from_date_and_adjuster(
135 opts.begin.as_str(),
136 opts.end.as_deref(),
137 opts.duration.as_deref(),
138 opts.yesterdays.as_deref(),
139 )?;
140
141 let mut advance = match (host, snapshot) {
142 (None, None) => new_advance_local(logger.clone(), dir, time_begin),
143 (Some(host), None) => new_advance_remote(logger.clone(), host, port, time_begin)?,
144 (None, Some(snapshot)) => {
145 let mut tarball =
146 Archive::new(fs::File::open(snapshot).context("Failed to open snapshot file")?);
147 let mut snapshot_dir = TempDir::with_prefix("snapshot_replay.")?.into_path();
148 tarball.unpack(&snapshot_dir)?;
149 for path in fs::read_dir(&snapshot_dir)? {
151 snapshot_dir.push(path.unwrap().file_name());
152 }
153 new_advance_local(logger.clone(), snapshot_dir, time_begin)
154 }
155 (Some(_), Some(_)) => {
156 bail!("--host and --snapshot are incompatible options")
157 }
158 };
159
160 advance.initialize();
161
162 Ok((time_begin, time_end, advance))
163}
164
165pub fn parse_pattern<T: FromStr>(
168 filename: String,
169 pattern_key: String,
170 section_key: &str,
171) -> Option<Vec<T>> {
172 let dump_map = match std::fs::read_to_string(filename) {
173 Ok(belowrc_str) => match belowrc_str.parse::<TValue>() {
174 Ok(belowrc_val) => belowrc_val
175 .get(get_belowrc_dump_section_key())
176 .unwrap_or_else(|| {
177 panic!(
178 "Failed to get section key: [{}.{}]",
179 get_belowrc_dump_section_key(),
180 section_key
181 )
182 })
183 .to_owned(),
184 Err(e) => panic!("Failed to parse belowrc file: {:#}", e),
185 },
186 Err(e) => panic!("Failed to parse belowrc file: {:#}", e),
187 };
188
189 Some(
190 dump_map
191 .get(section_key)
192 .unwrap_or_else(|| {
193 panic!(
194 "Failed to get section key: [{}.{}]",
195 get_belowrc_dump_section_key(),
196 section_key
197 )
198 })
199 .get(&pattern_key)
200 .unwrap_or_else(|| panic!("Failed to get pattern key: {}", pattern_key))
201 .as_array()
202 .unwrap_or_else(|| panic!("Failed to parse pattern {} value to array.", pattern_key))
203 .iter()
204 .map(|field| {
205 T::from_str(
206 field.as_str().unwrap_or_else(|| {
207 panic!("Failed to parse field key {} into string", field)
208 }),
209 )
210 .map_err(|_| format!("Failed to parse field key: {}", field))
211 .unwrap()
212 })
213 .collect(),
214 )
215}
216
217pub fn run(
218 logger: slog::Logger,
219 errs: Receiver<Error>,
220 dir: PathBuf,
221 host: Option<String>,
222 port: Option<u16>,
223 snapshot: Option<String>,
224 cmd: DumpCommand,
225) -> Result<()> {
226 let filename = get_belowrc_filename();
227
228 match cmd {
229 DumpCommand::System {
230 fields,
231 opts,
232 pattern,
233 } => {
234 let (time_begin, time_end, advance) =
235 get_advance(logger, dir, host, port, snapshot, &opts)?;
236 let default = opts.everything || opts.default;
237 let detail = opts.everything || opts.detail;
238 let fields = if let Some(pattern_key) = pattern {
239 parse_pattern(filename, pattern_key, "system")
240 } else {
241 fields
242 };
243 let fields = expand_fields(
244 match fields.as_ref() {
245 Some(fields) if !default => fields,
246 _ => command::DEFAULT_SYSTEM_FIELDS,
247 },
248 detail,
249 );
250 let system = system::System::new(&opts, fields);
251 let mut output: Box<dyn Write> = match opts.output.as_ref() {
252 Some(file_path) => Box::new(File::create(file_path)?),
253 None => Box::new(io::stdout()),
254 };
255 dump_timeseries(
256 advance,
257 time_begin,
258 time_end,
259 &system,
260 output.as_mut(),
261 opts.output_format,
262 opts.br,
263 errs,
264 )
265 }
266 DumpCommand::Disk {
267 fields,
268 opts,
269 select,
270 pattern,
271 } => {
272 let (time_begin, time_end, advance) =
273 get_advance(logger, dir, host, port, snapshot, &opts)?;
274 let default = opts.everything || opts.default;
275 let detail = opts.everything || opts.detail;
276 let fields = if let Some(pattern_key) = pattern {
277 parse_pattern(filename, pattern_key, "disk")
278 } else {
279 fields
280 };
281 let fields = expand_fields(
282 match fields.as_ref() {
283 Some(fields) if !default => fields,
284 _ => command::DEFAULT_DISK_FIELDS,
285 },
286 detail,
287 );
288 let disk = disk::Disk::new(&opts, select, fields);
289 let mut output: Box<dyn Write> = match opts.output.as_ref() {
290 Some(file_path) => Box::new(File::create(file_path)?),
291 None => Box::new(io::stdout()),
292 };
293 dump_timeseries(
294 advance,
295 time_begin,
296 time_end,
297 &disk,
298 output.as_mut(),
299 opts.output_format,
300 opts.br,
301 errs,
302 )
303 }
304 DumpCommand::Btrfs {
305 fields,
306 opts,
307 select,
308 pattern,
309 } => {
310 let (time_begin, time_end, advance) =
311 get_advance(logger, dir, host, port, snapshot, &opts)?;
312 let default = opts.everything || opts.default;
313 let detail = opts.everything || opts.detail;
314 let fields = if let Some(pattern_key) = pattern {
315 parse_pattern(filename, pattern_key, "btrfs")
316 } else {
317 fields
318 };
319 let fields = expand_fields(
320 match fields.as_ref() {
321 Some(fields) if !default => fields,
322 _ => command::DEFAULT_BTRFS_FIELDS,
323 },
324 detail,
325 );
326 let btrfs = btrfs::Btrfs::new(&opts, select, fields);
327 let mut output: Box<dyn Write> = match opts.output.as_ref() {
328 Some(file_path) => Box::new(File::create(file_path)?),
329 None => Box::new(io::stdout()),
330 };
331 dump_timeseries(
332 advance,
333 time_begin,
334 time_end,
335 &btrfs,
336 output.as_mut(),
337 opts.output_format,
338 opts.br,
339 errs,
340 )
341 }
342 DumpCommand::Process {
343 fields,
344 opts,
345 select,
346 pattern,
347 } => {
348 let (time_begin, time_end, advance) =
349 get_advance(logger, dir, host, port, snapshot, &opts)?;
350 let default = opts.everything || opts.default;
351 let detail = opts.everything || opts.detail;
352 let fields = if let Some(pattern_key) = pattern {
353 parse_pattern(filename, pattern_key, "process")
354 } else {
355 fields
356 };
357 let fields = expand_fields(
358 match fields.as_ref() {
359 Some(fields) if !default => fields,
360 _ => command::DEFAULT_PROCESS_FIELDS,
361 },
362 detail,
363 );
364 let process = process::Process::new(&opts, select, fields);
365 let mut output: Box<dyn Write> = match opts.output.as_ref() {
366 Some(file_path) => Box::new(File::create(file_path)?),
367 None => Box::new(io::stdout()),
368 };
369 dump_timeseries(
370 advance,
371 time_begin,
372 time_end,
373 &process,
374 output.as_mut(),
375 opts.output_format,
376 opts.br,
377 errs,
378 )
379 }
380 DumpCommand::Cgroup {
381 fields,
382 opts,
383 select,
384 pattern,
385 } => {
386 let (time_begin, time_end, advance) =
387 get_advance(logger, dir, host, port, snapshot, &opts)?;
388 let default = opts.everything || opts.default;
389 let detail = opts.everything || opts.detail;
390 let fields = if let Some(pattern_key) = pattern {
391 parse_pattern(filename, pattern_key, "cgroup")
392 } else {
393 fields
394 };
395 let fields = expand_fields(
396 match fields.as_ref() {
397 Some(fields) if !default => fields,
398 _ => command::DEFAULT_CGROUP_FIELDS,
399 },
400 detail,
401 );
402 let cgroup = cgroup::Cgroup::new(&opts, select, fields);
403 let mut output: Box<dyn Write> = match opts.output.as_ref() {
404 Some(file_path) => Box::new(File::create(file_path)?),
405 None => Box::new(io::stdout()),
406 };
407 dump_timeseries(
408 advance,
409 time_begin,
410 time_end,
411 &cgroup,
412 output.as_mut(),
413 opts.output_format,
414 opts.br,
415 errs,
416 )
417 }
418 DumpCommand::Iface {
419 fields,
420 opts,
421 select,
422 pattern,
423 } => {
424 let (time_begin, time_end, advance) =
425 get_advance(logger, dir, host, port, snapshot, &opts)?;
426 let default = opts.everything || opts.default;
427 let detail = opts.everything || opts.detail;
428 let fields = if let Some(pattern_key) = pattern {
429 parse_pattern(filename, pattern_key, "iface")
430 } else {
431 fields
432 };
433 let fields = expand_fields(
434 match fields.as_ref() {
435 Some(fields) if !default => fields,
436 _ => command::DEFAULT_IFACE_FIELDS,
437 },
438 detail,
439 );
440 let iface = iface::Iface::new(&opts, select, fields);
441 let mut output: Box<dyn Write> = match opts.output.as_ref() {
442 Some(file_path) => Box::new(File::create(file_path)?),
443 None => Box::new(io::stdout()),
444 };
445 dump_timeseries(
446 advance,
447 time_begin,
448 time_end,
449 &iface,
450 output.as_mut(),
451 opts.output_format,
452 opts.br,
453 errs,
454 )
455 }
456 DumpCommand::Network {
457 fields,
458 opts,
459 pattern,
460 } => {
461 let (time_begin, time_end, advance) =
462 get_advance(logger, dir, host, port, snapshot, &opts)?;
463 let default = opts.everything || opts.default;
464 let detail = opts.everything || opts.detail;
465 let fields = if let Some(pattern_key) = pattern {
466 parse_pattern(filename, pattern_key, "network")
467 } else {
468 fields
469 };
470 let fields = expand_fields(
471 match fields.as_ref() {
472 Some(fields) if !default => fields,
473 _ => command::DEFAULT_NETWORK_FIELDS,
474 },
475 detail,
476 );
477 let network = network::Network::new(&opts, fields);
478 let mut output: Box<dyn Write> = match opts.output.as_ref() {
479 Some(file_path) => Box::new(File::create(file_path)?),
480 None => Box::new(io::stdout()),
481 };
482 dump_timeseries(
483 advance,
484 time_begin,
485 time_end,
486 &network,
487 output.as_mut(),
488 opts.output_format,
489 opts.br,
490 errs,
491 )
492 }
493 DumpCommand::Transport {
494 fields,
495 opts,
496 pattern,
497 } => {
498 let (time_begin, time_end, advance) =
499 get_advance(logger, dir, host, port, snapshot, &opts)?;
500 let default = opts.everything || opts.default;
501 let detail = opts.everything || opts.detail;
502 let fields = if let Some(pattern_key) = pattern {
503 parse_pattern(filename, pattern_key, "transport")
504 } else {
505 fields
506 };
507 let fields = expand_fields(
508 match fields.as_ref() {
509 Some(fields) if !default => fields,
510 _ => command::DEFAULT_TRANSPORT_FIELDS,
511 },
512 detail,
513 );
514 let transport = transport::Transport::new(&opts, fields);
515 let mut output: Box<dyn Write> = match opts.output.as_ref() {
516 Some(file_path) => Box::new(File::create(file_path)?),
517 None => Box::new(io::stdout()),
518 };
519 dump_timeseries(
520 advance,
521 time_begin,
522 time_end,
523 &transport,
524 output.as_mut(),
525 opts.output_format,
526 opts.br,
527 errs,
528 )
529 }
530 DumpCommand::EthtoolQueue {
531 fields,
532 opts,
533 pattern,
534 } => {
535 let (time_begin, time_end, advance) =
536 get_advance(logger, dir, host, port, snapshot, &opts)?;
537 let default = opts.everything || opts.default;
538 let detail = opts.everything || opts.detail;
539 let fields = if let Some(pattern_key) = pattern {
540 parse_pattern(filename, pattern_key, "ethtool_queue")
541 } else {
542 fields
543 };
544 let fields = expand_fields(
545 match fields.as_ref() {
546 Some(fields) if !default => fields,
547 _ => command::DEFAULT_ETHTOOL_QUEUE_FIELDS,
548 },
549 detail,
550 );
551 let ethtool = ethtool::EthtoolQueue::new(&opts, fields);
552 let mut output: Box<dyn Write> = match opts.output.as_ref() {
553 Some(file_path) => Box::new(File::create(file_path)?),
554 None => Box::new(io::stdout()),
555 };
556 dump_timeseries(
557 advance,
558 time_begin,
559 time_end,
560 ðtool,
561 output.as_mut(),
562 opts.output_format,
563 opts.br,
564 errs,
565 )
566 }
567 DumpCommand::Tc {
568 fields,
569 opts,
570 pattern,
571 } => {
572 let (time_begin, time_end, advance) =
573 get_advance(logger, dir, host, port, snapshot, &opts)?;
574 let detail = opts.everything || opts.detail;
575 let fields = if let Some(pattern_key) = pattern {
576 parse_pattern(filename, pattern_key, "tc")
577 } else {
578 fields
579 };
580 let fields = expand_fields(
581 match fields.as_ref() {
582 Some(fields) => fields,
583 _ => command::DEFAULT_TC_FIELDS,
584 },
585 detail,
586 );
587 let tc = tc::Tc::new(&opts, fields);
588 let mut output: Box<dyn Write> = match opts.output.as_ref() {
589 Some(file_path) => Box::new(File::create(file_path)?),
590 None => Box::new(io::stdout()),
591 };
592 dump_timeseries(
593 advance,
594 time_begin,
595 time_end,
596 &tc,
597 output.as_mut(),
598 opts.output_format,
599 opts.br,
600 errs,
601 )
602 }
603 }
604}