Line data Source code
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 247 : const DynamicsMap& map) { 24 247 : auto iter = map.find(name); 25 247 : if (iter == map.end()) { 26 25 : return symbolic_pool_.add(name); 27 25 : } 28 : 29 222 : const DynamicSpans& dynamic_spans = iter->second; 30 222 : auto dynamic = dynamic_spans.begin(); 31 222 : 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 222 : StatNameVec segments; 36 222 : uint32_t segment_index = 0; 37 222 : std::vector<absl::string_view> dynamic_segments; 38 : 39 5372 : for (auto segment : absl::StrSplit(name, '.')) { 40 5372 : 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 1374 : ASSERT(dynamic_segments.empty()); 45 1374 : if (dynamic->second == segment_index) { 46 : // Handle start==end (a one-segment span). 47 1301 : segments.push_back(dynamic_pool_.add(segment)); 48 1301 : ++dynamic; 49 1301 : } else { 50 : // Handle start<end, so we save the first segment in dynamic_segments. 51 73 : dynamic_segments.push_back(segment); 52 73 : } 53 3998 : } else if (dynamic_segments.empty()) { 54 : // Handle that we are not in dynamic mode; we are just allocating 55 : // a symbolic segment. 56 3913 : segments.push_back(symbolic_pool_.add(segment)); 57 3913 : } else { 58 : // Handle the next dynamic segment. 59 85 : dynamic_segments.push_back(segment); 60 85 : 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 73 : segments.push_back(dynamic_pool_.add(absl::StrJoin(dynamic_segments, "."))); 64 73 : dynamic_segments.clear(); 65 73 : ++dynamic; 66 73 : } 67 85 : } 68 5372 : ++segment_index; 69 5372 : } 70 222 : ASSERT(dynamic_segments.empty()); 71 222 : ASSERT(dynamic == dynamic_end); 72 : 73 222 : storage_ptr_ = symbol_table_.join(segments); 74 222 : return StatName(storage_ptr_.get()); 75 247 : } 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