88use std:: path:: Path ;
99use std:: path:: PathBuf ;
1010use std:: process:: Stdio ;
11+ use std:: sync:: LazyLock ;
12+ use std:: sync:: Mutex ;
1113
1214use async_process_traits:: Child ;
1315use async_process_traits:: Command ;
1416use async_process_traits:: CommandSpawner ;
1517use async_process_traits:: TokioCommandSpawner ;
18+ use lru_cache:: LruCache ;
1619use tokio:: io:: AsyncBufReadExt ;
1720use tokio:: io:: BufReader ;
1821
@@ -23,12 +26,29 @@ use crate::utils::get_sapling_executable_path;
2326use crate :: utils:: get_sapling_options;
2427use 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 ) ]
2737pub 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+
3252pub 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
7393async 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 >
8297where
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
124161fn 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