1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2016 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18
19#include <algorithm>
20#include <cmath>
21#include <iterator>
22#include <numeric>
23#include <string>
24
25#include "stat_aggregator.h"
26
27StatAggregator::StatAggregator(int _num_stats, int _num_samples) {
28
29    num_stats = _num_stats;
30    num_samples = _num_samples;
31    t_stats = new stat_history_t*[num_stats];
32    for (int i = 0; i < num_stats; ++i) {
33        t_stats[i] = new stat_history_t[num_samples];
34    }
35}
36
37StatAggregator::~StatAggregator() {
38
39    for (int i = 0; i < num_stats; ++i) {
40        delete[] t_stats[i];
41    }
42    delete[] t_stats;
43}
44
45void StatAggregator::aggregateAndPrintStats(const char* title, int count,
46                                            const char* unit) {
47
48    samples_t all_timings;
49    for (int i = 0; i < num_stats; ++i) {
50        for (int j = 1; j < num_samples; ++j) {
51            t_stats[i][0].latencies.insert(t_stats[i][0].latencies.end(),
52                                           t_stats[i][j].latencies.begin(),
53                                           t_stats[i][j].latencies.end());
54            t_stats[i][j].latencies.clear();
55        }
56
57        all_timings.push_back(std::make_pair(t_stats[i][0].name,
58                                             &t_stats[i][0].latencies));
59    }
60
61    int printed = 0;
62    printf("\n===== Avg Latencies (%s) - %d samples (%s) %n",
63            title, count, unit, &printed);
64    fillLineWith('=', 88-printed);
65
66    printValues(all_timings, unit);
67
68    fillLineWith('=', 87);
69}
70
71
72// Given a vector of values (each a vector<T>) calcuate metrics on them
73// and print to stdout.
74void StatAggregator::printValues(samples_t values, std::string unit) {
75
76    // First, calculate mean, median, standard deviation and percentiles
77    // of each set of values, both for printing and to derive what the
78    // range of the graphs should be.
79    std::vector<Stats> value_stats;
80    for (const auto& t : values) {
81        Stats stats;
82        if (t.second->size() == 0) {
83            continue;
84        }
85        stats.name = t.first;
86        stats.values = t.second;
87        std::vector<uint64_t>& vec = *t.second;
88
89        // Calculate latency percentiles
90        std::sort(vec.begin(), vec.end());
91        stats.median = vec[(vec.size() * 50) / 100];
92        stats.pct5 = vec[(vec.size() * 5) / 100];
93        stats.pct95 = vec[(vec.size() * 95) / 100];
94        stats.pct99 = vec[(vec.size() * 99) / 100];
95
96        const double sum = std::accumulate(vec.begin(), vec.end(), 0.0);
97        stats.mean = sum / vec.size();
98        double accum = 0.0;
99        for (auto &d : vec) {
100            accum += (d - stats.mean) * (d - stats.mean);
101        }
102        stats.stddev = sqrt(accum / (vec.size() - 1));
103
104        value_stats.push_back(stats);
105    }
106
107    // From these find the start and end for the spark graphs which covers the
108    // a "reasonable sample" of each value set. We define that as from the 5th
109    // to the 95th percentile, so we ensure *all* sets have that range covered.
110    uint64_t spark_start = std::numeric_limits<uint64_t>::max();
111    uint64_t spark_end = 0;
112    for (const auto& stats : value_stats) {
113        spark_start = (stats.pct5 < spark_start) ? stats.pct5 : spark_start;
114        spark_end = (stats.pct95 > spark_end) ? stats.pct95 : spark_end;
115    }
116
117    printf("\n                                Percentile\n");
118    printf("  %-16s Median     95th     99th  Std Dev  "
119            "Histogram of samples\n\n", "");
120    // Finally, print out each set.
121    for (const auto& stats : value_stats) {
122        if (unit == "µs") {
123            printf("%-16s %8.03f %8.03f %8.03f %8.03f  ",
124                    stats.name.c_str(), stats.median, stats.pct95,
125                    stats.pct99, stats.stddev);
126        } else if (unit == "ms") {
127            printf("%-16s %8.03f %8.03f %8.03f %8.03f  ",
128                    stats.name.c_str(), stats.median/1e3, stats.pct95/1e3,
129                    stats.pct99/1e3, stats.stddev/1e3);
130        } else {    // unit == "s"
131            printf("%-16s %8.03f %8.03f %8.03f %8.03f  ",
132                    stats.name.c_str(), stats.median/1e6, stats.pct95/1e6,
133                    stats.pct99/1e6, stats.stddev/1e6);
134        }
135
136        // Calculate and render Sparkline (requires UTF-8 terminal).
137        const int nbins = 32;
138        int prev_distance = 0;
139        std::vector<size_t> histogram;
140        for (unsigned int bin = 0; bin < nbins; bin++) {
141            const uint64_t max_for_bin = (spark_end / nbins) * bin;
142            auto it = std::lower_bound(stats.values->begin(),
143                                       stats.values->end(),
144                                       max_for_bin);
145            const int distance = std::distance(stats.values->begin(), it);
146            histogram.push_back(distance - prev_distance);
147            prev_distance = distance;
148        }
149
150        const auto minmax = std::minmax_element(histogram.begin(),
151                                                histogram.end());
152        const size_t range = *minmax.second - *minmax.first + 1;
153        const int levels = 8;
154        for (const auto& h : histogram) {
155            int bar_size = ((h - *minmax.first + 1) * (levels - 1)) / range;
156            putchar('\xe2');
157            putchar('\x96');
158            putchar('\x81' + bar_size);
159        }
160        putchar('\n');
161    }
162    if (unit == "µs") {
163        printf("%52s  %-14d %s %14d\n", "",
164               int(spark_start), unit.c_str(), int(spark_end));
165    } else if (unit == "ms") {
166        printf("%52s  %-14d %s %14d\n", "",
167               int(spark_start/1e3), unit.c_str(), int(spark_end/1e3));
168    } else {    // unit == "s"
169        printf("%52s  %-14d %s %14d\n", "",
170               int(spark_start/1e6), unit.c_str(), int(spark_end/1e6));
171    }
172}
173
174void StatAggregator::fillLineWith(const char c, int spaces) {
175
176    for (int i = 0; i < spaces; ++i) {
177        putchar(c);
178    }
179    putchar('\n');
180}
181