Skip to content

Commit eab458b

Browse files
authored
oxlog: Glob match zone names (#8008)
`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 significant amount of time to walk, and when matching against multiple zones this becomes uncomfortably slow. To claw back some performance, use Rayon to walk in parallel. We limit the size of its thread pool to a maximum of 12 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 653b58b commit eab458b

File tree

4 files changed

+90
-36
lines changed

4 files changed

+90
-36
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: 71 additions & 35 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+
/// The default thread-per-physical core is excessive on a Gimlet.
19+
const MAX_THREADS: usize = 12;
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,44 +176,46 @@ fn main() -> Result<(), anyhow::Error> {
145176
);
146177
};
147178

148-
let logs = zones.zone_logs(&zone, filter);
149-
for (svc_name, svc_logs) in logs {
150-
if let Some(service) = &service {
151-
if svc_name != service.as_str() {
152-
continue;
179+
let zones = zones.matching_zone_logs(&zone_glob.0, filter);
180+
for logs in zones {
181+
for (svc_name, svc_logs) in logs {
182+
if let Some(service) = &service {
183+
if svc_name != service.as_str() {
184+
continue;
185+
}
153186
}
154-
}
155-
if filter.current {
156-
if let Some(current) = &svc_logs.current {
157-
if metadata {
158-
print_metadata(current);
159-
} else {
160-
println!("{}", current.path);
187+
if filter.current {
188+
if let Some(current) = &svc_logs.current {
189+
if metadata {
190+
print_metadata(current);
191+
} else {
192+
println!("{}", current.path);
193+
}
161194
}
162195
}
163-
}
164-
if filter.archived {
165-
for f in &svc_logs.archived {
166-
if metadata {
167-
print_metadata(f);
168-
} else {
169-
println!("{}", f.path);
196+
if filter.archived {
197+
for f in &svc_logs.archived {
198+
if metadata {
199+
print_metadata(f);
200+
} else {
201+
println!("{}", f.path);
202+
}
170203
}
171204
}
172-
}
173-
if filter.extra {
174-
for f in &svc_logs.extra {
175-
if metadata {
176-
print_metadata(f);
177-
} else {
178-
println!("{}", f.path);
205+
if filter.extra {
206+
for f in &svc_logs.extra {
207+
if metadata {
208+
print_metadata(f);
209+
} else {
210+
println!("{}", f.path);
211+
}
179212
}
180213
}
181214
}
182215
}
183216
Ok(())
184217
}
185-
Commands::Services { zone } => {
218+
Commands::Services { zone_glob } => {
186219
let zones = Zones::load()?;
187220

188221
// We want all logs that exist, anywhere, so we can find their
@@ -197,8 +230,11 @@ fn main() -> Result<(), anyhow::Error> {
197230

198231
// Collect a unique set of services, based on the logs in the
199232
// specified zone
200-
let services: BTreeSet<String> =
201-
zones.zone_logs(&zone, filter).into_keys().collect();
233+
let services: BTreeSet<String> = zones
234+
.matching_zone_logs(&zone_glob.0, filter)
235+
.into_iter()
236+
.flat_map(|l| l.into_keys())
237+
.collect();
202238

203239
for svc in services {
204240
println!("{}", svc);

dev-tools/oxlog/src/lib.rs

Lines changed: 15 additions & 1 deletion
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;
@@ -347,9 +349,21 @@ impl Zones {
347349
}
348350

349351
sort_logs(&mut output);
350-
351352
output
352353
}
354+
355+
/// Return log files for all zones whose names match `zone_pattern`
356+
pub fn matching_zone_logs(
357+
&self,
358+
zone_pattern: &Pattern,
359+
filter: Filter,
360+
) -> Vec<BTreeMap<ServiceName, SvcLogs>> {
361+
self.zones
362+
.par_iter()
363+
.filter(|(zone, _)| zone_pattern.matches(zone))
364+
.map(|(zone, _)| self.zone_logs(zone, filter))
365+
.collect()
366+
}
353367
}
354368

355369
fn sort_logs(output: &mut BTreeMap<String, SvcLogs>) {

0 commit comments

Comments
 (0)