/* Copyright (C) 2011-2018 Alexey Kopytov. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #ifdef STDC_HEADERS # include # include #endif #ifdef HAVE_STRING_H # include #endif #ifdef HAVE_MATH_H # include #endif #include "sysbench.h" #include "sb_histogram.h" #include "sb_logger.h" #include "sb_rand.h" #include "sb_ck_pr.h" #include "ck_cc.h" #include "sb_util.h" /* Number of slots for current histogram array. TODO: replace this constant with some autodetection code based on the number of available cores or some such. */ #define SB_HISTOGRAM_NSLOTS 128 /* Global latency histogram */ sb_histogram_t sb_latency_histogram CK_CC_CACHELINE; int sb_histogram_init(sb_histogram_t *h, size_t size, double range_min, double range_max) { size_t i; uint64_t *tmp; /* Allocate memory for cumulative_array + temp_array + all slot arrays */ tmp = (uint64_t *) calloc(size * (SB_HISTOGRAM_NSLOTS + 2), sizeof(uint64_t)); h->interm_slots = (uint64_t **) malloc(SB_HISTOGRAM_NSLOTS * sizeof(uint64_t *)); if (tmp == NULL || h->interm_slots == NULL) { log_text(LOG_FATAL, "Failed to allocate memory for a histogram object, size = %zd", size); return 1; } h->cumulative_array = tmp; tmp += size; h->temp_array = tmp; tmp += size; for (i = 0; i < SB_HISTOGRAM_NSLOTS; i++) { h->interm_slots[i] = tmp; tmp += size; } h->range_deduct = log(range_min); h->range_mult = (size - 1) / (log(range_max) - h->range_deduct); h->range_min = range_min; h->range_max = range_max; h->array_size = size; pthread_rwlock_init(&h->lock, NULL); return 0; } void sb_histogram_update(sb_histogram_t *h, double value) { size_t slot; ssize_t i; slot = sb_rand_uniform_uint64() % SB_HISTOGRAM_NSLOTS; i = floor((log(value) - h->range_deduct) * h->range_mult + 0.5); if (SB_UNLIKELY(i < 0)) i = 0; else if (SB_UNLIKELY(i >= (ssize_t) (h->array_size))) i = h->array_size - 1; ck_pr_inc_64(&h->interm_slots[slot][i]); } double sb_histogram_get_pct_intermediate(sb_histogram_t *h, double percentile) { size_t i, s; uint64_t nevents, ncur, nmax; double res; nevents = 0; /* This can be called concurrently with other sb_histogram_get_pct_*() functions, so use the lock to protect shared structures. This will not block sb_histogram_update() calls, but we make sure we don't lose any concurrent increments by atomically fetching each array element and replacing it with 0. */ pthread_rwlock_wrlock(&h->lock); /* Merge intermediate slots into temp_array. */ const size_t size = h->array_size; uint64_t * const array = h->temp_array; for (i = 0; i < size; i++) { array[i] = ck_pr_fas_64(&h->interm_slots[0][i], 0); nevents += array[i]; } for (s = 1; s < SB_HISTOGRAM_NSLOTS; s++) { for (i = 0; i < size; i++) { uint64_t t; t = ck_pr_fas_64(&h->interm_slots[s][i], 0); array[i] += t; nevents += t; } } /* Now that we have an aggregate 'snapshot' of current arrays and the total number of events in it, calculate the current, intermediate percentile value to return. */ nmax = floor(nevents * percentile / 100 + 0.5); ncur = 0; for (i = 0; i < size; i++) { ncur += array[i]; if (ncur >= nmax) break; } res = exp(i / h->range_mult + h->range_deduct); /* Finally, add temp_array into accumulated values in cumulative_array. */ for (i = 0; i < size; i++) { h->cumulative_array[i] += array[i]; } h->cumulative_nevents += nevents; pthread_rwlock_unlock(&h->lock); return res; } /* Aggregate arrays from intermediate slots into cumulative_array. This should be called with the histogram lock write-locked. */ static void merge_intermediate_into_cumulative(sb_histogram_t *h) { size_t i, s; uint64_t nevents; nevents = h->cumulative_nevents; const size_t size = h->array_size; uint64_t * const array = h->cumulative_array; for (s = 0; s < SB_HISTOGRAM_NSLOTS; s++) { for (i = 0; i < size; i++) { uint64_t t = ck_pr_fas_64(&h->interm_slots[s][i], 0); array[i] += t; nevents += t; } } h->cumulative_nevents = nevents; } /* Calculate a given percentile from the cumulative array. This should be called with the histogram lock either read- or write-locked. */ static double get_pct_cumulative(sb_histogram_t *h, double percentile) { size_t i; uint64_t ncur, nmax; nmax = floor(h->cumulative_nevents * percentile / 100 + 0.5); ncur = 0; for (i = 0; i < h->array_size; i++) { ncur += h->cumulative_array[i]; if (ncur >= nmax) break; } return exp(i / h->range_mult + h->range_deduct); } double sb_histogram_get_pct_cumulative(sb_histogram_t *h, double percentile) { double res; /* This can be called concurrently with other sb_histogram_get_pct_*() functions, so use the lock to protect shared structures. This will not block sb_histogram_update() calls, but we make sure we don't lose any concurrent increments by atomically fetching each array element and replacing it with 0. */ pthread_rwlock_wrlock(&h->lock); merge_intermediate_into_cumulative(h); res = get_pct_cumulative(h, percentile); pthread_rwlock_unlock(&h->lock); return res; } double sb_histogram_get_pct_checkpoint(sb_histogram_t *h, double percentile) { double res; /* This can be called concurrently with other sb_histogram_get_pct_*() functions, so use the lock to protect shared structures. This will not block sb_histogram_update() calls, but we make sure we don't lose any concurrent increments by atomically fetching each array element and replacing it with 0. */ pthread_rwlock_wrlock(&h->lock); merge_intermediate_into_cumulative(h); res = get_pct_cumulative(h, percentile); /* Reset the cumulative array */ memset(h->cumulative_array, 0, h->array_size * sizeof(uint64_t)); h->cumulative_nevents = 0; pthread_rwlock_unlock(&h->lock); return res; } void sb_histogram_print(sb_histogram_t *h) { uint64_t maxcnt; int width; size_t i; pthread_rwlock_wrlock(&h->lock); merge_intermediate_into_cumulative(h); uint64_t * const array = h->cumulative_array; maxcnt = 0; for (i = 0; i < h->array_size; i++) { if (array[i] > maxcnt) maxcnt = array[i]; } if (maxcnt == 0) return; printf(" value ------------- distribution ------------- count\n"); for (i = 0; i < h->array_size; i++) { if (array[i] == 0) continue; width = floor(array[i] * (double) 40 / maxcnt + 0.5); printf("%12.3f |%-40.*s %lu\n", exp(i / h->range_mult + h->range_deduct), /* value */ width, "****************************************", /* distribution */ (unsigned long) array[i]); /* count */ } pthread_rwlock_unlock(&h->lock); } void sb_histogram_done(sb_histogram_t *h) { pthread_rwlock_destroy(&h->lock); free(h->cumulative_array); free(h->interm_slots); } /* Allocate a new histogram and initialize it with sb_histogram_init(). */ sb_histogram_t *sb_histogram_new(size_t size, double range_min, double range_max) { sb_histogram_t *h; if ((h = malloc(sizeof(*h))) == NULL) return NULL; if (sb_histogram_init(h, size, range_min, range_max)) { free(h); return NULL; } return h; } /* Deallocate a histogram allocated with sb_histogram_new(). */ void sb_histogram_delete(sb_histogram_t *h) { sb_histogram_done(h); free(h); }