mirror of
https://github.com/redis/redis.git
synced 2026-10-28 08:25:04 +08:00
Update HdrHistogram to 0.12.1 and optimize INFO statistics (#15367)
Update the vendored HdrHistogram_c core to [0.12.1](https://github.com/HdrHistogram/HdrHistogram_c/commit/30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0) and reduce the cost of `INFO latencystats` with its single-pass batch percentile API. Preserve configured percentile order and INFO formatting, including command statistics. The dependency is generated from release commit `30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0` using upstream's `script/amalgamate.py --malloc-include hdr_redis_malloc.h`. The generated files have no manual edits. Only the core is vendored; Redis's allocator shim is retained. Both dependency READMEs include the source pin and regeneration instructions. HdrHistogram remains compiled with `-O3`. GCC 4.8 retains the upstream scalar scan fallback (`HDR_DISABLE_AVX2`), while other supported compilers retain runtime AVX2 dispatch where available. The iterator extension is now supplied upstream, and private headers are inlined by the generator. ## INFO changes - Initialize one percentile context per INFO section, shared across command and subcommand histograms. Prepare labels and sorted input once. - Keep up to 16 percentiles in inline stack storage; use overflow-checked heap arrays above 16 and free them once after traversal. All configuration sizes use `qsort`, with the original index breaking ties between equal percentiles. - Query every configured percentile in one histogram pass and emit results in the original order, preserving duplicates and empty configurations. - Use upstream 0.12.1's consistent p0 and empty-histogram behavior, removing the former Redis workarounds. The dependency also fixes p100 rank calculation at very large counts, unsafe rank conversion, and empty statistics. - Render latency values with integer microseconds and three fractional digits. Build commandstats lines without slowlog fields using `sdscatfmt`, retaining `usec_per_call` formatting.
This commit is contained in:
1 parent
d1a17fad78
commit
c4706ba71a
10 files changed
+986
-370
No files matched your search
Vendored
+35
-5
@@ -101,11 +101,41 @@ and our version:
|
||||
Hdr_Histogram
|
||||
---
|
||||
|
||||
Updated source can be found here: https://github.com/HdrHistogram/HdrHistogram_c
|
||||
We use a customized version based on master branch commit e4448cf6d1cd08fff519812d3b1e58bd5a94ac42.
|
||||
1. Compare all changes under /hdr_histogram directory to upstream master commit e4448cf6d1cd08fff519812d3b1e58bd5a94ac42
|
||||
2. Copy updated files from newer version onto files in /hdr_histogram.
|
||||
3. Apply the changes from 1 above to the updated files.
|
||||
We vendor the core amalgamation of [HdrHistogram_c 0.12.1](https://github.com/HdrHistogram/HdrHistogram_c/commit/30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0)
|
||||
(release commit `30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0`). It is used for per-command
|
||||
latency tracking, redis-cli, and redis-benchmark. Only the core is included; the
|
||||
log codec, packed histogram, interval recorder, and phaser are not vendored.
|
||||
|
||||
To upgrade, check out the desired upstream release and run its amalgamation
|
||||
script, replacing `/path/to/redis` with the Redis checkout:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/HdrHistogram/HdrHistogram_c.git
|
||||
cd HdrHistogram_c
|
||||
git checkout 30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0
|
||||
python3 script/amalgamate.py --output /path/to/redis/deps/hdr_histogram \
|
||||
--malloc-include hdr_redis_malloc.h
|
||||
cp LICENSE.txt COPYING.txt /path/to/redis/deps/hdr_histogram/
|
||||
```
|
||||
|
||||
`hdr_histogram.c` and `hdr_histogram.h` are generated files; do not edit them by
|
||||
hand. The private `hdr_atomic.h` and `hdr_tests.h` headers are inlined by the
|
||||
script and are no longer separate files. Keep Redis's `Makefile` and
|
||||
`hdr_redis_malloc.h`, which routes allocations through the Redis allocator. The
|
||||
existing `HDR_MALLOC_INCLUDE` build flag is compatible with the generated default.
|
||||
The Makefile uses `-O3` and selects upstream's scalar percentile fallback for
|
||||
GCC 4.8, whose intrinsic headers cannot support the runtime AVX2 dispatch.
|
||||
|
||||
The former local iterator extension, `hdr_iter_linear_set_value_units_per_bucket`,
|
||||
is now provided upstream. `INFO latencystats` sorts configured percentiles for the
|
||||
upstream batch API once per INFO section, using stack storage for up to 16
|
||||
percentiles and heap storage above that. Version 0.12.1 provides consistent p0
|
||||
and empty-histogram results in the scalar and batch APIs, so no caller
|
||||
workarounds or local library patches are needed.
|
||||
|
||||
After upgrading, update the version and release commit here and in
|
||||
`hdr_histogram/README.md`. Rebuild Redis from a clean tree and run the INFO,
|
||||
latency-monitor, redis-cli, and redis-benchmark tests.
|
||||
|
||||
CRoaring
|
||||
---
|
||||
|
||||
Vendored
+2
-1
@@ -1,5 +1,5 @@
|
||||
The code in this repository code was Written by Gil Tene, Michael Barker,
|
||||
and Matt Warren, and released to the public domain, as explained at
|
||||
Matt Warren, and Philip Orwig, and released to the public domain, as explained at
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
For users of this code who wish to consume it under the "BSD" license
|
||||
@@ -16,6 +16,7 @@ the Author.
|
||||
Copyright (c) 2012, 2013, 2014 Gil Tene
|
||||
Copyright (c) 2014 Michael Barker
|
||||
Copyright (c) 2014 Matt Warren
|
||||
Copyright (c) 2015 Philip Orwig
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
||||
Vendored
+7
-1
@@ -1,8 +1,14 @@
|
||||
STD= -std=c99
|
||||
WARN= -Wall
|
||||
OPT= -Os
|
||||
OPT= -O3
|
||||
|
||||
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS) -DHDR_MALLOC_INCLUDE=\"hdr_redis_malloc.h\"
|
||||
# GCC 4.8's intrinsic headers require global AVX flags, which would bypass
|
||||
# runtime CPU dispatch. Use the upstream scalar fallback on that toolchain.
|
||||
ifneq ($(filter 4.8 4.8.%,$(shell $(CC) -dumpversion 2>/dev/null)),)
|
||||
R_CFLAGS+= -DHDR_DISABLE_AVX2
|
||||
endif
|
||||
|
||||
R_LDFLAGS= $(LDFLAGS)
|
||||
DEBUG= -g
|
||||
|
||||
|
||||
Vendored
+12
-74
@@ -1,78 +1,16 @@
|
||||
HdrHistogram_c: 'C' port of High Dynamic Range (HDR) Histogram
|
||||
# HdrHistogram_c
|
||||
|
||||
HdrHistogram
|
||||
----------------------------------------------
|
||||
This directory vendors the core of [HdrHistogram_c 0.12.1](https://github.com/HdrHistogram/HdrHistogram_c/commit/30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0),
|
||||
release commit `30e9ce6ef5dcdc60f9270e1ca03c0966eaaf71d0`.
|
||||
|
||||
[](https://gitter.im/HdrHistogram/HdrHistogram)
|
||||
`hdr_histogram.c` and `hdr_histogram.h` are generated by the upstream
|
||||
`script/amalgamate.py` with `--malloc-include hdr_redis_malloc.h`. The core includes
|
||||
recording, percentile queries, and iterators. It uses libc and libm (plus libatomic on platforms that require it);
|
||||
the log codec, packed histogram, interval recorder, and phaser are not included.
|
||||
|
||||
This port contains a subset of the functionality supported by the Java
|
||||
implementation. The current supported features are:
|
||||
Redis maintains the `Makefile` and `hdr_redis_malloc.h` allocator shim. There are
|
||||
no hand-maintained changes to the generated library files. See the
|
||||
[dependency upgrade instructions](../README.md#hdr_histogram) to regenerate them.
|
||||
|
||||
* Standard histogram with 64 bit counts (32/16 bit counts not supported)
|
||||
* All iterator types (all values, recorded, percentiles, linear, logarithmic)
|
||||
* Histogram serialisation (encoding version 1.2, decoding 1.0-1.2)
|
||||
* Reader/writer phaser and interval recorder
|
||||
|
||||
Features not supported, but planned
|
||||
|
||||
* Auto-resizing of histograms
|
||||
|
||||
Features unlikely to be implemented
|
||||
|
||||
* Double histograms
|
||||
* Atomic/Concurrent histograms
|
||||
* 16/32 bit histograms
|
||||
|
||||
# Simple Tutorial
|
||||
|
||||
## Recording values
|
||||
|
||||
```C
|
||||
#include <hdr_histogram.h>
|
||||
|
||||
struct hdr_histogram* histogram;
|
||||
|
||||
// Initialise the histogram
|
||||
hdr_init(
|
||||
1, // Minimum value
|
||||
INT64_C(3600000000), // Maximum value
|
||||
3, // Number of significant figures
|
||||
&histogram) // Pointer to initialise
|
||||
|
||||
// Record value
|
||||
hdr_record_value(
|
||||
histogram, // Histogram to record to
|
||||
value) // Value to record
|
||||
|
||||
// Record value n times
|
||||
hdr_record_values(
|
||||
histogram, // Histogram to record to
|
||||
value, // Value to record
|
||||
10) // Record value 10 times
|
||||
|
||||
// Record value with correction for co-ordinated omission.
|
||||
hdr_record_corrected_value(
|
||||
histogram, // Histogram to record to
|
||||
value, // Value to record
|
||||
1000) // Record with expected interval of 1000.
|
||||
|
||||
// Print out the values of the histogram
|
||||
hdr_percentiles_print(
|
||||
histogram,
|
||||
stdout, // File to write to
|
||||
5, // Granularity of printed values
|
||||
1.0, // Multiplier for results
|
||||
CLASSIC); // Format CLASSIC/CSV supported.
|
||||
```
|
||||
|
||||
## More examples
|
||||
|
||||
For more detailed examples of recording and logging results look at the
|
||||
[hdr_decoder](examples/hdr_decoder.c)
|
||||
and [hiccup](examples/hiccup.c)
|
||||
examples. You can run hiccup and decoder
|
||||
and pipe the results of one into the other.
|
||||
|
||||
```
|
||||
$ ./examples/hiccup | ./examples/hdr_decoder
|
||||
```
|
||||
The upstream license texts are in [LICENSE.txt](LICENSE.txt) and
|
||||
[COPYING.txt](COPYING.txt).
|
||||
Vendored
-146
@@ -1,146 +0,0 @@
|
||||
/**
|
||||
* hdr_atomic.h
|
||||
* Written by Philip Orwig and released to the public domain,
|
||||
* as explained at http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
#ifndef HDR_ATOMIC_H__
|
||||
#define HDR_ATOMIC_H__
|
||||
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <intrin.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static void __inline * hdr_atomic_load_pointer(void** pointer)
|
||||
{
|
||||
_ReadBarrier();
|
||||
return *pointer;
|
||||
}
|
||||
|
||||
static void hdr_atomic_store_pointer(void** pointer, void* value)
|
||||
{
|
||||
_WriteBarrier();
|
||||
*pointer = value;
|
||||
}
|
||||
|
||||
static int64_t __inline hdr_atomic_load_64(int64_t* field)
|
||||
{
|
||||
_ReadBarrier();
|
||||
return *field;
|
||||
}
|
||||
|
||||
static void __inline hdr_atomic_store_64(int64_t* field, int64_t value)
|
||||
{
|
||||
_WriteBarrier();
|
||||
*field = value;
|
||||
}
|
||||
|
||||
static int64_t __inline hdr_atomic_exchange_64(volatile int64_t* field, int64_t value)
|
||||
{
|
||||
#if defined(_WIN64)
|
||||
return _InterlockedExchange64(field, value);
|
||||
#else
|
||||
int64_t comparand;
|
||||
int64_t initial_value = *field;
|
||||
do
|
||||
{
|
||||
comparand = initial_value;
|
||||
initial_value = _InterlockedCompareExchange64(field, value, comparand);
|
||||
}
|
||||
while (comparand != initial_value);
|
||||
|
||||
return initial_value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int64_t __inline hdr_atomic_add_fetch_64(volatile int64_t* field, int64_t value)
|
||||
{
|
||||
#if defined(_WIN64)
|
||||
return _InterlockedExchangeAdd64(field, value) + value;
|
||||
#else
|
||||
int64_t comparand;
|
||||
int64_t initial_value = *field;
|
||||
do
|
||||
{
|
||||
comparand = initial_value;
|
||||
initial_value = _InterlockedCompareExchange64(field, comparand + value, comparand);
|
||||
}
|
||||
while (comparand != initial_value);
|
||||
|
||||
return initial_value + value;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool __inline hdr_atomic_compare_exchange_64(volatile int64_t* field, int64_t* expected, int64_t desired)
|
||||
{
|
||||
return *expected == _InterlockedCompareExchange64(field, desired, *expected);
|
||||
}
|
||||
|
||||
#elif defined(__ATOMIC_SEQ_CST)
|
||||
|
||||
#define hdr_atomic_load_pointer(x) __atomic_load_n(x, __ATOMIC_SEQ_CST)
|
||||
#define hdr_atomic_store_pointer(f,v) __atomic_store_n(f,v, __ATOMIC_SEQ_CST)
|
||||
#define hdr_atomic_load_64(x) __atomic_load_n(x, __ATOMIC_SEQ_CST)
|
||||
#define hdr_atomic_store_64(f,v) __atomic_store_n(f,v, __ATOMIC_SEQ_CST)
|
||||
#define hdr_atomic_exchange_64(f,i) __atomic_exchange_n(f,i, __ATOMIC_SEQ_CST)
|
||||
#define hdr_atomic_add_fetch_64(field, value) __atomic_add_fetch(field, value, __ATOMIC_SEQ_CST)
|
||||
#define hdr_atomic_compare_exchange_64(field, expected, desired) __atomic_compare_exchange_n(field, expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
|
||||
|
||||
#elif defined(__x86_64__)
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
static inline void* hdr_atomic_load_pointer(void** pointer)
|
||||
{
|
||||
void* p = *pointer;
|
||||
asm volatile ("" ::: "memory");
|
||||
return p;
|
||||
}
|
||||
|
||||
static inline void hdr_atomic_store_pointer(void** pointer, void* value)
|
||||
{
|
||||
asm volatile ("lock; xchgq %0, %1" : "+q" (value), "+m" (*pointer));
|
||||
}
|
||||
|
||||
static inline int64_t hdr_atomic_load_64(int64_t* field)
|
||||
{
|
||||
int64_t i = *field;
|
||||
asm volatile ("" ::: "memory");
|
||||
return i;
|
||||
}
|
||||
|
||||
static inline void hdr_atomic_store_64(int64_t* field, int64_t value)
|
||||
{
|
||||
asm volatile ("lock; xchgq %0, %1" : "+q" (value), "+m" (*field));
|
||||
}
|
||||
|
||||
static inline int64_t hdr_atomic_exchange_64(volatile int64_t* field, int64_t value)
|
||||
{
|
||||
int64_t result = 0;
|
||||
asm volatile ("lock; xchgq %1, %2" : "=r" (result), "+q" (value), "+m" (*field));
|
||||
return result;
|
||||
}
|
||||
|
||||
static inline int64_t hdr_atomic_add_fetch_64(volatile int64_t* field, int64_t value)
|
||||
{
|
||||
return __sync_add_and_fetch(field, value);
|
||||
}
|
||||
|
||||
static inline bool hdr_atomic_compare_exchange_64(volatile int64_t* field, int64_t* expected, int64_t desired)
|
||||
{
|
||||
int64_t original;
|
||||
asm volatile( "lock; cmpxchgq %2, %1" : "=a"(original), "+m"(*field) : "q"(desired), "0"(*expected));
|
||||
return original == *expected;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#error "Unable to determine atomic operations for your platform"
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* HDR_ATOMIC_H__ */
|
||||
Vendored
+709
-85
File diff suppressed because it is too large.
Load diff
Vendored
+56
-16
@@ -110,6 +110,22 @@ size_t hdr_get_memory_size(struct hdr_histogram* h);
|
||||
*/
|
||||
bool hdr_record_value(struct hdr_histogram* h, int64_t value);
|
||||
|
||||
/**
|
||||
* Like hdr_record_value, but clamps the value into [0, highest_trackable_value]
|
||||
* instead of rejecting out-of-range input. 0 and values below
|
||||
* lowest_discernible_value are recorded as they are.
|
||||
*
|
||||
* @param h "This" pointer
|
||||
* @param value Value to add to the histogram
|
||||
* @return true for any value on a valid histogram.
|
||||
*/
|
||||
bool hdr_record_value_capped(struct hdr_histogram* h, int64_t value);
|
||||
|
||||
/**
|
||||
* Atomic version of hdr_record_value_capped, safe to call from several threads at once.
|
||||
*/
|
||||
bool hdr_record_value_capped_atomic(struct hdr_histogram* h, int64_t value);
|
||||
|
||||
/**
|
||||
* Records a value in the histogram, will round this value of to a precision at or better
|
||||
* than the significant_figure specified at construction time.
|
||||
@@ -132,9 +148,9 @@ bool hdr_record_value_atomic(struct hdr_histogram* h, int64_t value);
|
||||
*
|
||||
* @param h "This" pointer
|
||||
* @param value Value to add to the histogram
|
||||
* @param count Number of 'value's to add to the histogram
|
||||
* @return false if any value is larger than the highest_trackable_value and can't be recorded,
|
||||
* true otherwise.
|
||||
* @param count Number of 'value's to add to the histogram; must be non-negative
|
||||
* @return false if count is negative or any value is larger than the highest_trackable_value
|
||||
* and can't be recorded, true otherwise.
|
||||
*/
|
||||
bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count);
|
||||
|
||||
@@ -149,9 +165,9 @@ bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count);
|
||||
*
|
||||
* @param h "This" pointer
|
||||
* @param value Value to add to the histogram
|
||||
* @param count Number of 'value's to add to the histogram
|
||||
* @return false if any value is larger than the highest_trackable_value and can't be recorded,
|
||||
* true otherwise.
|
||||
* @param count Number of 'value's to add to the histogram; must be non-negative
|
||||
* @return false if count is negative or any value is larger than the highest_trackable_value
|
||||
* and can't be recorded, true otherwise.
|
||||
*/
|
||||
bool hdr_record_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count);
|
||||
|
||||
@@ -249,8 +265,9 @@ int64_t hdr_add_while_correcting_for_coordinated_omission(
|
||||
struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval);
|
||||
|
||||
/**
|
||||
* Get minimum value from the histogram. Will return 2^63-1 if the histogram
|
||||
* is empty.
|
||||
* Get minimum value from the histogram. Will return 0 if the histogram is
|
||||
* empty, like hdr_max; a recorded 0 also gives 0, so use hdr_total_count to
|
||||
* tell the two apart.
|
||||
*
|
||||
* @param h "This" pointer
|
||||
*/
|
||||
@@ -264,11 +281,22 @@ int64_t hdr_min(const struct hdr_histogram* h);
|
||||
*/
|
||||
int64_t hdr_max(const struct hdr_histogram* h);
|
||||
|
||||
/**
|
||||
* Get the total number of recorded values. Returns 0 if h is NULL. Uses an atomic
|
||||
* load, so it can be called while other threads use the *_atomic record functions.
|
||||
*
|
||||
* @param h "This" pointer
|
||||
*/
|
||||
int64_t hdr_total_count(const struct hdr_histogram* h);
|
||||
|
||||
/**
|
||||
* Get the value at a specific percentile.
|
||||
*
|
||||
* @param h "This" pointer.
|
||||
* @param percentile The percentile to get the value for
|
||||
* @param percentile The percentile to get the value for. Above 100 gives the value at
|
||||
* 100; below 0 and -inf give the first recorded value. Exactly 0 returns the lowest
|
||||
* equivalent value of that bucket.
|
||||
* @return the value at the percentile, or 0 if no values have been recorded.
|
||||
*/
|
||||
int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile);
|
||||
|
||||
@@ -276,11 +304,14 @@ int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile
|
||||
* Get the values at the given percentiles.
|
||||
*
|
||||
* @param h "This" pointer.
|
||||
* @param percentiles The ordered percentiles array to get the values for.
|
||||
* @param percentiles The ordered percentiles array to get the values for, with the
|
||||
* same out-of-range handling as hdr_value_at_percentile.
|
||||
* @param length Number of elements in the arrays.
|
||||
* @param values Destination array containing the values at the given percentiles.
|
||||
* The values array should be allocated by the caller.
|
||||
* @return 0 on success, ENOMEM if the provided destination array is null.
|
||||
* Percentiles that cannot be resolved get the same value hdr_value_at_percentile
|
||||
* returns for them, so all are 0 on an empty histogram.
|
||||
* @return 0 on success, EINVAL if percentiles or values is null.
|
||||
*/
|
||||
int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percentiles, int64_t *values, size_t length);
|
||||
|
||||
@@ -288,7 +319,7 @@ int hdr_value_at_percentiles(const struct hdr_histogram *h, const double *percen
|
||||
* Gets the standard deviation for the values in the histogram.
|
||||
*
|
||||
* @param h "This" pointer
|
||||
* @return The standard deviation
|
||||
* @return The standard deviation, or 0.0 if the histogram is empty.
|
||||
*/
|
||||
double hdr_stddev(const struct hdr_histogram* h);
|
||||
|
||||
@@ -296,7 +327,7 @@ double hdr_stddev(const struct hdr_histogram* h);
|
||||
* Gets the mean for the values in the histogram.
|
||||
*
|
||||
* @param h "This" pointer
|
||||
* @return The mean
|
||||
* @return The mean, or 0.0 if the histogram is empty.
|
||||
*/
|
||||
double hdr_mean(const struct hdr_histogram* h);
|
||||
|
||||
@@ -433,12 +464,20 @@ void hdr_iter_linear_init(
|
||||
int64_t value_units_per_bucket);
|
||||
|
||||
/**
|
||||
* Update the iterator value units per bucket
|
||||
* Change the bucket width of a linear iterator, e.g. to widen buckets once past a
|
||||
* region of interest. The next bucket was already scheduled with the old width, so
|
||||
* the new width applies from the bucket after it. Does nothing for an iterator that
|
||||
* was not initialised with hdr_iter_linear_init.
|
||||
*/
|
||||
void hdr_iter_linear_set_value_units_per_bucket(struct hdr_iter* iter, int64_t value_units_per_bucket);
|
||||
|
||||
/**
|
||||
* Initialise the iterator for use with logarithmic values
|
||||
* Initialise the iterator for use with logarithmic values.
|
||||
*
|
||||
* log_base is applied as an integer step (level *= (int64_t) log_base): a
|
||||
* fractional base truncates toward zero (2.5 behaves as 2), and any base <= 1 --
|
||||
* including 1 < base < 2, which truncates to 1 -- terminates after the first
|
||||
* level. Fractional bases are not supported (the Java reference iterates in double).
|
||||
*/
|
||||
void hdr_iter_log_init(
|
||||
struct hdr_iter* iter,
|
||||
@@ -510,7 +549,8 @@ int64_t hdr_median_equivalent_value(const struct hdr_histogram* h, int64_t value
|
||||
|
||||
/**
|
||||
* Used to reset counters after importing data manually into the histogram, used by the logging code
|
||||
* and other custom serialisation tools.
|
||||
* and other custom serialisation tools. The positive-count total saturates at
|
||||
* INT64_MAX if it cannot be represented; the stored counts are unchanged.
|
||||
*/
|
||||
void hdr_reset_internal_counters(struct hdr_histogram* h);
|
||||
|
||||
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
#ifndef HDR_TESTS_H
|
||||
#define HDR_TESTS_H
|
||||
|
||||
/* These are functions used in tests and are not intended for normal usage. */
|
||||
|
||||
#include "hdr_histogram.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
int32_t counts_index_for(const struct hdr_histogram* h, int64_t value);
|
||||
int hdr_encode_compressed(struct hdr_histogram* h, uint8_t** compressed_histogram, size_t* compressed_len);
|
||||
int hdr_decode_compressed(uint8_t* buffer, size_t length, struct hdr_histogram** histogram);
|
||||
void hdr_base64_decode_block(const char* input, uint8_t* output);
|
||||
void hdr_base64_encode_block(const uint8_t* input, char* output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+104
-20
@@ -6284,20 +6284,90 @@ void bytesToHuman(char *s, size_t size, unsigned long long n) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Fill percentile distribution of latencies. */
|
||||
sds fillPercentileDistributionLatencies(sds info, const char* histogram_name, struct hdr_histogram* histogram) {
|
||||
/* Keep the usual percentile configuration on the stack. Larger configurations
|
||||
* allocate once per INFO section, reusing the buffers for every histogram. */
|
||||
#define LATENCY_PERCENTILE_STACK_MAX 16
|
||||
|
||||
typedef struct latencyPercentile {
|
||||
double percentile;
|
||||
int index;
|
||||
char label[16]; /* Configured percentiles are in [0, 100], formatted with %f. */
|
||||
} latencyPercentile;
|
||||
|
||||
typedef struct latencyPercentileContext {
|
||||
int len;
|
||||
latencyPercentile *entries;
|
||||
double *percentiles;
|
||||
int64_t *values;
|
||||
int *order; /* Configured index -> sorted index. */
|
||||
latencyPercentile stack_entries[LATENCY_PERCENTILE_STACK_MAX];
|
||||
double stack_percentiles[LATENCY_PERCENTILE_STACK_MAX];
|
||||
int64_t stack_values[LATENCY_PERCENTILE_STACK_MAX];
|
||||
int stack_order[LATENCY_PERCENTILE_STACK_MAX];
|
||||
} latencyPercentileContext;
|
||||
|
||||
static int compareLatencyPercentiles(const void *a, const void *b) {
|
||||
const latencyPercentile *pa = a, *pb = b;
|
||||
if (pa->percentile < pb->percentile) return -1;
|
||||
if (pa->percentile > pb->percentile) return 1;
|
||||
return (pa->index > pb->index) - (pa->index < pb->index);
|
||||
}
|
||||
|
||||
static void initLatencyPercentileContext(latencyPercentileContext *ctx, const double *percentiles, int len) {
|
||||
ctx->len = len;
|
||||
if (len <= LATENCY_PERCENTILE_STACK_MAX) {
|
||||
ctx->entries = ctx->stack_entries;
|
||||
ctx->percentiles = ctx->stack_percentiles;
|
||||
ctx->values = ctx->stack_values;
|
||||
ctx->order = ctx->stack_order;
|
||||
} else {
|
||||
ctx->entries = zcalloc_num(len, sizeof(*ctx->entries));
|
||||
ctx->percentiles = zcalloc_num(len, sizeof(*ctx->percentiles));
|
||||
ctx->values = zcalloc_num(len, sizeof(*ctx->values));
|
||||
ctx->order = zcalloc_num(len, sizeof(*ctx->order));
|
||||
}
|
||||
for (int j = 0; j < len; j++) {
|
||||
ctx->entries[j].percentile = percentiles[j];
|
||||
ctx->entries[j].index = j;
|
||||
size_t l = snprintf(ctx->entries[j].label, sizeof(ctx->entries[j].label), "%f", percentiles[j]);
|
||||
trimDoubleString(ctx->entries[j].label, l);
|
||||
}
|
||||
/* Sort once for all command and subcommand histograms. */
|
||||
qsort(ctx->entries, len, sizeof(*ctx->entries), compareLatencyPercentiles);
|
||||
for (int j = 0; j < len; j++) {
|
||||
ctx->percentiles[j] = ctx->entries[j].percentile;
|
||||
ctx->order[ctx->entries[j].index] = j;
|
||||
}
|
||||
}
|
||||
|
||||
static void freeLatencyPercentileContext(latencyPercentileContext *ctx) {
|
||||
if (ctx->len > LATENCY_PERCENTILE_STACK_MAX) {
|
||||
zfree(ctx->entries);
|
||||
zfree(ctx->percentiles);
|
||||
zfree(ctx->values);
|
||||
zfree(ctx->order);
|
||||
}
|
||||
}
|
||||
|
||||
/* Resolve all configured percentiles in one histogram pass, then emit them in
|
||||
* the configured order. HdrHistogram 0.12.1 handles p0 and empty histograms. */
|
||||
static sds fillPercentileDistributionLatencies(sds info, const char* histogram_name, struct hdr_histogram* histogram, latencyPercentileContext *ctx) {
|
||||
info = sdscatfmt(info,"latency_percentiles_usec_%s:",histogram_name);
|
||||
for (int j = 0; j < server.latency_tracking_info_percentiles_len; j++) {
|
||||
char fbuf[128];
|
||||
size_t len = snprintf(fbuf, sizeof(fbuf), "%f", server.latency_tracking_info_percentiles[j]);
|
||||
trimDoubleString(fbuf, len);
|
||||
info = sdscatprintf(info,"p%s=%.3f", fbuf,
|
||||
((double)hdr_value_at_percentile(histogram,server.latency_tracking_info_percentiles[j]))/1000.0f);
|
||||
if (j != server.latency_tracking_info_percentiles_len-1)
|
||||
if (ctx->len > 0)
|
||||
hdr_value_at_percentiles(histogram, ctx->percentiles, ctx->values, ctx->len);
|
||||
|
||||
for (int j = 0; j < ctx->len; j++) {
|
||||
int index = ctx->order[j];
|
||||
int64_t v = ctx->values[index];
|
||||
/* Latencies are bounded to 1e9 nanoseconds. Integer formatting of
|
||||
* microseconds matches %.3f within that range, including bucket rounding,
|
||||
* while avoiding the floating-point formatter for every histogram. */
|
||||
info = sdscatprintf(info,"p%s=%lld.%03lld", ctx->entries[index].label,
|
||||
(long long)(v / 1000), (long long)(v % 1000));
|
||||
if (j != ctx->len-1)
|
||||
info = sdscatlen(info,",",1);
|
||||
}
|
||||
info = sdscatprintf(info,"\r\n");
|
||||
return info;
|
||||
}
|
||||
return sdscatlen(info,"\r\n",2);
|
||||
}
|
||||
|
||||
const char *replstateToString(int replstate) {
|
||||
@@ -6423,12 +6493,16 @@ sds genRedisInfoStringCommandStats(sds info, dict *commands) {
|
||||
c->slowlog_count, (double)c->slowlog_time_us_sum / 1000,
|
||||
(double)c->slowlog_time_us_max / 1000);
|
||||
} else {
|
||||
info = sdscatprintf(info,
|
||||
"cmdstat_%s:calls=%lld,usec=%lld,usec_per_call=%.2f"
|
||||
",rejected_calls=%lld,failed_calls=%lld\r\n",
|
||||
/* Hot path: sdscatfmt is cheaper than sdscatprintf but has no %f,
|
||||
* so format usec_per_call separately. */
|
||||
char upc[64];
|
||||
snprintf(upc, sizeof(upc), "%.2f",
|
||||
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls));
|
||||
info = sdscatfmt(info,
|
||||
"cmdstat_%s:calls=%I,usec=%I,usec_per_call=%s"
|
||||
",rejected_calls=%I,failed_calls=%I\r\n",
|
||||
getSafeInfoString(c->fullname, sdslen(c->fullname), &tmpsafe), c->calls, c->microseconds,
|
||||
(c->calls == 0) ? 0 : ((float)c->microseconds/c->calls),
|
||||
c->rejected_calls, c->failed_calls);
|
||||
upc, c->rejected_calls, c->failed_calls);
|
||||
}
|
||||
if (tmpsafe != NULL) zfree(tmpsafe);
|
||||
}
|
||||
@@ -6457,7 +6531,8 @@ sds genRedisInfoStringACLStats(sds info) {
|
||||
return info;
|
||||
}
|
||||
|
||||
sds genRedisInfoStringLatencyStats(sds info, dict *commands) {
|
||||
/* Reuse the context across command and subcommand histograms. */
|
||||
static sds genRedisInfoStringLatencyStatsImpl(sds info, dict *commands, latencyPercentileContext *ctx) {
|
||||
struct redisCommand *c;
|
||||
dictEntry *de;
|
||||
dictIterator di;
|
||||
@@ -6468,11 +6543,11 @@ sds genRedisInfoStringLatencyStats(sds info, dict *commands) {
|
||||
if (c->latency_histogram) {
|
||||
info = fillPercentileDistributionLatencies(info,
|
||||
getSafeInfoString(c->fullname, sdslen(c->fullname), &tmpsafe),
|
||||
c->latency_histogram);
|
||||
c->latency_histogram, ctx);
|
||||
if (tmpsafe != NULL) zfree(tmpsafe);
|
||||
}
|
||||
if (c->subcommands_dict) {
|
||||
info = genRedisInfoStringLatencyStats(info, c->subcommands_dict);
|
||||
info = genRedisInfoStringLatencyStatsImpl(info, c->subcommands_dict, ctx);
|
||||
}
|
||||
}
|
||||
dictResetIterator(&di);
|
||||
@@ -6480,6 +6555,15 @@ sds genRedisInfoStringLatencyStats(sds info, dict *commands) {
|
||||
return info;
|
||||
}
|
||||
|
||||
sds genRedisInfoStringLatencyStats(sds info, dict *commands) {
|
||||
latencyPercentileContext ctx;
|
||||
initLatencyPercentileContext(&ctx, server.latency_tracking_info_percentiles,
|
||||
server.latency_tracking_info_percentiles_len);
|
||||
info = genRedisInfoStringLatencyStatsImpl(info, commands, &ctx);
|
||||
freeLatencyPercentileContext(&ctx);
|
||||
return info;
|
||||
}
|
||||
|
||||
/* Takes a null terminated sections list, and adds them to the dict. */
|
||||
void addInfoSectionsToDict(dict *section_dict, char **sections) {
|
||||
while (*sections) {
|
||||
|
||||
@@ -114,6 +114,67 @@ start_server {tags {"info" "external:skip"}} {
|
||||
assert {$p50_debug >= $p50_set}
|
||||
} {} {needs:debug}
|
||||
|
||||
test {latencystats: percentiles configured out of order (incl p0/p100)} {
|
||||
r config resetstat
|
||||
r CONFIG SET latency-tracking yes
|
||||
# Out-of-order list including p0 and p100 exercises the single-pass
|
||||
# batch percentile resolver's sort/scatter and the p0->lowest /
|
||||
# else->highest equivalent-value handling. The emitted values must
|
||||
# stay consistent with the per-percentile path: monotonic
|
||||
# non-decreasing by percentile regardless of the configured order.
|
||||
r CONFIG SET latency-tracking-info-percentiles "100.0 0.0 99.9 50.0"
|
||||
# Build a real spread in the debug-command histogram (fast + slow).
|
||||
for {set i 0} {$i < 20} {incr i} { r debug sleep 0 }
|
||||
r debug sleep 0.03
|
||||
r debug sleep 0.05
|
||||
set line [latency_percentiles_usec debug]
|
||||
# Labels emitted in the configured (unordered) order.
|
||||
assert_match {*p100=*p0=*p99.9=*p50=*} $line
|
||||
assert {[regexp {p0=([0-9.]+)} $line -> p0]}
|
||||
assert {[regexp {p50=([0-9.]+)} $line -> p50]}
|
||||
assert {[regexp {p99\.9=([0-9.]+)} $line -> p999]}
|
||||
assert {[regexp {p100=([0-9.]+)} $line -> p100]}
|
||||
# Monotonic by percentile despite the unordered emission order
|
||||
# (a sort/scatter mismatch between label and value would break this).
|
||||
assert {$p0 <= $p50}
|
||||
assert {$p50 <= $p999}
|
||||
assert {$p999 <= $p100}
|
||||
# The spread is real: p100 reflects the ~50ms samples, well above p0.
|
||||
assert {$p100 >= 30000}
|
||||
assert {$p0 < $p100}
|
||||
} {} {needs:debug}
|
||||
|
||||
test {latencystats: stack and heap percentile contexts preserve output} {
|
||||
r config resetstat
|
||||
r CONFIG SET latency-tracking yes
|
||||
# Keep this histogram unchanged while testing different configs.
|
||||
# Millisecond samples have wide buckets, exposing a p0 boundary error.
|
||||
r debug sleep 0.005
|
||||
r debug sleep 0.015
|
||||
r debug sleep 0.03
|
||||
foreach percentiles {
|
||||
{0}
|
||||
{100 0 99.9 50 0 50}
|
||||
{100 0 99.9 50 1 2 3 4 5 6 7 8 9 10 25 75}
|
||||
} {
|
||||
r CONFIG SET latency-tracking-info-percentiles $percentiles
|
||||
set batch [split [latency_percentiles_usec debug] ,]
|
||||
assert_equal [llength $percentiles] [llength $batch]
|
||||
# Cross the stack/heap boundary while keeping the same prefix.
|
||||
foreach length {17 64} {
|
||||
set padded $percentiles
|
||||
while {[llength $padded] < $length} { lappend padded 100 }
|
||||
r CONFIG SET latency-tracking-info-percentiles $padded
|
||||
set heap [split [latency_percentiles_usec debug] ,]
|
||||
assert_equal $length [llength $heap]
|
||||
assert_equal $batch [lrange $heap 0 [expr {[llength $batch] - 1}]]
|
||||
}
|
||||
}
|
||||
r CONFIG SET latency-tracking-info-percentiles ""
|
||||
assert_equal {} [latency_percentiles_usec debug]
|
||||
assert_equal OK [r CONFIG SET latency-tracking-info-percentiles "50 99 99.9"]
|
||||
} {} {needs:debug}
|
||||
|
||||
test {errorstats: failed call authentication error} {
|
||||
r config resetstat
|
||||
assert_match {} [errorstat ERR]
|
||||
|
||||
Reference in new issue
Block a user