Skip to main content

cubecl_runtime/logging/
server.rs

1use core::fmt::Display;
2
3use crate::config::memory::MemoryLogLevel;
4use crate::config::streaming::StreamingLogLevel;
5use crate::config::{Logger, compilation::CompilationLogLevel, profiling::ProfilingLogLevel};
6use alloc::format;
7use alloc::string::String;
8use alloc::string::ToString;
9use async_channel::{Receiver, Sender};
10use cubecl_common::future::spawn_detached_fut;
11use cubecl_common::profile::ProfileDuration;
12
13use super::{ProfileLevel, Profiled};
14
15enum LogMessage {
16    Execution(String),
17    Compilation(String),
18    Streaming(String),
19    Memory(String),
20    Profile(String, ProfileDuration),
21    ProfileSummary,
22}
23
24/// Server logger.
25#[derive(Debug)]
26pub struct ServerLogger {
27    profile_level: Option<ProfileLevel>,
28    log_compile_info: bool,
29    log_compile_source: bool,
30    log_streaming: StreamingLogLevel,
31    log_channel: Option<Sender<LogMessage>>,
32    log_memory: MemoryLogLevel,
33}
34
35impl Default for ServerLogger {
36    fn default() -> Self {
37        let logger = Logger::new();
38
39        let disabled = matches!(
40            logger.config.compilation.logger.level,
41            CompilationLogLevel::Disabled
42        ) && matches!(
43            logger.config.profiling.logger.level,
44            ProfilingLogLevel::Disabled
45        ) && matches!(logger.config.memory.logger.level, MemoryLogLevel::Disabled)
46            && matches!(
47                logger.config.streaming.logger.level,
48                StreamingLogLevel::Disabled
49            );
50
51        if disabled {
52            return Self {
53                profile_level: None,
54                log_compile_info: false,
55                log_compile_source: false,
56                log_streaming: StreamingLogLevel::Disabled,
57                log_channel: None,
58                log_memory: MemoryLogLevel::Disabled,
59            };
60        }
61        let profile_level = match logger.config.profiling.logger.level {
62            ProfilingLogLevel::Disabled => None,
63            ProfilingLogLevel::Minimal => Some(ProfileLevel::ExecutionOnly),
64            ProfilingLogLevel::Basic => Some(ProfileLevel::Basic),
65            ProfilingLogLevel::Medium => Some(ProfileLevel::Medium),
66            ProfilingLogLevel::Full => Some(ProfileLevel::Full),
67        };
68
69        let log_compile_info = match logger.config.compilation.logger.level {
70            CompilationLogLevel::Disabled => false,
71            CompilationLogLevel::Basic => true,
72            CompilationLogLevel::Full => true,
73        };
74        let log_compile_source = matches!(
75            logger.config.compilation.logger.level,
76            CompilationLogLevel::Full
77        );
78        let log_streaming = logger.config.streaming.logger.level;
79        let log_memory = logger.config.memory.logger.level;
80
81        let (send, rec) = async_channel::unbounded();
82
83        // Spawn the logger as a detached task.
84        let async_logger = AsyncLogger {
85            message: rec,
86            logger,
87            profiled: Default::default(),
88        };
89        // Spawn the future in the background to logs messages / durations.
90        spawn_detached_fut(async_logger.process());
91
92        Self {
93            profile_level,
94            log_compile_info,
95            log_compile_source,
96            log_streaming,
97            log_memory,
98            log_channel: Some(send),
99        }
100    }
101}
102
103impl ServerLogger {
104    /// Returns the profile level, none if profiling is deactivated.
105    pub fn profile_level(&self) -> Option<ProfileLevel> {
106        self.profile_level
107    }
108
109    /// Returns true if compilation info should be logged.
110    pub fn compilation_activated(&self) -> bool {
111        self.log_compile_info
112    }
113
114    /// Returns true if compilation logging includes kernel sources
115    /// ([`CompilationLogLevel::Full`]) — the only level where attaching debug
116    /// info and formatting the source pays off. `Basic` prints a name-only
117    /// line, so runtimes must not spend a `clang-format` subprocess per fresh
118    /// kernel on it.
119    pub fn compilation_source_activated(&self) -> bool {
120        self.log_compile_source
121    }
122
123    /// Log the argument to a file when the compilation logger is activated.
124    pub fn log_compilation<I>(&self, arg: &I)
125    where
126        I: Display,
127    {
128        if let Some(channel) = &self.log_channel
129            && self.log_compile_info
130        {
131            // Channel will never be full, don't care if it's closed.
132            let _ = channel.try_send(LogMessage::Compilation(arg.to_string()));
133        }
134    }
135
136    /// Log the argument to the logger when the streaming logger is activated.
137    pub fn log_streaming<I: FnOnce() -> String, C: FnOnce(StreamingLogLevel) -> bool>(
138        &self,
139        cond: C,
140        format: I,
141    ) {
142        if let Some(channel) = &self.log_channel
143            && cond(self.log_streaming)
144        {
145            // Channel will never be full, don't care if it's closed.
146            let _ = channel.try_send(LogMessage::Streaming(format()));
147        }
148    }
149
150    /// Log the argument to the logger when the memory logger is activated.
151    pub fn log_memory<I: FnOnce() -> String, C: FnOnce(MemoryLogLevel) -> bool>(
152        &self,
153        cond: C,
154        format: I,
155    ) {
156        if let Some(channel) = &self.log_channel
157            && cond(self.log_memory)
158        {
159            // Channel will never be full, don't care if it's closed.
160            let _ = channel.try_send(LogMessage::Memory(format()));
161        }
162    }
163
164    /// Register a profiled task without timing.
165    pub fn register_execution(&self, name: impl Display) {
166        if let Some(channel) = &self.log_channel
167            && matches!(self.profile_level, Some(ProfileLevel::ExecutionOnly))
168        {
169            // Channel will never be full, don't care if it's closed.
170            let _ = channel.try_send(LogMessage::Execution(name.to_string()));
171        }
172    }
173
174    /// Register a profiled task.
175    pub fn register_profiled(&self, name: impl Display, duration: ProfileDuration) {
176        if let Some(channel) = &self.log_channel
177            && self.profile_level.is_some()
178        {
179            // Channel will never be full, don't care if it's closed.
180            let _ = channel.try_send(LogMessage::Profile(name.to_string(), duration));
181        }
182    }
183
184    /// Show the profiling summary if activated and reset its state.
185    pub fn profile_summary(&self) {
186        if let Some(channel) = &self.log_channel
187            && self.profile_level.is_some()
188        {
189            // Channel will never be full, don't care if it's closed.
190            let _ = channel.try_send(LogMessage::ProfileSummary);
191        }
192    }
193}
194
195struct AsyncLogger {
196    message: Receiver<LogMessage>,
197    logger: Logger,
198    profiled: Profiled,
199}
200
201impl AsyncLogger {
202    async fn process(mut self) {
203        while let Ok(msg) = self.message.recv().await {
204            match msg {
205                LogMessage::Compilation(msg) => {
206                    self.logger.log_compilation(&msg);
207                }
208                LogMessage::Streaming(msg) => {
209                    self.logger.log_streaming(&msg);
210                }
211                LogMessage::Memory(msg) => {
212                    self.logger.log_memory(&msg);
213                }
214                LogMessage::Profile(name, profile) => {
215                    let duration = profile.resolve().await.duration();
216                    self.profiled.update(&name, duration);
217                    self.logger
218                        .log_profiling(&format!("| {duration:<10?} | {name}"));
219                }
220                LogMessage::Execution(name) => {
221                    self.logger.log_profiling(&format!("Executing {name}"));
222                }
223                LogMessage::ProfileSummary => {
224                    if !self.profiled.is_empty() {
225                        self.logger.log_profiling(&self.profiled);
226                        self.profiled = Profiled::default();
227                    }
228                }
229            }
230        }
231    }
232}