-
Notifications
You must be signed in to change notification settings - Fork 5
Add partial scan and inode map size #1580
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,6 +91,23 @@ impl Serialize for InodeMap { | |
| } | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum IntrospectionRequestType { | ||
| InodeMap, | ||
| InodeMapSize, | ||
| } | ||
|
|
||
| #[derive(Debug)] | ||
| pub enum IntrospectionResponseType { | ||
| InodeMap(serde_json::Result<String>), | ||
| InodeMapSize, | ||
| } | ||
|
|
||
| pub type IntrospectionRequest = ( | ||
| IntrospectionRequestType, | ||
| oneshot::Sender<IntrospectionResponseType>, | ||
| ); | ||
|
|
||
| pub struct HostScanner { | ||
| kernel_inode_map: RefCell<aya::maps::HashMap<MapData, inode_key_t, inode_value_t>>, | ||
| inode_map: RefCell<InodeMap>, | ||
|
|
@@ -100,7 +117,7 @@ pub struct HostScanner { | |
|
|
||
| rx: mpsc::Receiver<Event>, | ||
| tx: mpsc::Sender<Event>, | ||
| introspection: mpsc::Receiver<oneshot::Sender<serde_json::Result<String>>>, | ||
| introspection: mpsc::Receiver<IntrospectionRequest>, | ||
|
|
||
| metrics: HostScannerMetrics, | ||
|
|
||
|
|
@@ -115,7 +132,7 @@ impl HostScanner { | |
| paths: watch::Receiver<Vec<PathBuf>>, | ||
| scan_interval: watch::Receiver<Duration>, | ||
| metrics: HostScannerMetrics, | ||
| introspection: mpsc::Receiver<oneshot::Sender<serde_json::Result<String>>>, | ||
| introspection: mpsc::Receiver<IntrospectionRequest>, | ||
| ) -> anyhow::Result<(Self, mpsc::Receiver<Event>)> { | ||
| let kernel_inode_map = RefCell::new(bpf.take_inode_map()?); | ||
| let inode_map = RefCell::new(InodeMap::new()); | ||
|
|
@@ -274,6 +291,7 @@ impl HostScanner { | |
| /// base path (the path up to the first glob special character) that | ||
| /// matches the supplied path. | ||
| fn scan_partial(&self, path: &Path) -> anyhow::Result<()> { | ||
| let start = Instant::now(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Record duration when a partial scan fails.
Also applies to: 310-313 🤖 Prompt for AI Agents |
||
| let scan_prefix_patterns = | ||
| self.paths_patterns | ||
| .iter() | ||
|
|
@@ -296,6 +314,10 @@ impl HostScanner { | |
| for pattern in scan_set.iter().map(|index| &self.paths_patterns[*index]) { | ||
| self.scan_inner(pattern)?; | ||
| } | ||
|
|
||
| self.metrics | ||
| .scan_partial_duration | ||
| .observe(start.elapsed().as_secs_f64()); | ||
| Ok(()) | ||
| } | ||
|
|
||
|
|
@@ -658,12 +680,23 @@ You can increase this limit with: | |
| } | ||
| }, | ||
| req = self.introspection.recv() => { | ||
| let Some(req) = req else { | ||
| let Some((req_type, ch)) = req else { | ||
| continue; | ||
| }; | ||
|
Comment on lines
+683
to
685
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository guidance ---'
find /tmp/coderabbit-repo-knowledge/stackrox-fact-466257ef -type f -name '*.md' -maxdepth 3 -print \
-exec sh -c 'printf "\n### %s\n" "$1"; cat "$1"' _ {} \;
printf '%s\n' '--- host scanner target ---'
sed -n '620,725p' fact/src/host_scanner.rs
printf '%s\n' '--- introspection bindings and uses ---'
rg -n -C 3 'introspection|InodeMapSize|select!' fact/src/host_scanner.rsRepository: stackrox/fact Length of output: 14085 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- introspection channel creation and senders ---'
rg -n -C 5 'IntrospectionRequest|mpsc::channel|introspection:' --glob '*.rs' --glob 'Cargo.toml' .
printf '%s\n' '--- Tokio dependency declaration ---'
rg -n -C 3 'tokio' Cargo.toml '**/Cargo.toml' 2>/dev/null || true
printf '%s\n' '--- loop termination context ---'
sed -n '600,715p' fact/src/host_scanner.rsRepository: stackrox/fact Length of output: 21949 🌐 Web query:
💡 Result: When using tokio::sync::mpsc::Receiver::recv in a tokio::select! loop, returning None indicates that the channel is closed and all remaining buffered messages have been consumed [1][2][3]. It does not indicate that all senders have dropped if messages are still buffered in the channel [1][4]. If your select! branch appears to be ready repeatedly despite returning None, it is likely because the branch pattern (e.g., Some(msg) = rx.recv) is not matching the None return value [5][6]. In tokio::select!, when a branch's expression completes, the macro attempts to match the result against the provided pattern [5][7]. If the pattern does not match (e.g., matching Some(v) against None), the branch is disabled for that iteration of select! [5][7]. If you do not have an else branch or other active branches, this can lead to logic errors or panics [5][8]. To correctly handle channel closure in a select! loop: 1. Pattern Matching: Ensure your pattern handles the None case. You can use an if condition to disable the branch once the channel is closed, or use a pattern that matches None to break the loop or terminate the task [5][6]. Example: loop { tokio::select! { Some(msg) = rx.recv => { /* process message */ }, else => break, // Terminate when receiver returns None } } 2. Avoid Hot Loops: Do not repeatedly poll a closed channel in a loop without yielding or handling the termination [9]. If the receiver is constantly returning Ready(None), and your code continues to loop without stopping, it will consume excessive CPU [9]. 3. Documentation Clarification: Note that historically, some documentation suggested that recv returns None immediately when all senders drop [4]. This is incorrect; recv only returns None after all senders are dropped AND the channel is empty [1][3]. For more complex scenarios, use a flag or state variable to track if the channel has closed and use it as a precondition in the select! macro to disable the branch explicitly [6]. Citations:
Disable the introspection branch after the channel closes. When the channel is closed and empty, 🤖 Prompt for AI Agents |
||
|
|
||
| let resp = serde_json::to_string(&*self.inode_map.borrow()); | ||
| if let Err(e) = req.send(resp) { | ||
| use IntrospectionRequestType::*; | ||
| let resp = match req_type { | ||
| InodeMap => { | ||
| let resp = serde_json::to_string(&*self.inode_map.borrow()); | ||
| IntrospectionResponseType::InodeMap(resp) | ||
| } | ||
| InodeMapSize => { | ||
| let len = self.inode_map.borrow().len(); | ||
| self.metrics.inode_map_size.set(len as i64); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do I read it right that we allow to set this metric to a particular value? Why?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because the alternative is to track the size of the map any time a value is inserted or removed, so instead I chose to trigger an update by querying the size when we get a request in the Another alternative would be to set the value during a scan or processing an event, which might be more effort and won't necessarily align with the point in time a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, ok, then I read this code incorrectly, and thought that it happens elsewhere. |
||
| IntrospectionResponseType::InodeMapSize | ||
| } | ||
| }; | ||
| if let Err(e) = ch.send(resp) { | ||
| warn!("Failed to reply introspection query: {e:?}"); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: stackrox/fact
Length of output: 50369
🏁 Script executed:
Repository: stackrox/fact
Length of output: 32210
Keep
/metricsavailable in replay mode.Replay mode drops the
HostScannerintrospection receiver. The send inhandle_metricsthen fails, soGET /metricsreturns HTTP 500 withexpose_metrics=true. Make the inode-map refresh optional when no host scanner runs.🤖 Prompt for AI Agents