summaryrefslogtreecommitdiffstats
path: root/src/common/perf_histogram.h
blob: 3052106be20f589d5c73af418327c1324ea71d9f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2017 OVH
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 *
 */

#ifndef CEPH_COMMON_PERF_HISTOGRAM_H
#define CEPH_COMMON_PERF_HISTOGRAM_H

#include <array>
#include <atomic>
#include <memory>

#include "common/Formatter.h"
#include "include/int_types.h"
#include "include/ceph_assert.h"

class PerfHistogramCommon {
public:
  enum scale_type_d : uint8_t {
    SCALE_LINEAR = 1,
    SCALE_LOG2 = 2,
  };

  struct axis_config_d {
    const char *m_name = nullptr;
    scale_type_d m_scale_type = SCALE_LINEAR;
    int64_t m_min = 0;
    int64_t m_quant_size = 0;
    int32_t m_buckets = 0;
    axis_config_d() = default;
    axis_config_d(const char* name,
		  scale_type_d scale_type,
		  int64_t min,
		  int64_t quant_size,
		  int32_t buckets)
      : m_name(name),
	m_scale_type(scale_type),
	m_min(min),
	m_quant_size(quant_size),
	m_buckets(buckets)
    {}
  };

protected:
  /// Dump configuration of one axis to a formatter
  static void dump_formatted_axis(ceph::Formatter *f, const axis_config_d &ac);

  /// Quantize given value and convert to bucket number on given axis
  static int64_t get_bucket_for_axis(int64_t value, const axis_config_d &ac);

  /// Calculate inclusive ranges of axis values for each bucket on that axis
  static std::vector<std::pair<int64_t, int64_t>> get_axis_bucket_ranges(
      const axis_config_d &ac);
};

/// PerfHistogram does trace a histogram of input values. It's an extended
/// version of a standard histogram which does trace characteristics of a single
/// one value only. In this implementation, values can be traced in multiple
/// dimensions - i.e. we can create a histogram of input request size (first
/// dimension) and processing latency (second dimension). Creating standard
/// histogram out of such multidimensional one is trivial and requires summing
/// values across dimensions we're not interested in.
template <int DIM = 2>
class PerfHistogram : public PerfHistogramCommon {
public:
  /// Initialize new histogram object
  PerfHistogram(std::initializer_list<axis_config_d> axes_config) {
    ceph_assert(axes_config.size() == DIM &&
		"Invalid number of axis configuration objects");

    int i = 0;
    for (const auto &ac : axes_config) {
      ceph_assertf(ac.m_buckets > 0, "Must have at least one bucket on axis");
      ceph_assertf(ac.m_quant_size > 0,
             "Quantization unit must be non-zero positive integer value");

      m_axes_config[i++] = ac;
    }

    m_rawData.reset(new std::atomic<uint64_t>[get_raw_size()] {});
  }

  /// Copy from other histogram object
  PerfHistogram(const PerfHistogram &other)
      : m_axes_config(other.m_axes_config) {
    int64_t size = get_raw_size();
    m_rawData.reset(new std::atomic<uint64_t>[size] {});
    for (int64_t i = 0; i < size; i++) {
      m_rawData[i] = other.m_rawData[i].load();
    }
  }

  /// Set all histogram values to 0
  void reset() {
    auto size = get_raw_size();
    for (auto i = size; --i >= 0;) {
      m_rawData[i] = 0;
    }
  }

  /// Increase counter for given axis values by one
  template <typename... T>
  void inc(T... axis) {
    auto index = get_raw_index_for_value(axis...);
    m_rawData[index]++;
  }

  /// Increase counter for given axis buckets by one
  template <typename... T>
  void inc_bucket(T... bucket) {
    auto index = get_raw_index_for_bucket(bucket...);
    m_rawData[index]++;
  }

  /// Read value from given bucket
  template <typename... T>
  uint64_t read_bucket(T... bucket) const {
    auto index = get_raw_index_for_bucket(bucket...);
    return m_rawData[index];
  }

  /// Dump data to a Formatter object
  void dump_formatted(ceph::Formatter *f) const {
    // Dump axes configuration
    f->open_array_section("axes");
    for (auto &ac : m_axes_config) {
      dump_formatted_axis(f, ac);
    }
    f->close_section();

    // Dump histogram values
    dump_formatted_values(f);
  }

protected:
  /// Raw data stored as linear space, internal indexes are calculated on
  /// demand.
  std::unique_ptr<std::atomic<uint64_t>[]> m_rawData;

  /// Configuration of axes
  std::array<axis_config_d, DIM> m_axes_config;

  /// Dump histogram counters to a formatter
  void dump_formatted_values(ceph::Formatter *f) const {
    visit_values([f](int) { f->open_array_section("values"); },
                 [f](int64_t value) { f->dump_unsigned("value", value); },
                 [f](int) { f->close_section(); });
  }

  /// Get number of all histogram counters
  int64_t get_raw_size() {
    int64_t ret = 1;
    for (const auto &ac : m_axes_config) {
      ret *= ac.m_buckets;
    }
    return ret;
  }

  /// Calculate m_rawData index from axis values
  template <typename... T>
  int64_t get_raw_index_for_value(T... axes) const {
    static_assert(sizeof...(T) == DIM, "Incorrect number of arguments");
    return get_raw_index_internal<0>(get_bucket_for_axis, 0, axes...);
  }

  /// Calculate m_rawData index from axis bucket numbers
  template <typename... T>
  int64_t get_raw_index_for_bucket(T... buckets) const {
    static_assert(sizeof...(T) == DIM, "Incorrect number of arguments");
    return get_raw_index_internal<0>(
        [](int64_t bucket, const axis_config_d &ac) {
          ceph_assertf(bucket >= 0, "Bucket index can not be negative");
          ceph_assertf(bucket < ac.m_buckets, "Bucket index too large");
          return bucket;
        },
        0, buckets...);
  }

  template <int level = 0, typename F, typename... T>
  int64_t get_raw_index_internal(F bucket_evaluator, int64_t startIndex,
                                 int64_t value, T... tail) const {
    static_assert(level + 1 + sizeof...(T) == DIM,
                  "Internal consistency check");
    auto &ac = m_axes_config[level];
    auto bucket = bucket_evaluator(value, ac);
    return get_raw_index_internal<level + 1>(
        bucket_evaluator, ac.m_buckets * startIndex + bucket, tail...);
  }

  template <int level, typename F>
  int64_t get_raw_index_internal(F, int64_t startIndex) const {
    static_assert(level == DIM, "Internal consistency check");
    return startIndex;
  }

  /// Visit all histogram counters, call onDimensionEnter / onDimensionLeave
  /// when starting / finishing traversal
  /// on given axis, call onValue when dumping raw histogram counter value.
  template <typename FDE, typename FV, typename FDL>
  void visit_values(FDE onDimensionEnter, FV onValue, FDL onDimensionLeave,
                    int level = 0, int startIndex = 0) const {
    if (level == DIM) {
      onValue(m_rawData[startIndex]);
      return;
    }

    onDimensionEnter(level);
    auto &ac = m_axes_config[level];
    startIndex *= ac.m_buckets;
    for (int32_t i = 0; i < ac.m_buckets; ++i, ++startIndex) {
      visit_values(onDimensionEnter, onValue, onDimensionLeave, level + 1,
                   startIndex);
    }
    onDimensionLeave(level);
  }
};

#endif