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:
Filipe Oliveira (Redis) authored and GitHub committed 2026-10-19 11:01:42 +08:00
1 parent d1a17fad78
commit c4706ba71a
10 files changed
+986 -370

No files matched your search

+35 -5
View File
@@ -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
---
+2 -1
View File
@@ -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
+7 -1
View File
@@ -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
+12 -74
View File
@@ -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`.
[![Gitter chat](https://badges.gitter.im/HdrHistogram/HdrHistogram.png)](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).
-146
View File
@@ -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__ */
+709 -85
View File
File diff suppressed because it is too large. Load diff
+56 -16
View File
@@ -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);
-22
View File
@@ -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
View File
@@ -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) {
+61
View File
@@ -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]