OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * |
| 3 * Copyright 2015, Google Inc. |
| 4 * All rights reserved. |
| 5 * |
| 6 * Redistribution and use in source and binary forms, with or without |
| 7 * modification, are permitted provided that the following conditions are |
| 8 * met: |
| 9 * |
| 10 * * Redistributions of source code must retain the above copyright |
| 11 * notice, this list of conditions and the following disclaimer. |
| 12 * * Redistributions in binary form must reproduce the above |
| 13 * copyright notice, this list of conditions and the following disclaimer |
| 14 * in the documentation and/or other materials provided with the |
| 15 * distribution. |
| 16 * * Neither the name of Google Inc. nor the names of its |
| 17 * contributors may be used to endorse or promote products derived from |
| 18 * this software without specific prior written permission. |
| 19 * |
| 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 * |
| 32 */ |
| 33 |
| 34 #include "src/core/statistics/window_stats.h" |
| 35 #include <math.h> |
| 36 #include <stddef.h> |
| 37 #include <string.h> |
| 38 #include <grpc/support/alloc.h> |
| 39 #include <grpc/support/log.h> |
| 40 #include <grpc/support/time.h> |
| 41 #include <grpc/support/useful.h> |
| 42 |
| 43 /* typedefs make typing long names easier. Use cws (for census_window_stats) */ |
| 44 typedef census_window_stats_stat_info cws_stat_info; |
| 45 typedef struct census_window_stats_sum cws_sum; |
| 46 |
| 47 /* Each interval is composed of a number of buckets, which hold a count of |
| 48 entries and a single statistic */ |
| 49 typedef struct census_window_stats_bucket { |
| 50 int64_t count; |
| 51 void *statistic; |
| 52 } cws_bucket; |
| 53 |
| 54 /* Each interval has a set of buckets, and the variables needed to keep |
| 55 track of their current state */ |
| 56 typedef struct census_window_stats_interval_stats { |
| 57 /* The buckets. There will be 'granularity' + 1 of these. */ |
| 58 cws_bucket *buckets; |
| 59 /* Index of the bucket containing the smallest time interval. */ |
| 60 int bottom_bucket; |
| 61 /* The smallest time storable in the current window. */ |
| 62 int64_t bottom; |
| 63 /* The largest time storable in the current window + 1ns */ |
| 64 int64_t top; |
| 65 /* The width of each bucket in ns. */ |
| 66 int64_t width; |
| 67 } cws_interval_stats; |
| 68 |
| 69 typedef struct census_window_stats { |
| 70 /* Number of intervals. */ |
| 71 int nintervals; |
| 72 /* Number of buckets in each interval. 'granularity' + 1. */ |
| 73 int nbuckets; |
| 74 /* Record of stat_info. */ |
| 75 cws_stat_info stat_info; |
| 76 /* Stats for each interval. */ |
| 77 cws_interval_stats *interval_stats; |
| 78 /* The time the newset stat was recorded. */ |
| 79 int64_t newest_time; |
| 80 } window_stats; |
| 81 |
| 82 /* Calculate an actual bucket index from a logical index 'IDX'. Other |
| 83 parameters supply information on the interval struct and overall stats. */ |
| 84 #define BUCKET_IDX(IS, IDX, WSTATS) \ |
| 85 ((IS->bottom_bucket + (IDX)) % WSTATS->nbuckets) |
| 86 |
| 87 /* The maximum seconds value we can have in a valid timespec. More than this |
| 88 will result in overflow in timespec_to_ns(). This works out to ~292 years. |
| 89 TODO: consider using doubles instead of int64. */ |
| 90 static int64_t max_seconds = (GPR_INT64_MAX - GPR_NS_PER_SEC) / GPR_NS_PER_SEC; |
| 91 |
| 92 static int64_t timespec_to_ns(const gpr_timespec ts) { |
| 93 if (ts.tv_sec > max_seconds) { |
| 94 return GPR_INT64_MAX - 1; |
| 95 } |
| 96 return ts.tv_sec * GPR_NS_PER_SEC + ts.tv_nsec; |
| 97 } |
| 98 |
| 99 static void cws_initialize_statistic(void *statistic, |
| 100 const cws_stat_info *stat_info) { |
| 101 if (stat_info->stat_initialize == NULL) { |
| 102 memset(statistic, 0, stat_info->stat_size); |
| 103 } else { |
| 104 stat_info->stat_initialize(statistic); |
| 105 } |
| 106 } |
| 107 |
| 108 /* Create and initialize a statistic */ |
| 109 static void *cws_create_statistic(const cws_stat_info *stat_info) { |
| 110 void *stat = gpr_malloc(stat_info->stat_size); |
| 111 cws_initialize_statistic(stat, stat_info); |
| 112 return stat; |
| 113 } |
| 114 |
| 115 window_stats *census_window_stats_create(int nintervals, |
| 116 const gpr_timespec intervals[], |
| 117 int granularity, |
| 118 const cws_stat_info *stat_info) { |
| 119 window_stats *ret; |
| 120 int i; |
| 121 /* validate inputs */ |
| 122 GPR_ASSERT(nintervals > 0 && granularity > 2 && intervals != NULL && |
| 123 stat_info != NULL); |
| 124 for (i = 0; i < nintervals; i++) { |
| 125 int64_t ns = timespec_to_ns(intervals[i]); |
| 126 GPR_ASSERT(intervals[i].tv_sec >= 0 && intervals[i].tv_nsec >= 0 && |
| 127 intervals[i].tv_nsec < GPR_NS_PER_SEC && ns >= 100 && |
| 128 granularity * 10 <= ns); |
| 129 } |
| 130 /* Allocate and initialize relevant data structures */ |
| 131 ret = (window_stats *)gpr_malloc(sizeof(window_stats)); |
| 132 ret->nintervals = nintervals; |
| 133 ret->nbuckets = granularity + 1; |
| 134 ret->stat_info = *stat_info; |
| 135 ret->interval_stats = |
| 136 (cws_interval_stats *)gpr_malloc(nintervals * sizeof(cws_interval_stats)); |
| 137 for (i = 0; i < nintervals; i++) { |
| 138 int64_t size_ns = timespec_to_ns(intervals[i]); |
| 139 cws_interval_stats *is = ret->interval_stats + i; |
| 140 cws_bucket *buckets = is->buckets = |
| 141 (cws_bucket *)gpr_malloc(ret->nbuckets * sizeof(cws_bucket)); |
| 142 int b; |
| 143 for (b = 0; b < ret->nbuckets; b++) { |
| 144 buckets[b].statistic = cws_create_statistic(stat_info); |
| 145 buckets[b].count = 0; |
| 146 } |
| 147 is->bottom_bucket = 0; |
| 148 is->bottom = 0; |
| 149 is->width = size_ns / granularity; |
| 150 /* Check for possible overflow issues, and maximize interval size if the |
| 151 user requested something large enough. */ |
| 152 if ((GPR_INT64_MAX - is->width) > size_ns) { |
| 153 is->top = size_ns + is->width; |
| 154 } else { |
| 155 is->top = GPR_INT64_MAX; |
| 156 is->width = GPR_INT64_MAX / (granularity + 1); |
| 157 } |
| 158 /* If size doesn't divide evenly, we can have a width slightly too small; |
| 159 better to have it slightly large. */ |
| 160 if ((size_ns - (granularity + 1) * is->width) > 0) { |
| 161 is->width += 1; |
| 162 } |
| 163 } |
| 164 ret->newest_time = 0; |
| 165 return ret; |
| 166 } |
| 167 |
| 168 /* When we try adding a measurement above the current interval range, we |
| 169 need to "shift" the buckets sufficiently to cover the new range. */ |
| 170 static void cws_shift_buckets(const window_stats *wstats, |
| 171 cws_interval_stats *is, int64_t when_ns) { |
| 172 int i; |
| 173 /* number of bucket time widths to "shift" */ |
| 174 int shift; |
| 175 /* number of buckets to clear */ |
| 176 int nclear; |
| 177 GPR_ASSERT(when_ns >= is->top); |
| 178 /* number of bucket time widths to "shift" */ |
| 179 shift = ((when_ns - is->top) / is->width) + 1; |
| 180 /* number of buckets to clear - limited by actual number of buckets */ |
| 181 nclear = GPR_MIN(shift, wstats->nbuckets); |
| 182 for (i = 0; i < nclear; i++) { |
| 183 int b = BUCKET_IDX(is, i, wstats); |
| 184 is->buckets[b].count = 0; |
| 185 cws_initialize_statistic(is->buckets[b].statistic, &wstats->stat_info); |
| 186 } |
| 187 /* adjust top/bottom times and current bottom bucket */ |
| 188 is->bottom_bucket = BUCKET_IDX(is, shift, wstats); |
| 189 is->top += shift * is->width; |
| 190 is->bottom += shift * is->width; |
| 191 } |
| 192 |
| 193 void census_window_stats_add(window_stats *wstats, const gpr_timespec when, |
| 194 const void *stat_value) { |
| 195 int i; |
| 196 int64_t when_ns = timespec_to_ns(when); |
| 197 GPR_ASSERT(wstats->interval_stats != NULL); |
| 198 for (i = 0; i < wstats->nintervals; i++) { |
| 199 cws_interval_stats *is = wstats->interval_stats + i; |
| 200 cws_bucket *bucket; |
| 201 if (when_ns < is->bottom) { /* Below smallest time in interval: drop */ |
| 202 continue; |
| 203 } |
| 204 if (when_ns >= is->top) { /* above limit: shift buckets */ |
| 205 cws_shift_buckets(wstats, is, when_ns); |
| 206 } |
| 207 /* Add the stat. */ |
| 208 GPR_ASSERT(is->bottom <= when_ns && when_ns < is->top); |
| 209 bucket = is->buckets + |
| 210 BUCKET_IDX(is, (when_ns - is->bottom) / is->width, wstats); |
| 211 bucket->count++; |
| 212 wstats->stat_info.stat_add(bucket->statistic, stat_value); |
| 213 } |
| 214 if (when_ns > wstats->newest_time) { |
| 215 wstats->newest_time = when_ns; |
| 216 } |
| 217 } |
| 218 |
| 219 /* Add a specific bucket contents to an accumulating total. */ |
| 220 static void cws_add_bucket_to_sum(cws_sum *sum, const cws_bucket *bucket, |
| 221 const cws_stat_info *stat_info) { |
| 222 sum->count += bucket->count; |
| 223 stat_info->stat_add(sum->statistic, bucket->statistic); |
| 224 } |
| 225 |
| 226 /* Add a proportion to an accumulating sum. */ |
| 227 static void cws_add_proportion_to_sum(double p, cws_sum *sum, |
| 228 const cws_bucket *bucket, |
| 229 const cws_stat_info *stat_info) { |
| 230 sum->count += p * bucket->count; |
| 231 stat_info->stat_add_proportion(p, sum->statistic, bucket->statistic); |
| 232 } |
| 233 |
| 234 void census_window_stats_get_sums(const window_stats *wstats, |
| 235 const gpr_timespec when, cws_sum sums[]) { |
| 236 int i; |
| 237 int64_t when_ns = timespec_to_ns(when); |
| 238 GPR_ASSERT(wstats->interval_stats != NULL); |
| 239 for (i = 0; i < wstats->nintervals; i++) { |
| 240 int when_bucket; |
| 241 int new_bucket; |
| 242 double last_proportion = 1.0; |
| 243 double bottom_proportion; |
| 244 cws_interval_stats *is = wstats->interval_stats + i; |
| 245 cws_sum *sum = sums + i; |
| 246 sum->count = 0; |
| 247 cws_initialize_statistic(sum->statistic, &wstats->stat_info); |
| 248 if (when_ns < is->bottom) { |
| 249 continue; |
| 250 } |
| 251 if (when_ns >= is->top) { |
| 252 cws_shift_buckets(wstats, is, when_ns); |
| 253 } |
| 254 /* Calculating the appropriate amount of which buckets to use can get |
| 255 complicated. Essentially there are two cases: |
| 256 1) if the "top" bucket (new_bucket, where the newest additions to the |
| 257 stats recorded are entered) corresponds to 'when', then we need |
| 258 to take a proportion of it - (if when < newest_time) or the full |
| 259 thing. We also (possibly) need to take a corresponding |
| 260 proportion of the bottom bucket. |
| 261 2) Other cases, we just take a straight proportion. |
| 262 */ |
| 263 when_bucket = (when_ns - is->bottom) / is->width; |
| 264 new_bucket = (wstats->newest_time - is->bottom) / is->width; |
| 265 if (new_bucket == when_bucket) { |
| 266 int64_t bottom_bucket_time = is->bottom + when_bucket * is->width; |
| 267 if (when_ns < wstats->newest_time) { |
| 268 last_proportion = (double)(when_ns - bottom_bucket_time) / |
| 269 (double)(wstats->newest_time - bottom_bucket_time); |
| 270 bottom_proportion = |
| 271 (double)(is->width - (when_ns - bottom_bucket_time)) / is->width; |
| 272 } else { |
| 273 bottom_proportion = |
| 274 (double)(is->width - (wstats->newest_time - bottom_bucket_time)) / |
| 275 is->width; |
| 276 } |
| 277 } else { |
| 278 last_proportion = |
| 279 (double)(when_ns + 1 - is->bottom - when_bucket * is->width) / |
| 280 is->width; |
| 281 bottom_proportion = 1.0 - last_proportion; |
| 282 } |
| 283 cws_add_proportion_to_sum(last_proportion, sum, |
| 284 is->buckets + BUCKET_IDX(is, when_bucket, wstats), |
| 285 &wstats->stat_info); |
| 286 if (when_bucket != 0) { /* last bucket isn't also bottom bucket */ |
| 287 int b; |
| 288 /* Add all of "bottom" bucket if we are looking at a subset of the |
| 289 full interval, or a proportion if we are adding full interval. */ |
| 290 cws_add_proportion_to_sum( |
| 291 (when_bucket == wstats->nbuckets - 1 ? bottom_proportion : 1.0), sum, |
| 292 is->buckets + is->bottom_bucket, &wstats->stat_info); |
| 293 /* Add all the remaining buckets (everything but top and bottom). */ |
| 294 for (b = 1; b < when_bucket; b++) { |
| 295 cws_add_bucket_to_sum(sum, is->buckets + BUCKET_IDX(is, b, wstats), |
| 296 &wstats->stat_info); |
| 297 } |
| 298 } |
| 299 } |
| 300 } |
| 301 |
| 302 void census_window_stats_destroy(window_stats *wstats) { |
| 303 int i; |
| 304 GPR_ASSERT(wstats->interval_stats != NULL); |
| 305 for (i = 0; i < wstats->nintervals; i++) { |
| 306 int b; |
| 307 for (b = 0; b < wstats->nbuckets; b++) { |
| 308 gpr_free(wstats->interval_stats[i].buckets[b].statistic); |
| 309 } |
| 310 gpr_free(wstats->interval_stats[i].buckets); |
| 311 } |
| 312 gpr_free(wstats->interval_stats); |
| 313 /* Ensure any use-after free triggers assert. */ |
| 314 wstats->interval_stats = NULL; |
| 315 gpr_free(wstats); |
| 316 } |
OLD | NEW |