1use std::{sync::Arc, time::Duration};
4
5use anyhow::Error;
6use futures::future::BoxFuture;
7use indicatif::ProgressBar;
8use std::io::IsTerminal as _;
9use wasmer::{Engine, Module};
10use wasmer_config::package::PackageSource;
11use wasmer_types::ModuleHash;
12use wasmer_wasix::{
13 SpawnError,
14 bin_factory::{BinaryPackage, BinaryPackageCommand},
15 runtime::{
16 module_cache::HashedModuleData,
17 resolver::{PackageSummary, QueryError},
18 },
19};
20use webc::Container;
21
22#[derive(Debug)]
27pub struct MonitoringRuntime<R> {
28 pub runtime: Arc<R>,
29 progress: ProgressBar,
30 quiet_mode: bool,
31}
32
33impl<R> MonitoringRuntime<R> {
34 pub fn new(runtime: R, progress: ProgressBar, quiet_mode: bool) -> Self {
35 MonitoringRuntime {
36 runtime: Arc::new(runtime),
37 progress,
38 quiet_mode,
39 }
40 }
41}
42
43impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
44 fn networking(&self) -> &virtual_net::DynVirtualNetworking {
45 self.runtime.networking()
46 }
47
48 fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
49 self.runtime.task_manager()
50 }
51
52 fn package_loader(
53 &self,
54 ) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
55 let inner = self.runtime.package_loader();
56 Arc::new(MonitoringPackageLoader {
57 inner,
58 progress: self.progress.clone(),
59 })
60 }
61
62 fn module_cache(
63 &self,
64 ) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
65 self.runtime.module_cache()
66 }
67
68 fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
69 let inner = self.runtime.source();
70 Arc::new(MonitoringSource {
71 inner,
72 progress: self.progress.clone(),
73 })
74 }
75
76 fn engine(&self) -> Engine {
77 self.runtime.engine()
78 }
79
80 fn new_store(&self) -> wasmer::Store {
81 self.runtime.new_store()
82 }
83
84 fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
85 self.runtime.http_client()
86 }
87
88 fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
89 self.runtime.tty()
90 }
91
92 #[cfg(feature = "journal")]
93 fn read_only_journals<'a>(
94 &'a self,
95 ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
96 self.runtime.read_only_journals()
97 }
98
99 #[cfg(feature = "journal")]
100 fn writable_journals<'a>(
101 &'a self,
102 ) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
103 self.runtime.writable_journals()
104 }
105
106 #[cfg(feature = "journal")]
107 fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
108 self.runtime.active_journal()
109 }
110
111 fn load_hashed_module(
112 &self,
113 module: HashedModuleData,
114 engine: Option<&Engine>,
115 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
116 if self.quiet_mode {
117 Box::pin(self.runtime.load_hashed_module(module, engine))
118 } else {
119 let hash = *module.hash();
120 let fut = self.runtime.load_hashed_module(module, engine);
121 Box::pin(compile_with_progress(
122 &self.progress,
123 fut,
124 hash,
125 None,
126 self.quiet_mode,
127 ))
128 }
129 }
130
131 fn load_hashed_module_sync(
132 &self,
133 wasm: HashedModuleData,
134 engine: Option<&Engine>,
135 ) -> Result<Module, wasmer_wasix::SpawnError> {
136 if self.quiet_mode {
137 self.runtime.load_hashed_module_sync(wasm, engine)
138 } else {
139 let hash = *wasm.hash();
140 compile_with_progress_sync(
141 &self.progress,
142 move || self.runtime.load_hashed_module_sync(wasm, engine),
143 &hash,
144 None,
145 )
146 }
147 }
148
149 fn load_command_module(
150 &self,
151 cmd: &BinaryPackageCommand,
152 ) -> BoxFuture<'_, Result<Module, SpawnError>> {
153 if self.quiet_mode {
154 self.runtime.load_command_module(cmd)
155 } else {
156 let fut = self.runtime.load_command_module(cmd);
157
158 Box::pin(compile_with_progress(
159 &self.progress,
160 fut,
161 *cmd.hash(),
162 Some(cmd.name().to_owned()),
163 self.quiet_mode,
164 ))
165 }
166 }
167
168 fn load_command_module_sync(
169 &self,
170 cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand,
171 ) -> Result<Module, wasmer_wasix::SpawnError> {
172 if self.quiet_mode {
173 self.runtime.load_command_module_sync(cmd)
174 } else {
175 compile_with_progress_sync(
176 &self.progress,
177 || self.runtime.load_command_module_sync(cmd),
178 cmd.hash(),
179 Some(cmd.name()),
180 )
181 }
182 }
183}
184
185async fn compile_with_progress<'a, F, T>(
186 bar: &ProgressBar,
187 fut: F,
188 hash: ModuleHash,
189 name: Option<String>,
190 quiet_mode: bool,
191) -> T
192where
193 F: std::future::Future<Output = T> + Send + 'a,
194 T: Send + 'static,
195{
196 if quiet_mode {
197 fut.await
198 } else {
199 let should_clear = bar.is_finished() || bar.is_hidden();
200 show_compile_progress(bar, &hash, name.as_deref());
201 let res = fut.await;
202 if should_clear {
203 bar.finish_and_clear();
204 }
205
206 res
207 }
208}
209
210fn compile_with_progress_sync<F, T>(
211 bar: &ProgressBar,
212 f: F,
213 hash: &ModuleHash,
214 name: Option<&str>,
215) -> T
216where
217 F: FnOnce() -> T,
218{
219 let should_clear = bar.is_finished() || bar.is_hidden();
220 show_compile_progress(bar, hash, name);
221 let res = f();
222 if should_clear {
223 bar.finish_and_clear();
224 }
225 res
226}
227
228fn show_compile_progress(bar: &ProgressBar, hash: &ModuleHash, name: Option<&str>) {
229 let hash = hash.to_string();
231 let hash = &hash[0..8];
232 let msg = if let Some(name) = name {
233 format!("Compiling WebAssembly module for command '{name}' ({hash})...")
234 } else {
235 format!("Compiling WebAssembly module {hash}...")
236 };
237
238 bar.set_message(msg);
239 bar.enable_steady_tick(Duration::from_millis(100));
240
241 if bar.is_finished() || bar.is_hidden() {
242 bar.reset();
243 }
244}
245
246#[derive(Debug)]
247struct MonitoringSource {
248 inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
249 progress: ProgressBar,
250}
251
252#[async_trait::async_trait]
253impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
254 async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
255 self.progress.set_message(format!("Looking up {package}"));
256 self.inner.query(package).await
257 }
258}
259
260#[derive(Debug)]
261struct MonitoringPackageLoader {
262 inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
263 progress: ProgressBar,
264}
265
266#[async_trait::async_trait]
267impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
268 async fn load(&self, summary: &PackageSummary) -> Result<Container, Error> {
269 let pkg_id = summary.package_id();
270 self.progress.set_message(format!("Downloading {pkg_id}"));
271
272 self.inner.load(summary).await
273 }
274
275 async fn load_package_tree(
276 &self,
277 root: &Container,
278 resolution: &wasmer_wasix::runtime::resolver::Resolution,
279 root_is_local_dir: bool,
280 ) -> Result<BinaryPackage, Error> {
281 self.inner
282 .load_package_tree(root, resolution, root_is_local_dir)
283 .await
284 }
285}