Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RAII type for console.time and console.timeEnd #16

Closed
fitzgen opened this issue Feb 26, 2019 · 16 comments
Closed

RAII type for console.time and console.timeEnd #16

fitzgen opened this issue Feb 26, 2019 · 16 comments
Labels

Comments

@fitzgen
Copy link
Member

fitzgen commented Feb 26, 2019

Let's pull it out of https://rustwasm.github.io/book/game-of-life/time-profiling.html#time-each-universetick-with-consoletime-and-consoletimeend

@fitzgen fitzgen added the good first issue Good for newcomers label Mar 15, 2019
@fitzgen
Copy link
Member Author

fitzgen commented Mar 15, 2019

Given that we already have an example implementation from the book, I think this would be a good issue for someone to make a quick strawman API proposal like #10 (comment), and then get a PR going!

@cbrevik
Copy link
Contributor

cbrevik commented Mar 16, 2019

I'd like to take a stab at this!

@cbrevik
Copy link
Contributor

cbrevik commented Mar 16, 2019

Summary

Add RAII type for console.time and console.timeEnd.

Motivation

Measuring invocation time of code blocks can be done with console.time and console.timeEnd, available via the web-sys crate. This requires two calls, a console.timeEnd call for every console.time call. Wrapping this up in a RAII type would make the measurement code less verbose. It would also make it make it more safe, since you ensure that console.timeEnd is called when the object goes out of scope.

Detailed Explanation

As noted, the implementation could be quite similar to the type from the Rust/Wasm book:

extern crate web_sys;
use web_sys::console;

pub struct ConsoleTimer<'a> {
    label: &'a str,
}

impl<'a> ConsoleTimer<'a> {
    pub fn new(label: &'a str) -> ConsoleTimer<'a> {
        console::time_with_label(label);
        ConsoleTimer { label }
    }
}

impl<'a> Drop for ConsoleTimer<'a> {
    fn drop(&mut self) {
        console::time_end_with_label(self.label);
    }
}

Then this type can be constructed at the top of a code block in order to do a measurement:

{
    let _timer = ConsoleTimer::new("my_unique_label"); // measurement starts
    // the code under measurement
} // measurement ends

Drawbacks, Rationale, and Alternatives

Although a simple way to perform measurements without too much orchestration, this type might be confusing to some developers? It might not immediately be apparant that you don't have to call some start or end-method for measurements.

Unresolved Questions

  • Unsure about naming here? ConsoleTimer might be to generic a name for this use case? Maybe CodeBlockTimer is a more descriptive name?
  • Should we allow a timer without a label? I.e. console::time() and console::time_end.
  • There is no check if a timer with the label already exists, but unsure if such a check is even possible at this time?

@Pauan
Copy link
Contributor

Pauan commented Mar 16, 2019

In my own personal projects, I've used a macro for this:

console_time!("Foo", {
    // code goes here
})

I personally think this is more ergonomic and intuitive, compared to an RAII type that has side effects on creation and dropping.

@cbrevik
Copy link
Contributor

cbrevik commented Mar 16, 2019

As a relative novice at Rust, that pattern immediately seems more explicit and intuitive to me as well @Pauan

Edit: Something like this?

@Pauan
Copy link
Contributor

Pauan commented Mar 18, 2019

@cbrevik I would just define it like this, without bothering with ConsoleTimer at all:

#[macro_export]
macro_rules! console_time {
    ($label:expr, $b:block) => {
        let label = $label;
        console::time_with_label(label);
        let output = $b;
        console::time_end_with_label(label);
        output
    };
}

But it doesn't matter too much, one way or the other.

@fitzgen
Copy link
Member Author

fitzgen commented Mar 18, 2019

I personally think this is more ergonomic and intuitive, compared to an RAII type that has side effects on creation and dropping.

Interesting. RAII is a pattern that is commonly used in Rust, and seems very familiar to me: std::fs::File, std::sync::Mutex, std::rc::Rc, tempfile::TempDir, etc.

Even when explicit control blocks are used, it is usually done via a closure rather than a macro, e.g. crossbeam_utils::thread::scope(|s| { ... }) instead of crossbeam_utils::thread::scope!(s, { .. }).

@fitzgen
Copy link
Member Author

fitzgen commented Mar 18, 2019

Additionally, it is easy to implement the explicit scope version with the RAII building block:

fn with_console_time<F, T>(label: &str, scope: F) -> T
where
    F: FnOnce() -> T
{
    let _timer = ConsoleTimer::new(label);
    f()
}

// usage...
with_console_timer("foobar", || {
    // ...
});

But you can't implement RAII in terms of callbacks or scope macros.

@fitzgen
Copy link
Member Author

fitzgen commented Mar 18, 2019

An RAII type can also be used for asynchronous tasks, while a closure can't be (maybe eventually with async/await syntax, but not right now).

@fitzgen
Copy link
Member Author

fitzgen commented Mar 18, 2019

I would be in favor of extending @cbrevik's ConsoleTimer API with the following

pub fn time_scope<F, T>(label: &str, f: F) -> T
where
    F: FnOnce() -> T;

but I think we must have the RAII struct.

Happy to bikeshed on naming as well.

@Pauan
Copy link
Contributor

Pauan commented Mar 18, 2019

Interesting. RAII is a pattern that is commonly used in Rust, and seems very familiar to me: std::fs::File, std::sync::Mutex, std::rc::Rc, tempfile::TempDir, etc.

The difference is that in all of those cases you actually use the variable.

In the case of ConsoleTimer you always do let _timer = ConsoleTimer::new("foo"); and then never touch it again.

RAII is great, but it's not always the right tool for the job. Using a macro or closure based API is better in this case.

However, I agree with you that there are some edge cases where RAII is useful (putting into a struct to log how long an object lives, using it with Futures 0.1 to benchmark async code, etc.)

So I agree that we should have an RAII type, but it should be put into a raw submodule.

@neoeinstein
Copy link

I think that having the core struct be the RAII type is just fine. For those who want to use it that way, it is easily accessible. We should do as @fitzgen mentioned and expose the closure-based form as well, though, as something like ConsoleTimer::scope("xyz", || …). In documentation, we should prefer to demonstrate using the closure form. For cases where this doesn't work, or the timer needs to be passed into another scope (such as a delayed action) the struct form is still necessary.

@fitzgen
Copy link
Member Author

fitzgen commented Mar 18, 2019

I like the idea of having the scoped callback version be a static method on the RAII type 👍

@cbrevik
Copy link
Contributor

cbrevik commented Mar 19, 2019

I like it! That makes sense to me as well.

I have a semi-related dumb question; would it make sense to have this suggested API in its own specific crate, e.g. gloo-console-timer? (Awaiting bikeshedding on naming.) Or would it make more sense to put it in a gloo-console crate where the API can be expanded to include more console related functionality?

This sentence from the README sort of makes me think it should be in its own crate:

Carefully crafting a tiny Wasm module and integrating it back into an existing JavaScript project? Grab that one targeted library you need out from the toolkit and use it by itself.

But I'm not clear on how granular it should be.

@fitzgen
Copy link
Member Author

fitzgen commented Mar 19, 2019

We haven't figured out the answer to all these questions yet (e.g. see #27) but in this case I think having it in its own gloo-console-timer crate makes sense.

I think we are ready for a PR here! :)

@cbrevik
Copy link
Contributor

cbrevik commented Mar 19, 2019

Cool, will get on it! :)

ranile pushed a commit that referenced this issue Feb 15, 2022
)

* feat: feature gate json support

* feat: feature gate weboscket api

* ci: check websocket and json features seperately in CI, check no default features

* feat: feature gate the http API

* refactor: use futures-core and futures-sink instead of depending on whole of futures

* ci: test http feature seperately in CI

* fix: only compile error conversion funcs if either APIs are enabled

* fix: add futures to dev-deps for tests, fix doc test
ranile added a commit that referenced this issue Feb 16, 2022
* Initial commit

* provide a better interface for errors, rename `RequestMethod` to `Method`

* remove method for array buffer and blob in favor of as_raw

* prepare for release

* add CI, update readme

* hide JsError in the docs

* fix CI?

* Install wasm-pack in CI

* misc

* websocket API

Fixes: ranile/reqwasm#1

* add tests for websocket

* update documentation, prepare for release

* fix mistake in documentation

* Rewrite WebSockets code (#4)

* redo websockets

* docs + tests

* remove gloo-console

* fix CI

* Add getters for the underlying WebSocket fields

* better API

* better API part 2 electric boogaloo

* deserialize Blob to Vec<u8> (#9)

* Update to Rust 2021 and use JsError from gloo-utils (#10)

* Update to Rust 2021 and use JsError from gloo-utils

* use new toolchain

* Add response.binary method to obtain response as Vec<u8>

Fixes: ranile/reqwasm#7

* Remove `Clone` impl from WebSocket.

When the WebSocket is used with frameworks, passed down as props, it might be `drop`ed automatically, which closes the WebSocket connection. Initially `Clone` was added so sender and receiver can be in different `spawn_local`s but it turns out that `StreamExt::split` solves that problem very well.

See #13 for more information about the issue

* Rustfmt + ignore editor config files

* Fix onclose handling (#14)

* feat: feature gate json, websocket and http; enable them by default (#16)

* feat: feature gate json support

* feat: feature gate weboscket api

* ci: check websocket and json features seperately in CI, check no default features

* feat: feature gate the http API

* refactor: use futures-core and futures-sink instead of depending on whole of futures

* ci: test http feature seperately in CI

* fix: only compile error conversion funcs if either APIs are enabled

* fix: add futures to dev-deps for tests, fix doc test

* 0.3.0

* Fix outdated/missing docs

* 0.3.1

* Change edition from 2021 to 2018 (#18)

* Change edition from 2021 to 2018

* Fix missing import due to edition 2021 prelude

* hopefully this will fix the issue (#19)

* There's no message

* Replace `async-broadcast` with `futures-channel::mpsc` (#21)

We no longer need a multi-producer-multi-consumer channel. There's only one consumer as of ranile/reqwasm@445e9a5

* Release 0.4.0

* Fix message ordering not being preserved (#29)

The websocket specification guarantees that messages are received in the
same order they are sent. The implementation in this library can violate
this guarantee because messages are parsed in a spawn_local block before
being sent over the channel. When multiple messages are received before
the next executor tick the scheduling order of the futures is
unspecified.
We fix this by performing all operations synchronously. The only part
where async is needed is the conversion of Blob to ArrayBuffer which we
obsolete by setting the websocket to always receive binary data as
ArrayBuffer.

* 0.4.1

* move files for gloo merge

* remove licence files

* gloo-specific patches

* fix CI

* re-export net from gloo

Co-authored-by: Michael Hueschen <mhuesch@users.noreply.github.com>
Co-authored-by: Stepan Henek <stepan+github@henek.name>
Co-authored-by: Yusuf Bera Ertan <y.bera003.06@protonmail.com>
Co-authored-by: Luke Chu <37006668+lukechu10@users.noreply.github.com>
Co-authored-by: Valentin <vakevk+github@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants