1 //
2 //
3 // Copyright 2018 gRPC authors.
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 #ifndef GRPC_SRC_CORE_EXT_XDS_XDS_CLIENT_STATS_H
20 #define GRPC_SRC_CORE_EXT_XDS_XDS_CLIENT_STATS_H
21 
22 #include <grpc/support/port_platform.h>
23 
24 #include <atomic>
25 #include <cstdint>
26 #include <initializer_list>
27 #include <map>
28 #include <string>
29 #include <utility>
30 
31 #include "absl/base/thread_annotations.h"
32 #include "absl/strings/str_format.h"
33 #include "absl/strings/string_view.h"
34 
35 #include "src/core/ext/xds/xds_bootstrap.h"
36 #include "src/core/lib/gpr/useful.h"
37 #include "src/core/lib/gprpp/per_cpu.h"
38 #include "src/core/lib/gprpp/ref_counted.h"
39 #include "src/core/lib/gprpp/ref_counted_ptr.h"
40 #include "src/core/lib/gprpp/sync.h"
41 
42 namespace grpc_core {
43 
44 // Forward declaration to avoid circular dependency.
45 class XdsClient;
46 
47 // Locality name.
48 class XdsLocalityName : public RefCounted<XdsLocalityName> {
49  public:
50   struct Less {
operatorLess51     bool operator()(const XdsLocalityName* lhs,
52                     const XdsLocalityName* rhs) const {
53       if (lhs == nullptr || rhs == nullptr) return QsortCompare(lhs, rhs);
54       return lhs->Compare(*rhs) < 0;
55     }
56 
operatorLess57     bool operator()(const RefCountedPtr<XdsLocalityName>& lhs,
58                     const RefCountedPtr<XdsLocalityName>& rhs) const {
59       return (*this)(lhs.get(), rhs.get());
60     }
61   };
62 
XdsLocalityName(std::string region,std::string zone,std::string sub_zone)63   XdsLocalityName(std::string region, std::string zone, std::string sub_zone)
64       : region_(std::move(region)),
65         zone_(std::move(zone)),
66         sub_zone_(std::move(sub_zone)) {}
67 
68   bool operator==(const XdsLocalityName& other) const {
69     return region_ == other.region_ && zone_ == other.zone_ &&
70            sub_zone_ == other.sub_zone_;
71   }
72 
73   bool operator!=(const XdsLocalityName& other) const {
74     return !(*this == other);
75   }
76 
Compare(const XdsLocalityName & other)77   int Compare(const XdsLocalityName& other) const {
78     int cmp_result = region_.compare(other.region_);
79     if (cmp_result != 0) return cmp_result;
80     cmp_result = zone_.compare(other.zone_);
81     if (cmp_result != 0) return cmp_result;
82     return sub_zone_.compare(other.sub_zone_);
83   }
84 
region()85   const std::string& region() const { return region_; }
zone()86   const std::string& zone() const { return zone_; }
sub_zone()87   const std::string& sub_zone() const { return sub_zone_; }
88 
AsHumanReadableString()89   const std::string& AsHumanReadableString() {
90     if (human_readable_string_.empty()) {
91       human_readable_string_ =
92           absl::StrFormat("{region=\"%s\", zone=\"%s\", sub_zone=\"%s\"}",
93                           region_, zone_, sub_zone_);
94     }
95     return human_readable_string_;
96   }
97 
98  private:
99   std::string region_;
100   std::string zone_;
101   std::string sub_zone_;
102   std::string human_readable_string_;
103 };
104 
105 // Drop stats for an xds cluster.
106 class XdsClusterDropStats : public RefCounted<XdsClusterDropStats> {
107  public:
108   // The total number of requests dropped for any reason is the sum of
109   // uncategorized_drops, and dropped_requests map.
110   using CategorizedDropsMap = std::map<std::string /* category */, uint64_t>;
111   struct Snapshot {
112     uint64_t uncategorized_drops = 0;
113     // The number of requests dropped for the specific drop categories
114     // outlined in the drop_overloads field in the EDS response.
115     CategorizedDropsMap categorized_drops;
116 
117     Snapshot& operator+=(const Snapshot& other) {
118       uncategorized_drops += other.uncategorized_drops;
119       for (const auto& p : other.categorized_drops) {
120         categorized_drops[p.first] += p.second;
121       }
122       return *this;
123     }
124 
IsZeroSnapshot125     bool IsZero() const {
126       if (uncategorized_drops != 0) return false;
127       for (const auto& p : categorized_drops) {
128         if (p.second != 0) return false;
129       }
130       return true;
131     }
132   };
133 
134   XdsClusterDropStats(RefCountedPtr<XdsClient> xds_client,
135                       const XdsBootstrap::XdsServer& lrs_server,
136                       absl::string_view cluster_name,
137                       absl::string_view eds_service_name);
138   ~XdsClusterDropStats() override;
139 
140   // Returns a snapshot of this instance and resets all the counters.
141   Snapshot GetSnapshotAndReset();
142 
143   void AddUncategorizedDrops();
144   void AddCallDropped(const std::string& category);
145 
146  private:
147   RefCountedPtr<XdsClient> xds_client_;
148   const XdsBootstrap::XdsServer& lrs_server_;
149   absl::string_view cluster_name_;
150   absl::string_view eds_service_name_;
151   std::atomic<uint64_t> uncategorized_drops_{0};
152   // Protects categorized_drops_. A mutex is necessary because the length of
153   // dropped_requests can be accessed by both the picker (from data plane
154   // mutex) and the load reporting thread (from the control plane combiner).
155   Mutex mu_;
156   CategorizedDropsMap categorized_drops_ ABSL_GUARDED_BY(mu_);
157 };
158 
159 // Locality stats for an xds cluster.
160 class XdsClusterLocalityStats : public RefCounted<XdsClusterLocalityStats> {
161  public:
162   struct BackendMetric {
163     uint64_t num_requests_finished_with_metric = 0;
164     double total_metric_value = 0;
165 
166     BackendMetric& operator+=(const BackendMetric& other) {
167       num_requests_finished_with_metric +=
168           other.num_requests_finished_with_metric;
169       total_metric_value += other.total_metric_value;
170       return *this;
171     }
172 
IsZeroBackendMetric173     bool IsZero() const {
174       return num_requests_finished_with_metric == 0 && total_metric_value == 0;
175     }
176   };
177 
178   struct Snapshot {
179     uint64_t total_successful_requests = 0;
180     uint64_t total_requests_in_progress = 0;
181     uint64_t total_error_requests = 0;
182     uint64_t total_issued_requests = 0;
183     std::map<std::string, BackendMetric> backend_metrics;
184 
185     Snapshot& operator+=(const Snapshot& other) {
186       total_successful_requests += other.total_successful_requests;
187       total_requests_in_progress += other.total_requests_in_progress;
188       total_error_requests += other.total_error_requests;
189       total_issued_requests += other.total_issued_requests;
190       for (const auto& p : other.backend_metrics) {
191         backend_metrics[p.first] += p.second;
192       }
193       return *this;
194     }
195 
IsZeroSnapshot196     bool IsZero() const {
197       if (total_successful_requests != 0 || total_requests_in_progress != 0 ||
198           total_error_requests != 0 || total_issued_requests != 0) {
199         return false;
200       }
201       for (const auto& p : backend_metrics) {
202         if (!p.second.IsZero()) return false;
203       }
204       return true;
205     }
206   };
207 
208   XdsClusterLocalityStats(RefCountedPtr<XdsClient> xds_client,
209                           const XdsBootstrap::XdsServer& lrs_server_,
210                           absl::string_view cluster_name,
211                           absl::string_view eds_service_name,
212                           RefCountedPtr<XdsLocalityName> name);
213   ~XdsClusterLocalityStats() override;
214 
215   // Returns a snapshot of this instance and resets all the counters.
216   Snapshot GetSnapshotAndReset();
217 
218   void AddCallStarted();
219   void AddCallFinished(const std::map<absl::string_view, double>* named_metrics,
220                        bool fail = false);
221 
222  private:
223   struct Stats {
224     std::atomic<uint64_t> total_successful_requests{0};
225     std::atomic<uint64_t> total_requests_in_progress{0};
226     std::atomic<uint64_t> total_error_requests{0};
227     std::atomic<uint64_t> total_issued_requests{0};
228 
229     // Protects backend_metrics. A mutex is necessary because the length of
230     // backend_metrics_ can be accessed by both the callback intercepting the
231     // call's recv_trailing_metadata and the load reporting thread.
232     Mutex backend_metrics_mu;
233     std::map<std::string, BackendMetric> backend_metrics
234         ABSL_GUARDED_BY(backend_metrics_mu);
235   };
236 
237   RefCountedPtr<XdsClient> xds_client_;
238   const XdsBootstrap::XdsServer& lrs_server_;
239   absl::string_view cluster_name_;
240   absl::string_view eds_service_name_;
241   RefCountedPtr<XdsLocalityName> name_;
242   PerCpu<Stats> stats_{PerCpuOptions().SetMaxShards(32).SetCpusPerShard(4)};
243 };
244 
245 }  // namespace grpc_core
246 
247 #endif  // GRPC_SRC_CORE_EXT_XDS_XDS_CLIENT_STATS_H
248