Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Integrate metrics sender into state machine #488

Merged
merged 4 commits into from Aug 14, 2020

Conversation

Robert-Steiner
Copy link
Contributor

@Robert-Steiner Robert-Steiner commented Aug 3, 2020

The second part of the Implement data collection to InfluxDB task.

Integration of the MetricsSender into the state machine.

My first idea was to put the metrics sender into the CoordinatorState so that we don't have to add another field to the PhaseState. However, I didn't like it because the metrics sender has nothing to do with the coordinator state. Therefore, I ended up putting the metrics sender into the PhaseState.

In my opinion the same applies for EventPublisher. It may make sense to decouple the EventPublisher from the CoordinatorState in a separate PR. We might have two structs: a state struct and an io struct. Both are fields of a coordinator struct which then used in the state machine.

pub struct State {
    /// The credentials of the coordinator.
    pub keys: EncryptKeyPair,
    /// Internal ID used to identify a round
    pub round_id: u64,
    /// The round parameters.
    pub round_params: RoundParameters,
    /// The minimum of required sum/sum2 messages.
    pub min_sum_count: usize,
    /// The minimum of required update messages.
    pub min_update_count: usize,
    /// The minimum time (in seconds) reserved for processing sum/sum2 messages.
    pub min_sum_time: u64,
    /// The minimum time (in seconds) reserved for processing update messages.
    pub min_update_time: u64,
    /// The maximum time (in seconds) permitted for processing sum/sum2 messages.
    pub max_sum_time: u64,
    /// The maximum time (in seconds) permitted for processing update messages.
    pub max_update_time: u64,
    /// The number of expected participants.
    pub expected_participants: usize,
    /// The masking configuration.
    pub mask_config: MaskConfig,
    /// The size of the model.
    pub model_size: usize,
}

pub struct IO<R> {
    /// The request receiver half.
    pub request_rx: RequestReceiver<R>,
    /// The event publisher.
    pub events: EventPublisher,
    /// The metrics sender half.
    pub metrics_tx: MetricsSender, 
}

pub struct Coordinator<R> {
    pub state: State,
    pub io: IO<R>
}


pub struct PhaseState<R, S> {
    /// The inner state.
    pub(in crate::state_machine) inner: S,
    /// The Coordinator state.
    pub(in crate::state_machine) coordinator: Coordinator<R>,
}

Future improvements

My original plan was to have something like a log macro e.g. metric!(round_id, 1) which you can add anywhere in the code without having to carry a metrics sender around. However, I have no glue how to do that. I know that the metrics-rs crate does just that, but it is at a very early stage of development. It could be possible to do this with #[proc_macro_hack].

@Robert-Steiner
Copy link
Contributor Author

I was able to implement a Function-like procedural macros for the code sender.send(metric)
The macro will only add this line if the metrics feature is enabled. You can find an example here.

@little-dude
Copy link
Contributor

@Robert-Steiner should I review now or do you think it's worth waiting for you to rebase against the changes in #492 ?

@Robert-Steiner
Copy link
Contributor Author

@little-dude I think it makes sense to merge #492 first. Then I can rebase and finish the implementation of the missing message metrics.
I will drop a message when it is ready for review.
@finiteprods Since you've already started the review, feel free to post your comments.

@rsaffi
Copy link
Contributor

rsaffi commented Aug 7, 2020

#build

Copy link
Contributor

@finiteprods finiteprods left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good to me so far! left a few comments that caught me eye. BTW when trying to build it earlier, I got several

error[E0658]: procedural macros cannot be expanded to statements

errors - but perhaps this is expected.

rust/examples/metrics-macro.rs Outdated Show resolved Hide resolved
rust/examples/metrics-macro.rs Outdated Show resolved Hide resolved
rust/src/bin/main.rs Outdated Show resolved Hide resolved
rust/src/state_machine/phases/sum.rs Show resolved Hide resolved
@Robert-Steiner
Copy link
Contributor Author

looks good to me so far! left a few comments that caught me eye. BTW when trying to build it earlier, I got several

error[E0658]: procedural macros cannot be expanded to statements

errors - but perhaps this is expected.

You right, function-like procedural macros were added recently. I forgot to mention that you need at least rust v1.45.0. (rustup update should do the job)

@Robert-Steiner
Copy link
Contributor Author

Robert-Steiner commented Aug 10, 2020

I would like to briefly summarize again what the idea behind the macro is and how it works.
The idea is to reduce the usage of #[cfg(feature = "metrics")].
For example, for each sender.send(metrics) line we need to add #[cfg(feature = "metrics")] as well.

#[cfg(feature = "metrics")]
sender.send(metrics)

It would be more elegant if we could reduce the two lines into one line.

metrics!(sender, metric::update());

First, I tried to solve this with marcos_rules!

macro_rules! metrics {
    ($sender:expr, $metric:expr) => {
        #[cfg(feature = "metrics")]
        $sender.send($metric);
    };
}

metrics!(sender, metric::update());

However it has two downsides.

  1. we always create an object (metric::update()) even if we don't use the metric feature.
  2. the metrics module + its dependencies must be available in both cases (with/without the metrics feature) otherwise we get a compiler error.

Next, I tried to solve it with proc-macros.
proc-macros are more powerful than marcos_rules!. They cannot be called in the crate in which they are defined, which is why we have to create the additional crate xaynet-metrics-macro. I think the reason for this need is that proc-macros have to be compiled before the actual code is compiled.

Now the trick is that we use conditional compilation in the proc-macros as well.

#[proc_macro]
pub fn metrics(input: TokenStream) -> TokenStream {
    let Send { sender, metrics } = parse_macro_input!(input as Send);

    TokenStream::from(quote! {
            #[cfg(feature = "metrics")]
            {
                #(#sender.send(#metrics);)*
            }
    })
}

So when we call cargo build (without the metrics feature) the line metrics!(sender, metric::update()); is replaced by () before the crate xaynet is compiled.

However, this approach also has its disadvantages. We need to publish an additional crate and it is a bit hacky.
Therefore I'm fine to postpone the macro and go with

#[cfg(feature = "metrics")]
sender.send(metrics)

first.

@codecov
Copy link

codecov bot commented Aug 10, 2020

Codecov Report

Merging #488 into master will increase coverage by 0.68%.
The diff coverage is 70.24%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #488      +/-   ##
==========================================
+ Coverage   53.75%   54.43%   +0.68%     
==========================================
  Files          65       67       +2     
  Lines        3099     3187      +88     
==========================================
+ Hits         1666     1735      +69     
- Misses       1433     1452      +19     
Impacted Files Coverage Δ
rust/src/metrics/service.rs 0.00% <0.00%> (ø)
rust/src/state_machine/mod.rs 33.33% <0.00%> (-5.80%) ⬇️
rust/xaynet-macros/src/lib.rs 0.00% <0.00%> (ø)
rust/src/state_machine/phases/mod.rs 79.51% <27.27%> (-8.16%) ⬇️
rust/src/state_machine/phases/idle.rs 89.09% <81.81%> (-1.82%) ⬇️
rust/src/state_machine/phases/unmask.rs 70.73% <83.33%> (+2.16%) ⬆️
rust/src/metrics/models.rs 95.65% <94.44%> (-4.35%) ⬇️
rust/src/metrics/mod.rs 100.00% <100.00%> (+24.48%) ⬆️
rust/src/state_machine/phases/error.rs 92.85% <100.00%> (+1.94%) ⬆️
rust/src/state_machine/phases/sum.rs 69.84% <100.00%> (+2.04%) ⬆️
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5567db9...7631a06. Read the comment docs.

@rsaffi
Copy link
Contributor

rsaffi commented Aug 10, 2020

#build

let Send { sender, metrics } = parse_macro_input!(input as Send);

TokenStream::from(quote! {
#[cfg(feature = "metrics")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@rsaffi
Copy link
Contributor

rsaffi commented Aug 12, 2020

#build

@little-dude little-dude self-requested a review August 12, 2020 07:46
@rsaffi
Copy link
Contributor

rsaffi commented Aug 12, 2020

#build

1 similar comment
@rsaffi
Copy link
Contributor

rsaffi commented Aug 12, 2020

#build

@Robert-Steiner
Copy link
Contributor Author

Updates:

  • renamed xain-metrics-macro to xaynet-macros (just in case we decide in the future to remove the metrics! macro, we can reuse that crate for other macros)
  • added metrics/macro documentation
  • removed macro example (I embedded the example into the macro documentation)
  • added attributes #![cfg_attr(docsrs, feature(doc_cfg))] and #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))] to rust/src/lib.rs. They trigger that feature note
    Screen Shot 2020-08-12 at 15 00 34

is displayed. It only works if it is built by docs.rs. It can be configured to work locally but it doesn't work nicely with our setup since it is a nightly feature.

@rsaffi
Copy link
Contributor

rsaffi commented Aug 12, 2020

Updates:

  • renamed xain-metrics-macro to xaynet-macros (just in case we decide in the future to remove the metrics! macro, we can reuse that crate for other macros)
  • added metrics/macro documentation
  • removed macro example (I embedded the example into the macro documentation)
  • added attributes #![cfg_attr(docsrs, feature(doc_cfg))] and #[cfg_attr(docsrs, doc(cfg(feature = "metrics")))] to rust/src/lib.rs. They trigger that feature note is displayed. It only works if it is built by docs.rs. It can be configured to work locally but it doesn't work nicely with our setup since it is a nightly feature.

Very, very nice! 👏

@rsaffi
Copy link
Contributor

rsaffi commented Aug 13, 2020

#build

Last one prior to it being merged (but please, if you already feel like merging it, go ahead!)

@little-dude little-dude self-requested a review August 13, 2020 09:10
Copy link
Contributor

@little-dude little-dude left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice. Would be nice to split this in three commits for merging: the macros, the removal of the yaml files, and the actual integration in the state machine.

Copy link
Contributor

@finiteprods finiteprods left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the use of the metrics! macro is very nice indeed!

rust/xaynet-macros/src/lib.rs Outdated Show resolved Hide resolved
rust/src/metrics/service.rs Outdated Show resolved Hide resolved
rust/src/metrics/service.rs Outdated Show resolved Hide resolved
rust/src/metrics/service.rs Outdated Show resolved Hide resolved
@Robert-Steiner Robert-Steiner merged commit af1d766 into master Aug 14, 2020
@Robert-Steiner Robert-Steiner deleted the metrics-state-machine branch August 14, 2020 08:56
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants