Skip to content

Commit c630c02

Browse files
committed
oxlog: Glob match zone names
`oxlog logs` currently requires a precise match for the zone name. This is inconvenient when attempting to search across multiple zones with similar names. For example, when searching all Crucible downstairs, or using Pilot to query Nexus logs on multiple sleds. Use the `glob` crate to take a pattern to match against the zone name for the `services` and `logs` subcommands, potentially allowing multiple zones to be returned. If more than one zone is matched, then the logs and services will be sorted globally. Zones with a large number of archived logs may take a second or more to sort, and when matching against multiple zones this becomes uncomfortably slow. To claw back some performance, use Rayon to sort in parallel. We limit the size of its thread pool to a maximum of eight threads to avoid soaking up all available threads on a sled. On smaller machines we fall back to the number of logical threads. Example usage: $ oxlog services 'oxz_crucible_[!p]*' # All non-pantry Crucible zones $ oxlog logs 'oxz_nexus_*' --current # The nexus zone
1 parent 267d3d8 commit c630c02

File tree

4 files changed

+79
-41
lines changed

4 files changed

+79
-41
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dev-tools/oxlog/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ anyhow.workspace = true
1212
camino.workspace = true
1313
chrono.workspace = true
1414
clap.workspace = true
15+
glob.workspace = true
1516
jiff.workspace = true
17+
rayon.workspace = true
1618
sigpipe.workspace = true
1719
uuid.workspace = true
1820
omicron-workspace-hack.workspace = true

dev-tools/oxlog/src/bin/oxlog.rs

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@
55
//! Tool for discovering oxide related logfiles on sleds
66
77
use clap::{ArgAction, Args, Parser, Subcommand};
8+
use glob::Pattern;
89
use jiff::civil::DateTime;
910
use jiff::tz::TimeZone;
1011
use jiff::{Span, Timestamp};
1112
use oxlog::{DateRange, Filter, LogFile, Zones};
1213
use std::collections::BTreeSet;
14+
use std::num::NonZeroUsize;
15+
use std::str::FromStr;
16+
17+
/// The number of threads to given to the Rayon thread pool.
18+
/// Use this limit to avoid consuming excessive CPU time on a sled.
19+
const MAX_THREADS: usize = 8;
1320

1421
#[derive(Debug, Parser)]
1522
#[command(version)]
@@ -25,8 +32,8 @@ enum Commands {
2532

2633
/// List logs for a given service
2734
Logs {
28-
/// The name of the zone
29-
zone: String,
35+
/// The glob pattern to match against zone names
36+
zone_glob: GlobPattern,
3037

3138
/// The name of the service to list logs for
3239
service: Option<String>,
@@ -49,15 +56,26 @@ enum Commands {
4956
after: Option<Timestamp>,
5057
},
5158

52-
/// List the names of all services in a zone, from the perspective of oxlog.
59+
/// List the names of all services in matching zones, from the perspective of oxlog.
5360
/// Use these names with `oxlog logs` to filter output to logs from a
5461
/// specific service.
5562
Services {
56-
/// The name of the zone
57-
zone: String,
63+
/// The glob pattern to match against zone names
64+
zone_glob: GlobPattern,
5865
},
5966
}
6067

68+
#[derive(Clone, Debug)]
69+
struct GlobPattern(Pattern);
70+
71+
impl FromStr for GlobPattern {
72+
type Err = glob::PatternError;
73+
74+
fn from_str(s: &str) -> Result<Self, Self::Err> {
75+
Pattern::new(s).map(GlobPattern)
76+
}
77+
}
78+
6179
#[derive(Args, Debug)]
6280
#[group(required = true, multiple = true)]
6381
struct FilterArgs {
@@ -109,6 +127,12 @@ fn parse_timestamp(
109127
fn main() -> Result<(), anyhow::Error> {
110128
sigpipe::reset();
111129

130+
let num_threads = std::thread::available_parallelism()
131+
.map(NonZeroUsize::get)
132+
.unwrap_or(MAX_THREADS)
133+
.min(MAX_THREADS);
134+
rayon::ThreadPoolBuilder::new().num_threads(num_threads).build_global()?;
135+
112136
let cli = Cli::parse();
113137

114138
match cli.command {
@@ -118,7 +142,14 @@ fn main() -> Result<(), anyhow::Error> {
118142
}
119143
Ok(())
120144
}
121-
Commands::Logs { zone, service, metadata, filter, before, after } => {
145+
Commands::Logs {
146+
zone_glob,
147+
service,
148+
metadata,
149+
filter,
150+
before,
151+
after,
152+
} => {
122153
let zones = Zones::load()?;
123154
let date_range = match (before, after) {
124155
(None, None) => None,
@@ -145,7 +176,7 @@ fn main() -> Result<(), anyhow::Error> {
145176
);
146177
};
147178

148-
let logs = zones.zone_logs(&zone, filter);
179+
let logs = zones.zone_logs(&zone_glob.0, filter);
149180
for (svc_name, svc_logs) in logs {
150181
if let Some(service) = &service {
151182
if svc_name != service.as_str() {
@@ -182,7 +213,7 @@ fn main() -> Result<(), anyhow::Error> {
182213
}
183214
Ok(())
184215
}
185-
Commands::Services { zone } => {
216+
Commands::Services { zone_glob } => {
186217
let zones = Zones::load()?;
187218

188219
// We want all logs that exist, anywhere, so we can find their
@@ -198,7 +229,7 @@ fn main() -> Result<(), anyhow::Error> {
198229
// Collect a unique set of services, based on the logs in the
199230
// specified zone
200231
let services: BTreeSet<String> =
201-
zones.zone_logs(&zone, filter).into_keys().collect();
232+
zones.zone_logs(&zone_glob.0, filter).into_keys().collect();
202233

203234
for svc in services {
204235
println!("{}", svc);

dev-tools/oxlog/src/lib.rs

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
99
use anyhow::Context;
1010
use camino::{Utf8DirEntry, Utf8Path, Utf8PathBuf};
11+
use glob::Pattern;
1112
use jiff::Timestamp;
13+
use rayon::prelude::*;
1214
use std::collections::BTreeMap;
1315
use std::io;
1416
use uuid::Uuid;
@@ -186,8 +188,8 @@ impl SvcLogs {
186188
/// scattered across several different directories -- and we care more
187189
/// about filename than which directory they are in.
188190
pub fn sort_by_file_name(&mut self) {
189-
self.archived.sort_unstable_by(LogFile::file_name_cmp);
190-
self.extra.sort_unstable_by(LogFile::file_name_cmp);
191+
self.archived.par_sort_unstable_by(LogFile::file_name_cmp);
192+
self.extra.par_sort_unstable_by(LogFile::file_name_cmp);
191193
}
192194
}
193195

@@ -302,47 +304,48 @@ impl Zones {
302304
Ok(Zones { zones })
303305
}
304306

305-
/// Return log files organized by service name
307+
/// Return log files for all matching zone names organized by service name
306308
pub fn zone_logs(
307309
&self,
308-
zone: &str,
310+
zone_pattern: &Pattern,
309311
filter: Filter,
310312
) -> BTreeMap<ServiceName, SvcLogs> {
311313
let mut output = BTreeMap::new();
312-
let Some(paths) = self.zones.get(zone) else {
313-
return BTreeMap::new();
314-
};
315-
// Some rotated files exist in `paths.primary` that we track as
316-
// 'archived'. These files have not yet been migrated into the debug
317-
// directory.
318-
if filter.current || filter.archived {
319-
load_svc_logs(
320-
paths.primary.clone(),
321-
&mut output,
322-
filter.show_empty,
323-
filter.date_range,
324-
);
325-
}
326-
327-
if filter.archived {
328-
for dir in paths.debug.clone() {
314+
for (_, paths) in
315+
self.zones.iter().filter(|(zone, _)| zone_pattern.matches(zone))
316+
{
317+
// Some rotated files exist in `paths.primary` that we track as
318+
// 'archived'. These files have not yet been migrated into the debug
319+
// directory.
320+
if filter.current || filter.archived {
329321
load_svc_logs(
330-
dir,
322+
paths.primary.clone(),
331323
&mut output,
332324
filter.show_empty,
333325
filter.date_range,
334326
);
335327
}
336-
}
337-
if filter.extra {
338-
for (svc_name, dir) in paths.extra.clone() {
339-
load_extra_logs(
340-
dir,
341-
svc_name,
342-
&mut output,
343-
filter.show_empty,
344-
filter.date_range,
345-
);
328+
329+
if filter.archived {
330+
for dir in paths.debug.clone() {
331+
load_svc_logs(
332+
dir,
333+
&mut output,
334+
filter.show_empty,
335+
filter.date_range,
336+
);
337+
}
338+
}
339+
if filter.extra {
340+
for (svc_name, dir) in paths.extra.clone() {
341+
load_extra_logs(
342+
dir,
343+
svc_name,
344+
&mut output,
345+
filter.show_empty,
346+
filter.date_range,
347+
);
348+
}
346349
}
347350
}
348351

0 commit comments

Comments
 (0)