From 4fe20ca71d04d7410939084c5d92a2011f45cf81 Mon Sep 17 00:00:00 2001 From: Ben Sully <ben.sully88@gmail.com> Date: Wed, 16 Oct 2024 14:11:29 +0100 Subject: [PATCH 1/6] chore: don't bother with minifying transpiled code --- components/justfile | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/components/justfile b/components/justfile index 0eb2028..3d7958f 100644 --- a/components/justfile +++ b/components/justfile @@ -75,20 +75,8 @@ transpile: build --out-dir js/prophet-wasmstan \ cpp/prophet-wasmstan/wit/prophet-wasmstan.wit -transpile-min: build - jco transpile \ - --name prophet-wasmstan \ - --minify \ - --optimize \ - --out-dir js/prophet-wasmstan \ - cpp/prophet-wasmstan/prophet-wasmstan-component.wasm - jco types \ - --name prophet-wasmstan \ - --out-dir js/prophet-wasmstan \ - cpp/prophet-wasmstan/wit/prophet-wasmstan.wit - test: transpile cd js/prophet-wasmstan && npm ci && npm run test:ci -publish: transpile-min +publish: transpile cd js/prophet-wasmstan && npm ci && npm publish --access public From ee9b876a32c4637622bbc4d1603b4ce5ef29f83e Mon Sep 17 00:00:00 2001 From: Ben Sully <ben.sully88@gmail.com> Date: Wed, 16 Oct 2024 14:13:28 +0100 Subject: [PATCH 2/6] fix: never emit Stan logs starting containing 'Iter' These are just the header row but we parse the rows and emit them as structured logs anyway. --- crates/augurs-js/src/prophet.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/crates/augurs-js/src/prophet.rs b/crates/augurs-js/src/prophet.rs index 2010477..5fbc03e 100644 --- a/crates/augurs-js/src/prophet.rs +++ b/crates/augurs-js/src/prophet.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, mem, num::TryFromIntError}; +use std::{collections::HashMap, num::TryFromIntError}; use augurs_prophet::PositiveFloat; use js_sys::{Float64Array, Int32Array}; @@ -414,28 +414,14 @@ struct Logs { pub error: String, /// Fatal logs. pub fatal: String, - - #[serde(default, skip)] - emitted_header: bool, } impl Logs { - fn emit(mut self) { - let debug = mem::take(&mut self.debug); - let info = mem::take(&mut self.info); - let warn = mem::take(&mut self.warn); - let error = mem::take(&mut self.error); - let fatal = mem::take(&mut self.fatal); - for line in debug.lines() { + fn emit(self) { + for line in self.debug.lines() { tracing::trace!(target: "augurs::prophet::stan::optimize", "{}", line); } - for line in info.lines() { - if line.contains("Iter") { - if self.emitted_header { - return; - } - self.emitted_header = true; - } + for line in self.info.lines().filter(|line| !line.contains("Iter")) { match ConvergenceLog::new(line) { Some(log) => { tracing::debug!( @@ -455,13 +441,13 @@ impl Logs { } } } - for line in warn.lines() { + for line in self.warn.lines() { tracing::warn!(target: "augurs::prophet::stan::optimize", "{}", line); } - for line in error.lines() { + for line in self.error.lines() { tracing::error!(target: "augurs::prophet::stan::optimize", "{}", line); } - for line in fatal.lines() { + for line in self.fatal.lines() { tracing::error!(target: "augurs::prophet::stan::optimize", "{}", line); } } From f73e0f2d520ea9a4664c2fd2286183190acaeda2 Mon Sep 17 00:00:00 2001 From: Ben Sully <ben.sully88@gmail.com> Date: Wed, 16 Oct 2024 14:16:36 +0100 Subject: [PATCH 3/6] Fix invalid deserialize impl for PositiveFloat --- crates/augurs-prophet/src/positive_float.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/augurs-prophet/src/positive_float.rs b/crates/augurs-prophet/src/positive_float.rs index fb65f19..b6aed6e 100644 --- a/crates/augurs-prophet/src/positive_float.rs +++ b/crates/augurs-prophet/src/positive_float.rs @@ -2,7 +2,7 @@ #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct PositiveFloat(f64); /// An invalid float was provided when trying to create a [`PositiveFloat`]. @@ -48,3 +48,14 @@ impl From<PositiveFloat> for f64 { value.0 } } + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for PositiveFloat { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: serde::Deserializer<'de>, + { + let f = f64::deserialize(deserializer)?; + Self::try_new(f).map_err(serde::de::Error::custom) + } +} From 89fa1e3ddd1a69e03363efbdcadb5f4c36ddf07c Mon Sep 17 00:00:00 2001 From: Ben Sully <ben.sully88@gmail.com> Date: Wed, 16 Oct 2024 14:17:01 +0100 Subject: [PATCH 4/6] Fix generated Typescript type for sigmaObs field --- crates/augurs-js/src/prophet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/augurs-js/src/prophet.rs b/crates/augurs-js/src/prophet.rs index 5fbc03e..baae8ba 100644 --- a/crates/augurs-js/src/prophet.rs +++ b/crates/augurs-js/src/prophet.rs @@ -474,6 +474,7 @@ struct OptimizedParams { /// Trend offset. pub m: f64, /// Observation noise. + #[tsify(type = "number")] pub sigma_obs: PositiveFloat, /// Trend rate adjustments. #[tsify(type = "Float64Array")] From e539ae7fa7f87cf599548ffe853512268e6bb38e Mon Sep 17 00:00:00 2001 From: Ben Sully <ben.sully88@gmail.com> Date: Wed, 16 Oct 2024 14:40:34 +0100 Subject: [PATCH 5/6] Shim in a different function to run the init generator to completion When I tried to use the existing module within a Webpack project I found that the generator was continuing with promises and trying to initialize WebAssembly using them, which obviously failed. This function seems to work a lot better for some reason, although I haven't digged into why, exactly. --- .github/workflows/wasmstan.yml | 2 +- components/js/prophet-wasmstan/run.js | 17 +++++++++++++++++ components/justfile | 8 ++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 components/js/prophet-wasmstan/run.js diff --git a/.github/workflows/wasmstan.yml b/.github/workflows/wasmstan.yml index b2913ff..186ff93 100644 --- a/.github/workflows/wasmstan.yml +++ b/.github/workflows/wasmstan.yml @@ -20,7 +20,7 @@ jobs: targets: wasm32-unknown-unknown,wasm32-wasip1 - uses: taiki-e/install-action@v2 with: - tool: cargo-binstall,just,wasmtime + tool: cargo-binstall,just,ripgrep,wasmtime - name: Install deps run: just components/install-deps - uses: actions/setup-node@v4 diff --git a/components/js/prophet-wasmstan/run.js b/components/js/prophet-wasmstan/run.js new file mode 100644 index 0000000..181107d --- /dev/null +++ b/components/js/prophet-wasmstan/run.js @@ -0,0 +1,17 @@ +// Note: this function comes from https://stackoverflow.com/questions/30401486/ecma6-generators-yield-promise. +// It is used to convert a generator function into a promise. +// `jco transpile` generates a similar function but it didn't work for me. +// I'm not sure why, but I'll raise an issue on the `jco` repo. +// See the `justfile` for how this gets shimmed into the transpiled code; +// in short, we use `ripgrep` as in +// https://unix.stackexchange.com/questions/181180/replace-multiline-string-in-files +// (it was a Stack-Overflow heavy day...) +// The indentation is intentional so the function matches the original. + function run(g) { + return Promise.resolve(function step(v) { + const res = g.next(v); + if (res.done) return res.value; + return res.value.then(step); + }()); + } + return run(gen); diff --git a/components/justfile b/components/justfile index 3d7958f..23fc310 100644 --- a/components/justfile +++ b/components/justfile @@ -74,6 +74,14 @@ transpile: build --name prophet-wasmstan \ --out-dir js/prophet-wasmstan \ cpp/prophet-wasmstan/wit/prophet-wasmstan.wit + rg --replace="$(rg --invert-match --no-line-number '//' js/prophet-wasmstan/run.js)" \ + --multiline --multiline-dotall \ + --passthru \ + --no-line-number \ + ' let promise, resolve, reject;.+?return promise \|\| maybeSyncReturn;' \ + js/prophet-wasmstan/prophet-wasmstan.js \ + > js/prophet-wasmstan/prophet-wasmstan.fixed.js + mv js/prophet-wasmstan/prophet-wasmstan.fixed.js js/prophet-wasmstan/prophet-wasmstan.js test: transpile cd js/prophet-wasmstan && npm ci && npm run test:ci From a1f6631369c90b03d5dfe824726b9653f3a45981 Mon Sep 17 00:00:00 2001 From: Ben Sully <ben.sully88@gmail.com> Date: Wed, 16 Oct 2024 15:44:00 +0100 Subject: [PATCH 6/6] Update READMEs of JS packages to mention Webpack config --- components/js/prophet-wasmstan/README.md | 21 ++++++++++++++++++ crates/augurs-js/README.md | 28 +++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/components/js/prophet-wasmstan/README.md b/components/js/prophet-wasmstan/README.md index 5d7f6e7..8418145 100644 --- a/components/js/prophet-wasmstan/README.md +++ b/components/js/prophet-wasmstan/README.md @@ -26,3 +26,24 @@ prophet.predict({ ds: [ 1713717414 ]}) ``` See the documentation for `@bsull/augurs` for more details. + +## Troubleshooting + +### Webpack + +The generated Javascript bindings in this package may require some additional Webpack configuration to work. +Adding this to your `webpack.config.js` should be enough: + +```javascript +{ + experiments: { + // Required to load WASM modules. + asyncWebAssembly: true, + }, + resolve: { + fallback: { + fs: false, + }, + }, +} +``` diff --git a/crates/augurs-js/README.md b/crates/augurs-js/README.md index 6b9e885..449cdc3 100644 --- a/crates/augurs-js/README.md +++ b/crates/augurs-js/README.md @@ -8,7 +8,7 @@ Javascript bindings to the [`augurs`][repo] time series framework. ```json "dependencies": { - "@bsull/augurs": "^0.3.0" + "@bsull/augurs": "^0.4.1" } ``` @@ -36,4 +36,30 @@ const { point, lower, upper } = model.predictInSample(predictionInterval); const { point: futurePoint, lower: futureLower, upper: futureUpper } = model.predict(10, predictionInterval); ``` +## Troubleshooting + +### Webpack + +Some of the dependencies of `augurs` require a few changes to the Webpack configuration to work correctly. +Adding this to your `webpack.config.js` should be enough: + +```javascript +{ + experiments: { + // Required to load WASM modules. + asyncWebAssembly: true, + }, + module: { + rules: [ + { + test: /\@bsull\/augurs\/.*\.js$/, + resolve: { + fullySpecified: false + } + }, + ] + }, +} +``` + [repo]: https://github.com/grafana/augurs