use std::{sync::Arc, time::Duration};
use anyhow::Error;
use futures::future::BoxFuture;
use indicatif::ProgressBar;
use std::io::IsTerminal as _;
use wasmer::{Engine, Module};
use wasmer_config::package::PackageSource;
use wasmer_types::ModuleHash;
use wasmer_wasix::{
SpawnError,
bin_factory::{BinaryPackage, BinaryPackageCommand},
runtime::{
ModuleInput,
module_cache::{
HashedModuleData,
progress::{ModuleLoadProgress, ModuleLoadProgressReporter},
},
resolver::{PackageSummary, QueryError},
},
};
use webc::Container;
#[derive(Debug)]
pub struct MonitoringRuntime<R> {
pub runtime: Arc<R>,
progress: ProgressBar,
quiet_mode: bool,
}
impl<R> MonitoringRuntime<R> {
pub fn new(runtime: R, progress: ProgressBar, quiet_mode: bool) -> Self {
MonitoringRuntime {
runtime: Arc::new(runtime),
progress,
quiet_mode,
}
}
}
impl<R: wasmer_wasix::Runtime + Send + Sync> wasmer_wasix::Runtime for MonitoringRuntime<R> {
fn networking(&self) -> &virtual_net::DynVirtualNetworking {
self.runtime.networking()
}
fn task_manager(&self) -> &Arc<dyn wasmer_wasix::VirtualTaskManager> {
self.runtime.task_manager()
}
fn package_loader(
&self,
) -> Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync> {
let inner = self.runtime.package_loader();
Arc::new(MonitoringPackageLoader {
inner,
progress: self.progress.clone(),
})
}
fn module_cache(
&self,
) -> Arc<dyn wasmer_wasix::runtime::module_cache::ModuleCache + Send + Sync> {
self.runtime.module_cache()
}
fn source(&self) -> Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync> {
let inner = self.runtime.source();
Arc::new(MonitoringSource {
inner,
progress: self.progress.clone(),
})
}
fn engine(&self) -> Engine {
self.runtime.engine()
}
fn new_store(&self) -> wasmer::Store {
self.runtime.new_store()
}
fn http_client(&self) -> Option<&wasmer_wasix::http::DynHttpClient> {
self.runtime.http_client()
}
fn tty(&self) -> Option<&(dyn wasmer_wasix::os::TtyBridge + Send + Sync)> {
self.runtime.tty()
}
fn additional_imports(
&self,
module: &Module,
store: &mut wasmer::StoreMut,
) -> anyhow::Result<wasmer::Imports> {
self.runtime.additional_imports(module, store)
}
fn configure_new_instance(
&self,
module: &Module,
store: &mut wasmer::StoreMut,
instance: &wasmer::Instance,
imported_memory: Option<&wasmer::Memory>,
) -> anyhow::Result<()> {
self.runtime
.configure_new_instance(module, store, instance, imported_memory)
}
#[cfg(feature = "journal")]
fn read_only_journals<'a>(
&'a self,
) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynReadableJournal>> + 'a> {
self.runtime.read_only_journals()
}
#[cfg(feature = "journal")]
fn writable_journals<'a>(
&'a self,
) -> Box<dyn Iterator<Item = Arc<wasmer_wasix::journal::DynJournal>> + 'a> {
self.runtime.writable_journals()
}
#[cfg(feature = "journal")]
fn active_journal(&self) -> Option<&'_ wasmer_wasix::journal::DynJournal> {
self.runtime.active_journal()
}
fn resolve_module<'a>(
&'a self,
input: ModuleInput<'a>,
engine: Option<&Engine>,
on_progress: Option<ModuleLoadProgressReporter>,
) -> BoxFuture<'a, Result<Module, SpawnError>> {
if on_progress.is_some() || self.quiet_mode {
return self.runtime.resolve_module(input, engine, on_progress);
}
use std::fmt::Write as _;
let short_hash = input.hash().short_hash();
let progress_msg = match &input {
ModuleInput::Bytes(_) | ModuleInput::Hashed(_) => {
format!("Compiling module ({short_hash})")
}
ModuleInput::Command(cmd) => format!("Compiling {}", cmd.name()),
};
let pb = self.progress.clone();
let on_progress = Some(ModuleLoadProgressReporter::new({
let base_msg = progress_msg.clone();
move |prog| {
let msg = match prog {
ModuleLoadProgress::CompilingModule(c) => {
let mut msg = base_msg.clone();
if let (Some(step), Some(step_count)) =
(c.phase_step(), c.phase_step_count())
{
pb.set_length(step_count);
pb.set_position(step);
if step_count != 0 {
write!(
&mut msg,
" ({:.0}%)",
100.0 * step as f32 / step_count as f32
)
.unwrap();
}
};
pb.tick();
msg
}
_ => base_msg.clone(),
};
pb.set_message(msg);
Ok(())
}
}));
let engine = engine.cloned();
let style = indicatif::ProgressStyle::default_bar()
.template("{spinner} {wide_bar:.cyan/blue} {msg}")
.expect("invalid progress bar template");
self.progress.set_style(style);
self.progress.reset();
if self.progress.is_hidden() {
self.progress
.set_draw_target(indicatif::ProgressDrawTarget::stderr());
}
self.progress.set_message(progress_msg);
let f = async move {
let res = self
.runtime
.resolve_module(input, engine.as_ref(), on_progress)
.await;
self.progress
.set_style(indicatif::ProgressStyle::default_spinner());
self.progress.reset();
self.progress.finish_and_clear();
res
};
Box::pin(f)
}
}
#[derive(Debug)]
struct MonitoringSource {
inner: Arc<dyn wasmer_wasix::runtime::resolver::Source + Send + Sync>,
progress: ProgressBar,
}
#[async_trait::async_trait]
impl wasmer_wasix::runtime::resolver::Source for MonitoringSource {
async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
self.progress.set_message(format!("Looking up {package}"));
self.inner.query(package).await
}
}
#[derive(Debug)]
struct MonitoringPackageLoader {
inner: Arc<dyn wasmer_wasix::runtime::package_loader::PackageLoader + Send + Sync>,
progress: ProgressBar,
}
#[async_trait::async_trait]
impl wasmer_wasix::runtime::package_loader::PackageLoader for MonitoringPackageLoader {
async fn load(&self, summary: &PackageSummary) -> Result<Container, Error> {
let pkg_id = summary.package_id();
self.progress.set_message(format!("Downloading {pkg_id}"));
self.inner.load(summary).await
}
async fn load_package_tree(
&self,
root: &Container,
resolution: &wasmer_wasix::runtime::resolver::Resolution,
root_is_local_dir: bool,
) -> Result<BinaryPackage, Error> {
self.inner
.load_package_tree(root, resolution, root_is_local_dir)
.await
}
}