Skip to content

Commit d80377a

Browse files
jdelliotfacebook-github-bot
authored andcommitted
Add caching of debug_diff_dirs computation
Summary: # Context: We are working to refactor the EdenFs CLI client library into a supported XFN consumable library to avoid code duplication and sub-optimal usage patterns. # This Diff: Watchman had some caching of frequently ran Sapling operations. This will be handly for clients that are more long-running (e.g. buck2 and meerkat in daemon mode). Here we are adding caching to debug_diff_dirs results. Reviewed By: lXXXw Differential Revision: D73067767 fbshipit-source-id: fe3aa4df15797c8e3e521315241e7e5255620846
1 parent 4c3294c commit d80377a

File tree

1 file changed

+91
-55
lines changed

1 file changed

+91
-55
lines changed

eden/fs/cli_rs/sapling-client/src/debug_diff_dirs.rs

Lines changed: 91 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
use std::path::Path;
99
use std::path::PathBuf;
1010
use std::process::Stdio;
11+
use std::sync::LazyLock;
12+
use std::sync::Mutex;
1113

1214
use async_process_traits::Child;
1315
use async_process_traits::Command;
1416
use async_process_traits::CommandSpawner;
1517
use async_process_traits::TokioCommandSpawner;
18+
use lru_cache::LruCache;
1619
use tokio::io::AsyncBufReadExt;
1720
use tokio::io::BufReader;
1821

@@ -23,12 +26,29 @@ use crate::utils::get_sapling_executable_path;
2326
use crate::utils::get_sapling_options;
2427
use crate::utils::process_one_status_line;
2528

29+
// NOTE: We might wish to cache Results here, but we would want to add a way to evict
30+
// Err entries from the cache based on some policy - e.g. a TTL in seconds.
31+
// For now, we just cache Ok entries.
32+
const DIFF_DIRS_LRU_CACHE_SIZE: usize = 32;
33+
static DIFF_DIRS_LRU_CACHE: LazyLock<Mutex<LruCache<GetDiffDirsParams, SaplingGetDiffDirsResult>>> =
34+
LazyLock::new(|| Mutex::new(LruCache::new(DIFF_DIRS_LRU_CACHE_SIZE)));
35+
2636
#[derive(Clone, Debug, PartialEq)]
2737
pub enum SaplingGetDiffDirsResult {
2838
Normal(Vec<(SaplingStatus, String)>),
2939
TooManyChanges,
3040
}
3141

42+
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
43+
struct GetDiffDirsParams {
44+
first: String,
45+
second: Option<String>,
46+
limit_results: usize,
47+
root: Option<PathBuf>,
48+
included_roots: Option<Vec<PathBuf>>,
49+
excluded_roots: Option<Vec<PathBuf>>,
50+
}
51+
3252
pub async fn get_diff_dirs_with_includes(
3353
first: &str,
3454
second: Option<&str>,
@@ -58,67 +78,84 @@ pub async fn get_diff_dirs(
5878
included_roots: &Option<Vec<PathBuf>>,
5979
excluded_roots: &Option<Vec<PathBuf>>,
6080
) -> Result<SaplingGetDiffDirsResult> {
61-
get_diff_dirs_impl(
62-
&TokioCommandSpawner,
63-
first,
64-
second,
81+
let params = GetDiffDirsParams {
82+
first: first.to_string(),
83+
second: second.map(|s| s.to_string()),
6584
limit_results,
66-
root,
67-
included_roots,
68-
excluded_roots,
69-
)
70-
.await
85+
root: root.clone(),
86+
included_roots: included_roots.clone(),
87+
excluded_roots: excluded_roots.clone(),
88+
};
89+
90+
get_diff_dirs_impl(&TokioCommandSpawner, params).await
7191
}
7292

7393
async fn get_diff_dirs_impl<Spawner>(
7494
spawner: &Spawner,
75-
first: &str,
76-
second: Option<&str>,
77-
limit_results: usize,
78-
root: &Option<PathBuf>,
79-
included_roots: &Option<Vec<PathBuf>>,
80-
excluded_roots: &Option<Vec<PathBuf>>,
95+
params: GetDiffDirsParams,
8196
) -> Result<SaplingGetDiffDirsResult>
8297
where
8398
Spawner: CommandSpawner,
8499
{
85-
let mut args = vec!["debugdiffdirs", "--rev", first];
86-
if let Some(second) = second {
87-
args.push("--rev");
88-
args.push(second);
100+
{
101+
let mut lru_cache = DIFF_DIRS_LRU_CACHE.lock().unwrap();
102+
let entry = lru_cache.get_mut(&params).cloned();
103+
if let Some(entry) = entry {
104+
return Ok(entry);
105+
}
89106
}
90107

91-
let root_path_arg: String;
92-
if let Some(root) = root {
93-
root_path_arg = format!("path:{}", root.display());
94-
args.push(&root_path_arg);
95-
};
108+
let result = {
109+
let mut args = vec!["debugdiffdirs", "--rev", &params.first];
110+
let second: String;
111+
if let Some(second_) = &params.second {
112+
second = second_.to_string();
113+
args.push("--rev");
114+
args.push(&second);
115+
}
96116

97-
let mut command = Spawner::Command::new(get_sapling_executable_path());
98-
command
99-
.envs(get_sapling_options())
100-
.args(args)
101-
.stdout(Stdio::piped());
102-
let mut child = spawner.spawn(&mut command)?;
103-
let stdout = child.stdout().take().ok_or_else(|| {
104-
SaplingError::Other("Failed to read stdout when invoking 'sl debugdiffdirs'.".to_string())
105-
})?;
106-
let reader = BufReader::new(stdout);
107-
108-
let mut status = vec![];
109-
let mut lines = reader.lines();
110-
while let Some(line) = lines.next_line().await? {
111-
if let Some(status_line) = process_one_status_line(&line)? {
112-
if is_path_included(&status_line.1, included_roots, excluded_roots) {
113-
if status.len() >= limit_results {
114-
return Ok(SaplingGetDiffDirsResult::TooManyChanges);
117+
let root_path_arg: String;
118+
if let Some(root) = &params.root {
119+
root_path_arg = format!("path:{}", root.display());
120+
args.push(&root_path_arg);
121+
};
122+
123+
let mut command = Spawner::Command::new(get_sapling_executable_path());
124+
command
125+
.envs(get_sapling_options())
126+
.args(args)
127+
.stdout(Stdio::piped());
128+
let mut child = spawner.spawn(&mut command)?;
129+
let stdout = child.stdout().take().ok_or_else(|| {
130+
SaplingError::Other(
131+
"Failed to read stdout when invoking 'sl debugdiffdirs'.".to_string(),
132+
)
133+
})?;
134+
let reader = BufReader::new(stdout);
135+
136+
let mut status = vec![];
137+
let mut lines = reader.lines();
138+
while let Some(line) = lines.next_line().await? {
139+
if let Some(status_line) = process_one_status_line(&line)? {
140+
if is_path_included(
141+
&status_line.1,
142+
&params.included_roots,
143+
&params.excluded_roots,
144+
) {
145+
if status.len() >= params.limit_results {
146+
return Ok(SaplingGetDiffDirsResult::TooManyChanges);
147+
}
148+
status.push(status_line);
115149
}
116-
status.push(status_line);
117150
}
118151
}
119-
}
120152

121-
Ok(SaplingGetDiffDirsResult::Normal(status))
153+
SaplingGetDiffDirsResult::Normal(status)
154+
};
155+
156+
let mut lru_cache = DIFF_DIRS_LRU_CACHE.lock().unwrap();
157+
lru_cache.insert(params, result.clone());
158+
Ok(result)
122159
}
123160

124161
fn is_path_included(
@@ -167,16 +204,15 @@ A fbcode/buck2/app/buck2_audit_server/src/perf
167204
get_sapling_executable_path(),
168205
Some((0, Some(output.as_bytes().to_vec()))),
169206
);
170-
let result = get_diff_dirs_impl(
171-
&spawner,
172-
FIRST_COMMIT_ID,
173-
Some(SECOND_COMMIT_ID),
174-
1000,
175-
&None,
176-
&None,
177-
&None,
178-
)
179-
.await?;
207+
let params = GetDiffDirsParams {
208+
first: FIRST_COMMIT_ID.to_string(),
209+
second: Some(SECOND_COMMIT_ID.to_string()),
210+
limit_results: 1000,
211+
root: None,
212+
included_roots: None,
213+
excluded_roots: None,
214+
};
215+
let result = get_diff_dirs_impl(&spawner, params).await?;
180216
let expected = SaplingGetDiffDirsResult::Normal(vec![
181217
(
182218
SaplingStatus::Modified,

0 commit comments

Comments
 (0)