Coverage Report

Created: 2024-09-19 09:45

/proc/self/cwd/source/common/stats/stat_merger.cc
Line
Count
Source (jump to first uncovered line)
1
#include "source/common/stats/stat_merger.h"
2
3
#include <algorithm>
4
5
namespace Envoy {
6
namespace Stats {
7
8
0
StatMerger::StatMerger(Store& target_store) : temp_scope_(target_store.createScope("")) {}
9
10
0
StatMerger::~StatMerger() {
11
  // By the time a parent exits, all its contributions to accumulated gauges
12
  // should be zero. But depending on the timing of the stat-merger
13
  // communication shutdown and other shutdown activities on the parent, the
14
  // gauges may not all be zero yet. So simply erase all the parent
15
  // contributions.
16
0
  for (StatName stat_name : parent_gauges_) {
17
0
    Gauge& gauge = temp_scope_->gaugeFromStatName(stat_name, Gauge::ImportMode::Uninitialized);
18
0
    gauge.setParentValue(0);
19
0
  }
20
0
}
21
22
StatName StatMerger::DynamicContext::makeDynamicStatName(const std::string& name,
23
797
                                                         const DynamicsMap& map) {
24
797
  auto iter = map.find(name);
25
797
  if (iter == map.end()) {
26
251
    return symbolic_pool_.add(name);
27
251
  }
28
29
546
  const DynamicSpans& dynamic_spans = iter->second;
30
546
  auto dynamic = dynamic_spans.begin();
31
546
  auto dynamic_end = dynamic_spans.end();
32
33
  // Name has embedded dynamic segments; we'll need to join together the
34
  // static/dynamic StatName segments.
35
546
  StatNameVec segments;
36
546
  uint32_t segment_index = 0;
37
546
  std::vector<absl::string_view> dynamic_segments;
38
39
20.2M
  for (auto segment : absl::StrSplit(name, '.')) {
40
20.2M
    if (dynamic != dynamic_end && dynamic->first == segment_index) {
41
      // Handle start of dynamic span. We note that we are in a dynamic
42
      // span by adding to dynamic_segments, which should of course be
43
      // non-empty.
44
10.7M
      ASSERT(dynamic_segments.empty());
45
10.7M
      if (dynamic->second == segment_index) {
46
        // Handle start==end (a one-segment span).
47
9.34M
        segments.push_back(dynamic_pool_.add(segment));
48
9.34M
        ++dynamic;
49
9.34M
      } else {
50
        // Handle start<end, so we save the first segment in dynamic_segments.
51
1.44M
        dynamic_segments.push_back(segment);
52
1.44M
      }
53
10.7M
    } else if (dynamic_segments.empty()) {
54
      // Handle that we are not in dynamic mode; we are just allocating
55
      // a symbolic segment.
56
6.02M
      segments.push_back(symbolic_pool_.add(segment));
57
6.02M
    } else {
58
      // Handle the next dynamic segment.
59
3.46M
      dynamic_segments.push_back(segment);
60
3.46M
      if (dynamic->second == segment_index) {
61
        // Handle that this dynamic segment is the last one, and we're flipping
62
        // back to symbolic mode.
63
1.44M
        segments.push_back(dynamic_pool_.add(absl::StrJoin(dynamic_segments, ".")));
64
1.44M
        dynamic_segments.clear();
65
1.44M
        ++dynamic;
66
1.44M
      }
67
3.46M
    }
68
20.2M
    ++segment_index;
69
20.2M
  }
70
546
  ASSERT(dynamic_segments.empty());
71
546
  ASSERT(dynamic == dynamic_end);
72
73
546
  storage_ptr_ = symbol_table_.join(segments);
74
546
  return StatName(storage_ptr_.get());
75
546
}
76
77
void StatMerger::mergeCounters(const Protobuf::Map<std::string, uint64_t>& counter_deltas,
78
0
                               const DynamicsMap& dynamic_map) {
79
0
  for (const auto& counter : counter_deltas) {
80
0
    const std::string& name = counter.first;
81
0
    StatMerger::DynamicContext dynamic_context(temp_scope_->symbolTable());
82
0
    StatName stat_name = dynamic_context.makeDynamicStatName(name, dynamic_map);
83
0
    temp_scope_->counterFromStatName(stat_name).add(counter.second);
84
0
  }
85
0
}
86
87
void StatMerger::mergeGauges(const Protobuf::Map<std::string, uint64_t>& gauges,
88
0
                             const DynamicsMap& dynamic_map) {
89
0
  for (const auto& gauge : gauges) {
90
    // Merging gauges via RPC from the parent has 3 cases; case 1 and 3b are the
91
    // most common.
92
    //
93
    // 1. Child thinks gauge is Accumulate : data is combined in
94
    //    gauge_ref.add() below.
95
    // 2. Child thinks gauge is NeverImport: we skip this loop entry via
96
    //    'continue'.
97
    // 3. Child has not yet initialized gauge yet -- this merge is the
98
    //    first time the child learns of the gauge. It's possible the child
99
    //    will think the gauge is NeverImport due to a code change. But for
100
    //    now we will leave the gauge in the child process as
101
    //    import_mode==Uninitialized, and accumulate the parent value in
102
    //    gauge_ref.add(). Gauges in this mode will be included in
103
    //    stats-sinks or the admin /stats calls, until the child initializes
104
    //    the gauge, in which case:
105
    // 3a. Child later initializes gauges as NeverImport: the parent value is
106
    //     cleared during the mergeImportMode call.
107
    // 3b. Child later initializes gauges as Accumulate: the parent value is
108
    //     retained.
109
110
0
    StatMerger::DynamicContext dynamic_context(temp_scope_->symbolTable());
111
0
    StatName stat_name = dynamic_context.makeDynamicStatName(gauge.first, dynamic_map);
112
0
    GaugeOptConstRef gauge_opt = temp_scope_->findGauge(stat_name);
113
114
0
    Gauge::ImportMode import_mode = Gauge::ImportMode::Uninitialized;
115
0
    if (gauge_opt) {
116
0
      import_mode = gauge_opt->get().importMode();
117
0
      if (import_mode == Gauge::ImportMode::NeverImport) {
118
0
        continue;
119
0
      }
120
0
    }
121
122
    // TODO(snowp): Propagate tag values during hot restarts.
123
0
    auto& gauge_ref = temp_scope_->gaugeFromStatName(stat_name, import_mode);
124
0
    if (gauge_ref.importMode() == Gauge::ImportMode::NeverImport) {
125
      // On the first iteration through the loop, the gauge will not be loaded into the scope
126
      // cache even though it might exist in another scope. Thus, we need to check again for
127
      // the import status to see if we should skip this gauge.
128
      //
129
      // TODO(mattklein123): There is a race condition here. It's technically possible that
130
      // between the time we created this stat, the stat might be created by the child as a
131
      // never import stat, making the below math invalid. A follow up solution is to take the
132
      // store lock starting from gaugeFromStatName() to the end of this function, but this will
133
      // require adding some type of mergeGauge() function to the scope and dealing with recursive
134
      // lock acquisition, etc. so we will leave this as a follow up. This race should be incredibly
135
      // rare.
136
0
      continue;
137
0
    }
138
139
0
    const uint64_t new_parent_value = gauge.second;
140
0
    parent_gauges_.insert(gauge_ref.statName());
141
0
    gauge_ref.setParentValue(new_parent_value);
142
0
  }
143
0
}
144
145
0
void StatMerger::retainParentGaugeValue(Stats::StatName gauge_name) {
146
0
  parent_gauges_.erase(gauge_name);
147
0
}
148
149
void StatMerger::mergeStats(const Protobuf::Map<std::string, uint64_t>& counter_deltas,
150
                            const Protobuf::Map<std::string, uint64_t>& gauges,
151
0
                            const DynamicsMap& dynamics) {
152
0
  mergeCounters(counter_deltas, dynamics);
153
0
  mergeGauges(gauges, dynamics);
154
0
}
155
156
} // namespace Stats
157
} // namespace Envoy