3891: Fix the way we compute the 99th percentile r=dureuill a=Kerollmops

This PR fixes how we compute the 99th percentile by avoiding using float and doing the multiplication and divisions in the correct order avoiding going out of the buffer of timings. You can see the issue on [this rust playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021).

When there are a very small number of successful requests, the number is so tiny that the 99th percentile calculus sometimes gives an index out of the buffer. In this example, the `1`/`1.0` represent the number of timings you collected (one). As you can see, the float computation gives us the index `1.0`, with is out of a vector of only one value. This makes the engine generate a `null` value.

```rust
1 * 99 / 100 = 0 // with integers
0.99_f64 * (1.0 - 1.0) + 1.0 = 1.0 // with floats
```

Co-authored-by: Clément Renault <clement@meilisearch.com>
This commit is contained in:
meili-bors[bot] 2023-07-06 06:04:08 +00:00 committed by GitHub
commit 886c8bb647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -761,17 +761,17 @@ impl SearchAggregator {
if self.total_received == 0 { if self.total_received == 0 {
None None
} else { } else {
// the index of the 99th percentage of value
let percentile_99th = 0.99 * (self.total_succeeded as f64 - 1.) + 1.;
// we get all the values in a sorted manner // we get all the values in a sorted manner
let time_spent = self.time_spent.into_sorted_vec(); let time_spent = self.time_spent.into_sorted_vec();
// the index of the 99th percentage of value
let percentile_99th = time_spent.len() * 99 / 100;
// We are only interested by the slowest value of the 99th fastest results // We are only interested by the slowest value of the 99th fastest results
let time_spent = time_spent.get(percentile_99th as usize); let time_spent = time_spent.get(percentile_99th);
let properties = json!({ let properties = json!({
"user-agent": self.user_agents, "user-agent": self.user_agents,
"requests": { "requests": {
"99th_response_time": time_spent.map(|t| format!("{:.2}", t)), "99th_response_time": time_spent.map(|t| format!("{:.2}", t)),
"total_succeeded": self.total_succeeded, "total_succeeded": self.total_succeeded,
"total_failed": self.total_received.saturating_sub(self.total_succeeded), // just to be sure we never panics "total_failed": self.total_received.saturating_sub(self.total_succeeded), // just to be sure we never panics
"total_received": self.total_received, "total_received": self.total_received,