@@ -38,7 +38,7 @@ use futures::{future, stream, Sink, SinkExt, Stream, StreamExt, TryFutureExt};
3838use number_prefix:: NumberPrefix ;
3939use serde:: { Deserialize , Serialize } ;
4040use std:: cell:: Cell ;
41- use std:: collections:: HashMap ;
41+ use std:: collections:: { HashMap , HashSet } ;
4242use std:: env;
4343use std:: ffi:: { OsStr , OsString } ;
4444use std:: future:: Future ;
@@ -1510,11 +1510,23 @@ impl Default for ServerStats {
15101510 }
15111511}
15121512
1513+ pub trait ServerStatsWriter {
1514+ fn write ( & mut self , text : & str ) ;
1515+ }
1516+
1517+ pub struct StdoutServerStatsWriter ;
1518+
1519+ impl ServerStatsWriter for StdoutServerStatsWriter {
1520+ fn write ( & mut self , text : & str ) {
1521+ println ! ( "{text}" ) ;
1522+ }
1523+ }
1524+
15131525impl ServerStats {
1514- /// Print stats to stdout in a human-readable format.
1526+ /// Print stats in a human-readable format.
15151527 ///
15161528 /// Return the formatted width of each of the (name, value) columns.
1517- fn print ( & self , advanced : bool ) -> ( usize , usize ) {
1529+ fn print < T : ServerStatsWriter > ( & self , writer : & mut T , advanced : bool ) -> ( usize , usize ) {
15181530 macro_rules! set_stat {
15191531 ( $vec: ident, $var: expr, $name: expr) => { {
15201532 // name, value, suffix length
@@ -1570,6 +1582,9 @@ impl ServerStats {
15701582 set_lang_stat ! ( stats_vec, self . cache_hits, "Cache hits" ) ;
15711583 set_lang_stat ! ( stats_vec, self . cache_misses, "Cache misses" ) ;
15721584 }
1585+
1586+ self . set_percentage_stats ( & mut stats_vec, advanced) ;
1587+
15731588 set_stat ! ( stats_vec, self . cache_timeouts, "Cache timeouts" ) ;
15741589 set_stat ! ( stats_vec, self . cache_read_errors, "Cache read errors" ) ;
15751590 set_stat ! ( stats_vec, self . forced_recaches, "Forced recaches" ) ;
@@ -1627,45 +1642,110 @@ impl ServerStats {
16271642 let name_width = stats_vec. iter ( ) . map ( |( n, _, _) | n. len ( ) ) . max ( ) . unwrap ( ) ;
16281643 let stat_width = stats_vec. iter ( ) . map ( |( _, s, _) | s. len ( ) ) . max ( ) . unwrap ( ) ;
16291644 for ( name, stat, suffix_len) in stats_vec {
1630- println ! (
1645+ writer . write ( & format ! (
16311646 "{:<name_width$} {:>stat_width$}" ,
16321647 name,
16331648 stat,
16341649 name_width = name_width,
16351650 stat_width = stat_width + suffix_len
1636- ) ;
1651+ ) ) ;
16371652 }
16381653 if !self . dist_compiles . is_empty ( ) {
1639- println ! ( "\n Successful distributed compiles" ) ;
1654+ writer . write ( "\n Successful distributed compiles" ) ;
16401655 let mut counts: Vec < _ > = self . dist_compiles . iter ( ) . collect ( ) ;
16411656 counts. sort_by ( |( _, c1) , ( _, c2) | c1. cmp ( c2) . reverse ( ) ) ;
16421657 for ( reason, count) in counts {
1643- println ! (
1658+ writer . write ( & format ! (
16441659 " {:<name_width$} {:>stat_width$}" ,
16451660 reason,
16461661 count,
16471662 name_width = name_width - 2 ,
1648- stat_width = stat_width
1649- ) ;
1663+ stat_width = stat_width,
1664+ ) ) ;
16501665 }
16511666 }
16521667 if !self . not_cached . is_empty ( ) {
1653- println ! ( "\n Non-cacheable reasons:" ) ;
1668+ writer . write ( "\n Non-cacheable reasons:" ) ;
16541669 let mut counts: Vec < _ > = self . not_cached . iter ( ) . collect ( ) ;
16551670 counts. sort_by ( |( _, c1) , ( _, c2) | c1. cmp ( c2) . reverse ( ) ) ;
16561671 for ( reason, count) in counts {
1657- println ! (
1672+ writer . write ( & format ! (
16581673 "{:<name_width$} {:>stat_width$}" ,
16591674 reason,
16601675 count,
16611676 name_width = name_width,
1662- stat_width = stat_width
1663- ) ;
1677+ stat_width = stat_width,
1678+ ) ) ;
16641679 }
1665- println ! ( ) ;
1680+ writer . write ( "" ) ;
16661681 }
16671682 ( name_width, stat_width)
16681683 }
1684+
1685+ fn set_percentage_stats ( & self , stats_vec : & mut Vec < ( String , String , usize ) > , advanced : bool ) {
1686+ set_percentage_stat (
1687+ stats_vec,
1688+ self . cache_hits . all ( ) ,
1689+ self . cache_misses . all ( ) + self . cache_hits . all ( ) ,
1690+ "Cache hits rate" ,
1691+ ) ;
1692+
1693+ let ( stats_hits, stats_misses) : ( Vec < _ > , Vec < _ > ) = if advanced {
1694+ (
1695+ self . cache_hits . adv_counts . iter ( ) . collect ( ) ,
1696+ self . cache_misses . adv_counts . iter ( ) . collect ( ) ,
1697+ )
1698+ } else {
1699+ (
1700+ self . cache_hits . counts . iter ( ) . collect ( ) ,
1701+ self . cache_misses . counts . iter ( ) . collect ( ) ,
1702+ )
1703+ } ;
1704+
1705+ let mut all_languages: HashSet < & String > = HashSet :: new ( ) ;
1706+ for ( lang, _) in & stats_hits {
1707+ all_languages. insert ( lang) ;
1708+ }
1709+ for ( lang, _) in & stats_misses {
1710+ all_languages. insert ( lang) ;
1711+ }
1712+
1713+ let mut all_languages: Vec < & String > = all_languages. into_iter ( ) . collect ( ) ;
1714+ all_languages. sort ( ) ;
1715+
1716+ for lang in all_languages {
1717+ let count_hits = stats_hits
1718+ . iter ( )
1719+ . find ( |& & ( l, _) | l == lang)
1720+ . map_or ( 0 , |& ( _, & count) | count) ;
1721+
1722+ let count_misses = stats_misses
1723+ . iter ( )
1724+ . find ( |& & ( l, _) | l == lang)
1725+ . map_or ( 0 , |& ( _, & count) | count) ;
1726+
1727+ set_percentage_stat (
1728+ stats_vec,
1729+ count_hits,
1730+ count_hits + count_misses,
1731+ & format ! ( "Cache hits rate ({})" , lang) ,
1732+ ) ;
1733+ }
1734+ }
1735+ }
1736+
1737+ fn set_percentage_stat (
1738+ vec : & mut Vec < ( String , String , usize ) > ,
1739+ count_hits : u64 ,
1740+ total : u64 ,
1741+ name : & str ,
1742+ ) {
1743+ if total == 0 {
1744+ vec. push ( ( name. to_string ( ) , "-" . to_string ( ) , 0 ) ) ;
1745+ } else {
1746+ let ratio = count_hits as f64 / total as f64 ;
1747+ vec. push ( ( name. to_string ( ) , format ! ( "{:.2} %" , ratio * 100.0 ) , 2 ) ) ;
1748+ }
16691749}
16701750
16711751impl ServerInfo {
@@ -1700,7 +1780,7 @@ impl ServerInfo {
17001780
17011781 /// Print info to stdout in a human-readable format.
17021782 pub fn print ( & self , advanced : bool ) {
1703- let ( name_width, stat_width) = self . stats . print ( advanced) ;
1783+ let ( name_width, stat_width) = self . stats . print ( & mut StdoutServerStatsWriter , advanced) ;
17041784 println ! (
17051785 "{:<name_width$} {}" ,
17061786 "Cache location" ,
@@ -1978,3 +2058,108 @@ fn waits_until_zero() {
19782058 drop ( active2) ;
19792059 assert_eq ! ( wait. now_or_never( ) , Some ( ( ) ) ) ;
19802060}
2061+
2062+ #[ cfg( test) ]
2063+ mod tests {
2064+ use super :: * ;
2065+
2066+ struct StringWriter {
2067+ buffer : String ,
2068+ }
2069+
2070+ impl StringWriter {
2071+ fn new ( ) -> StringWriter {
2072+ StringWriter {
2073+ buffer : String :: new ( ) ,
2074+ }
2075+ }
2076+
2077+ fn get_output ( self ) -> String {
2078+ self . buffer
2079+ }
2080+ }
2081+
2082+ impl ServerStatsWriter for StringWriter {
2083+ fn write ( & mut self , text : & str ) {
2084+ self . buffer . push_str ( & format ! ( "{}\n " , text) ) ;
2085+ }
2086+ }
2087+
2088+ #[ test]
2089+ fn test_print_cache_hits_rate_default_server_stats ( ) {
2090+ let stats = ServerStats :: default ( ) ;
2091+
2092+ let mut writer = StringWriter :: new ( ) ;
2093+ stats. print ( & mut writer, false ) ;
2094+
2095+ let output = writer. get_output ( ) ;
2096+
2097+ assert ! ( output. contains( "Cache hits rate -" ) ) ;
2098+ }
2099+
2100+ #[ test]
2101+ fn test_print_cache_hits_rate_server_stats ( ) {
2102+ let mut cache_hits_counts = HashMap :: new ( ) ;
2103+ cache_hits_counts. insert ( "Rust" . to_string ( ) , 100 ) ;
2104+ cache_hits_counts. insert ( "C/C++" . to_string ( ) , 200 ) ;
2105+
2106+ let mut cache_misses_counts = HashMap :: new ( ) ;
2107+ cache_misses_counts. insert ( "Rust" . to_string ( ) , 50 ) ;
2108+ cache_misses_counts. insert ( "Cuda" . to_string ( ) , 300 ) ;
2109+
2110+ let stats = ServerStats {
2111+ cache_hits : PerLanguageCount {
2112+ counts : cache_hits_counts,
2113+ ..Default :: default ( )
2114+ } ,
2115+ cache_misses : PerLanguageCount {
2116+ counts : cache_misses_counts,
2117+ ..Default :: default ( )
2118+ } ,
2119+ ..Default :: default ( )
2120+ } ;
2121+
2122+ let mut writer = StringWriter :: new ( ) ;
2123+ stats. print ( & mut writer, false ) ;
2124+
2125+ let output = writer. get_output ( ) ;
2126+
2127+ assert ! ( output. contains( "Cache hits rate 46.15 %" ) ) ;
2128+ assert ! ( output. contains( "Cache hits rate (C/C++) 100.00 %" ) ) ;
2129+ assert ! ( output. contains( "Cache hits rate (Cuda) 0.00 %" ) ) ;
2130+ assert ! ( output. contains( "Cache hits rate (Rust) 66.67 %" ) ) ;
2131+ }
2132+
2133+ #[ test]
2134+ fn test_print_cache_hits_rate_advanced_server_stats ( ) {
2135+ let mut cache_hits_counts = HashMap :: new ( ) ;
2136+ cache_hits_counts. insert ( "rust" . to_string ( ) , 50 ) ;
2137+ cache_hits_counts. insert ( "c/c++ [clang]" . to_string ( ) , 30 ) ;
2138+
2139+ let mut cache_misses_counts = HashMap :: new ( ) ;
2140+ cache_misses_counts. insert ( "rust" . to_string ( ) , 100 ) ;
2141+ cache_misses_counts. insert ( "cuda" . to_string ( ) , 70 ) ;
2142+
2143+ let stats = ServerStats {
2144+ cache_hits : PerLanguageCount {
2145+ adv_counts : cache_hits_counts,
2146+ ..Default :: default ( )
2147+ } ,
2148+ cache_misses : PerLanguageCount {
2149+ adv_counts : cache_misses_counts,
2150+ ..Default :: default ( )
2151+ } ,
2152+ ..Default :: default ( )
2153+ } ;
2154+
2155+ let mut writer = StringWriter :: new ( ) ;
2156+ stats. print ( & mut writer, true ) ;
2157+
2158+ let output = writer. get_output ( ) ;
2159+
2160+ assert ! ( output. contains( "Cache hits rate -" ) ) ;
2161+ assert ! ( output. contains( "Cache hits rate (c/c++ [clang]) 100.00 %" ) ) ;
2162+ assert ! ( output. contains( "Cache hits rate (cuda) 0.00 %" ) ) ;
2163+ assert ! ( output. contains( "Cache hits rate (rust) 33.33 %" ) ) ;
2164+ }
2165+ }
0 commit comments