tag1consulting / goose

Load testing framework, inspired by Locust
https://tag1.com/goose
Apache License 2.0
809 stars 70 forks source link

Writing custom metrics based on the event in task #396

Open wchmiela opened 2 years ago

wchmiela commented 2 years ago

I'm using different HTTP/2 client (h2 instead of reqwest) because of push promise support, therefore I need to measure some events like pushed data.

In locust.io there was an option to fire certain event using events.request.fire(...) providing request type, response time etc.

In goose, I can see a pub fn set_success(&self, request: &mut GooseRequestMetric) -> GooseTaskResult, which manually marks a request as a success. What is the best approach to mark custom event (which has no origin in built-in HTTP/2 client) and make it visible in the metrics section?

jeremyandrews commented 2 years ago

Are you referring to https://docs.rs/h2/0.3.7/h2/client/index.html?

At this time Goose is very tightly coupled with Reqwest -- the goal is to make it possible to swap it out for other clients in #32 but this hasn't been worked on recently. Request metrics are collected in GooseUser::request, and this is the function that would ultimately need to be made more generic.

In theory you should be able to do the same thing that we do in that function in your own code to generate metrics. You'd need to create a GooseRequestMetric as happens here: https://github.com/tag1consulting/goose/blob/main/src/goose.rs#L1556

Record the response time: https://github.com/tag1consulting/goose/blob/main/src/goose.rs#L1565

Record the response code: https://github.com/tag1consulting/goose/blob/main/src/goose.rs#L1574

Record the final URL (to know if it redirected): https://github.com/tag1consulting/goose/blob/main/src/goose.rs#L1575

Update success to true or false, setting error if there was one. https://github.com/tag1consulting/goose/blob/main/src/goose.rs#L1581

And send the resulting object to the parent: https://github.com/tag1consulting/goose/blob/main/src/goose.rs#L1628

jeremyandrews commented 2 years ago

Note that none of the methods defined on GooseRequestMetric (used above) are public: https://github.com/tag1consulting/goose/blob/main/src/metrics.rs#L293

But all the fields are public, so you can just set/update them directly.

jeremyandrews commented 2 years ago

In writing an Example to demonstrate how this might work, I realized send_request_metric_to_parent() isn't public, so what I describe above isn't going to work. Simply making that function public does make the following possible -- would this be enough to be useful to you? Perhaps if a couple of helpers are added it could be useful enough.

use goose::prelude::*;
use goose::metrics::{GooseRawRequest, GooseRequestMetric};
use isahc::prelude::*;

async fn loadtest_index(user: &mut GooseUser) -> GooseTaskResult {
    let started = std::time::Instant::now();
    let url = "http://example.com/";
    let mut response = isahc::get(url).unwrap();

    let status = response.status();
    let mut headers: Vec<String> = Vec::new();
    for header in response.headers() {
        headers.push(format!("{:?}", header));
    }
    response.consume().unwrap();

    // Record information about the request.
    let raw_request = GooseRawRequest {
        method: GooseMethod::Get,
        url: url.to_string(),
        headers,
        body: "".to_string(),
    };
    let request_metric = GooseRequestMetric {
        elapsed: user.started.elapsed().as_millis() as u64,
        raw: raw_request,
        //@TODO
        name: url.to_string(),
        //@TODO
        final_url: url.to_string(),
        //@TODO
        redirected: false,
        response_time: started.elapsed().as_millis() as u64,
        status_code: status.as_u16(),
        success: status.is_success(),
        update: false,
        user: user.weighted_users_index,
        //@TODO:
        error: "".to_string(),
        //@TODO
        coordinated_omission_elapsed: 0,
        //@TODO
        user_cadence: 0,
    };

    user.send_request_metric_to_parent(request_metric)?;

    Ok(())
}
wchmiela commented 2 years ago

Thanks @jeremyandrews for a quick response. That's exactly what I've implemented. I guess it's a nice way to record any metric.

jeremyandrews commented 2 years ago

There's a work in progress here to better demonstrate with a fully-functional example: https://github.com/tag1consulting/goose/pull/397

My eventual goal is to make changes to Goose that simplify this example, but to begin my goal is simply for it to work without lost functionality.

jeremyandrews commented 2 years ago

One issue I've noticed is that if you manage your own client, then each time a task runs you're essentially building a new client and things like cookies and headers aren't preserved across requests. This means you can't (easily) simulate logging into an application, etc. I do hope to come up with a generic solution in the linked work-in-progress PR.