Line data Source code
1 : #include "source/common/http/codes.h"
2 :
3 : #include <cstdint>
4 : #include <string>
5 :
6 : #include "envoy/http/header_map.h"
7 : #include "envoy/stats/scope.h"
8 :
9 : #include "source/common/common/enum_to_int.h"
10 : #include "source/common/common/utility.h"
11 : #include "source/common/http/headers.h"
12 : #include "source/common/http/utility.h"
13 :
14 : #include "absl/strings/match.h"
15 : #include "absl/strings/str_cat.h"
16 : #include "absl/strings/str_join.h"
17 :
18 : namespace Envoy {
19 : namespace Http {
20 :
21 : CodeStatsImpl::CodeStatsImpl(Stats::SymbolTable& symbol_table)
22 : : stat_name_pool_(symbol_table), symbol_table_(symbol_table),
23 : canary_(stat_name_pool_.add("canary")), external_(stat_name_pool_.add("external")),
24 : internal_(stat_name_pool_.add("internal")),
25 : upstream_rq_1xx_(stat_name_pool_.add("upstream_rq_1xx")),
26 : upstream_rq_2xx_(stat_name_pool_.add("upstream_rq_2xx")),
27 : upstream_rq_3xx_(stat_name_pool_.add("upstream_rq_3xx")),
28 : upstream_rq_4xx_(stat_name_pool_.add("upstream_rq_4xx")),
29 : upstream_rq_5xx_(stat_name_pool_.add("upstream_rq_5xx")),
30 : upstream_rq_unknown_(stat_name_pool_.add("upstream_rq_unknown")), // Covers invalid http
31 : // response codes e.g. 600.
32 : upstream_rq_completed_(stat_name_pool_.add("upstream_rq_completed")),
33 : upstream_rq_time_(stat_name_pool_.add("upstream_rq_time")),
34 : vcluster_(stat_name_pool_.add("vcluster")), vhost_(stat_name_pool_.add("vhost")),
35 589 : route_(stat_name_pool_.add("route")), zone_(stat_name_pool_.add("zone")) {
36 :
37 : // Pre-allocate response codes 200, 404, and 503, as those seem quite likely.
38 : // We don't pre-allocate all the HTTP codes because the first 127 allocations
39 : // are likely to be encoded in one byte, and we would rather spend those on
40 : // common components of stat-names that appear frequently.
41 589 : upstreamRqStatName(Code::OK);
42 589 : upstreamRqStatName(Code::NotFound);
43 589 : upstreamRqStatName(Code::ServiceUnavailable);
44 589 : }
45 :
46 1196 : void CodeStatsImpl::incCounter(Stats::Scope& scope, const Stats::StatNameVec& names) const {
47 1196 : const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join(names);
48 1196 : scope.counterFromStatName(Stats::StatName(stat_name_storage.get())).inc();
49 1196 : }
50 :
51 599 : void CodeStatsImpl::incCounter(Stats::Scope& scope, Stats::StatName a, Stats::StatName b) const {
52 599 : const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join({a, b});
53 599 : scope.counterFromStatName(Stats::StatName(stat_name_storage.get())).inc();
54 599 : }
55 :
56 : void CodeStatsImpl::recordHistogram(Stats::Scope& scope, const Stats::StatNameVec& names,
57 219 : Stats::Histogram::Unit unit, uint64_t count) const {
58 219 : const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join(names);
59 219 : scope.histogramFromStatName(Stats::StatName(stat_name_storage.get()), unit).recordValue(count);
60 219 : }
61 :
62 : void CodeStatsImpl::chargeBasicResponseStat(Stats::Scope& scope, Stats::StatName prefix,
63 : Code response_code,
64 200 : bool exclude_http_code_stats) const {
65 200 : ASSERT(&symbol_table_ == &scope.symbolTable());
66 :
67 : // Build a dynamic stat for the response code and increment it.
68 200 : incCounter(scope, prefix, upstream_rq_completed_);
69 :
70 200 : if (!exclude_http_code_stats) {
71 200 : const Stats::StatName rq_group = upstreamRqGroup(response_code);
72 200 : if (!rq_group.empty()) {
73 199 : incCounter(scope, prefix, rq_group);
74 199 : }
75 200 : incCounter(scope, prefix, upstreamRqStatName(response_code));
76 200 : }
77 200 : }
78 :
79 : void CodeStatsImpl::chargeResponseStat(const ResponseStatInfo& info,
80 200 : bool exclude_http_code_stats) const {
81 200 : const Code code = static_cast<Code>(info.response_status_code_);
82 :
83 200 : ASSERT(&info.cluster_scope_.symbolTable() == &symbol_table_);
84 200 : chargeBasicResponseStat(info.cluster_scope_, info.prefix_, code, exclude_http_code_stats);
85 :
86 200 : const Stats::StatName rq_group = upstreamRqGroup(code);
87 200 : const Stats::StatName rq_code = upstreamRqStatName(code);
88 :
89 : // If the response is from a canary, also create canary stats.
90 200 : if (info.upstream_canary_) {
91 0 : writeCategory(info, rq_group, rq_code, canary_);
92 0 : }
93 :
94 : // Split stats into external vs. internal.
95 200 : if (info.internal_request_) {
96 113 : writeCategory(info, rq_group, rq_code, internal_);
97 156 : } else {
98 87 : writeCategory(info, rq_group, rq_code, external_);
99 87 : }
100 :
101 : // Handle request virtual cluster.
102 200 : if (!info.request_vcluster_name_.empty()) {
103 0 : incCounter(info.global_scope_, {vhost_, info.request_vhost_name_, vcluster_,
104 0 : info.request_vcluster_name_, upstream_rq_completed_});
105 0 : incCounter(info.global_scope_, {vhost_, info.request_vhost_name_, vcluster_,
106 0 : info.request_vcluster_name_, rq_group});
107 0 : incCounter(info.global_scope_,
108 0 : {vhost_, info.request_vhost_name_, vcluster_, info.request_vcluster_name_, rq_code});
109 0 : }
110 :
111 : // Handle route level stats.
112 200 : if (!info.request_route_name_.empty()) {
113 0 : incCounter(info.global_scope_, {vhost_, info.request_vhost_name_, route_,
114 0 : info.request_route_name_, upstream_rq_completed_});
115 0 : incCounter(info.global_scope_,
116 0 : {vhost_, info.request_vhost_name_, route_, info.request_route_name_, rq_group});
117 0 : incCounter(info.global_scope_,
118 0 : {vhost_, info.request_vhost_name_, route_, info.request_route_name_, rq_code});
119 0 : }
120 :
121 : // Handle per zone stats.
122 200 : if (!info.from_zone_.empty() && !info.to_zone_.empty()) {
123 199 : incCounter(info.cluster_scope_,
124 199 : {info.prefix_, zone_, info.from_zone_, info.to_zone_, upstream_rq_completed_});
125 199 : incCounter(info.cluster_scope_,
126 199 : {info.prefix_, zone_, info.from_zone_, info.to_zone_, rq_group});
127 199 : incCounter(info.cluster_scope_, {info.prefix_, zone_, info.from_zone_, info.to_zone_, rq_code});
128 199 : }
129 200 : }
130 :
131 : void CodeStatsImpl::writeCategory(const ResponseStatInfo& info, Stats::StatName rq_group,
132 200 : Stats::StatName rq_code, Stats::StatName category) const {
133 200 : incCounter(info.cluster_scope_, {info.prefix_, category, upstream_rq_completed_});
134 200 : if (!rq_group.empty()) {
135 199 : incCounter(info.cluster_scope_, {info.prefix_, category, rq_group});
136 199 : }
137 200 : incCounter(info.cluster_scope_, {info.prefix_, category, rq_code});
138 200 : }
139 :
140 73 : void CodeStatsImpl::chargeResponseTiming(const ResponseTimingInfo& info) const {
141 73 : const uint64_t count = info.response_time_.count();
142 73 : recordHistogram(info.cluster_scope_, {info.prefix_, upstream_rq_time_},
143 73 : Stats::Histogram::Unit::Milliseconds, count);
144 73 : if (info.upstream_canary_) {
145 0 : recordHistogram(info.cluster_scope_, {info.prefix_, canary_, upstream_rq_time_},
146 0 : Stats::Histogram::Unit::Milliseconds, count);
147 0 : }
148 :
149 73 : if (info.internal_request_) {
150 33 : recordHistogram(info.cluster_scope_, {info.prefix_, internal_, upstream_rq_time_},
151 33 : Stats::Histogram::Unit::Milliseconds, count);
152 73 : } else {
153 40 : recordHistogram(info.cluster_scope_, {info.prefix_, external_, upstream_rq_time_},
154 40 : Stats::Histogram::Unit::Milliseconds, count);
155 40 : }
156 :
157 73 : if (!info.request_vcluster_name_.empty()) {
158 0 : recordHistogram(info.global_scope_,
159 0 : {vhost_, info.request_vhost_name_, vcluster_, info.request_vcluster_name_,
160 0 : upstream_rq_time_},
161 0 : Stats::Histogram::Unit::Milliseconds, count);
162 0 : }
163 :
164 73 : if (!info.request_route_name_.empty()) {
165 0 : recordHistogram(
166 0 : info.global_scope_,
167 0 : {vhost_, info.request_vhost_name_, route_, info.request_route_name_, upstream_rq_time_},
168 0 : Stats::Histogram::Unit::Milliseconds, count);
169 0 : }
170 :
171 : // Handle per zone stats.
172 73 : if (!info.from_zone_.empty() && !info.to_zone_.empty()) {
173 73 : recordHistogram(info.cluster_scope_,
174 73 : {info.prefix_, zone_, info.from_zone_, info.to_zone_, upstream_rq_time_},
175 73 : Stats::Histogram::Unit::Milliseconds, count);
176 73 : }
177 73 : }
178 :
179 400 : Stats::StatName CodeStatsImpl::upstreamRqGroup(Code response_code) const {
180 400 : switch (enumToInt(response_code) / 100) {
181 0 : case 1:
182 0 : return upstream_rq_1xx_;
183 282 : case 2:
184 282 : return upstream_rq_2xx_;
185 0 : case 3:
186 0 : return upstream_rq_3xx_;
187 0 : case 4:
188 0 : return upstream_rq_4xx_;
189 116 : case 5:
190 116 : return upstream_rq_5xx_;
191 400 : }
192 2 : return empty_; // Unknown codes do not go into a group.
193 400 : }
194 :
195 2167 : Stats::StatName CodeStatsImpl::upstreamRqStatName(Code response_code) const {
196 : // Take a lock only if we've never seen this response-code before.
197 2167 : const uint32_t rc_index = static_cast<uint32_t>(response_code) - HttpCodeOffset;
198 2167 : if (rc_index >= NumHttpCodes) {
199 2 : return upstream_rq_unknown_;
200 2 : }
201 2165 : return Stats::StatName(rc_stat_names_.get(rc_index, [this, response_code]() -> const uint8_t* {
202 1772 : return stat_name_pool_.addReturningStorage(
203 1772 : absl::StrCat("upstream_rq_", enumToInt(response_code)));
204 1772 : }));
205 2167 : }
206 :
207 0 : std::string CodeUtility::groupStringForResponseCode(Code response_code) {
208 : // Note: this is only used in the unit test and in dynamo_filter.cc, which
209 : // needs the same sort of symbolization treatment we are doing here.
210 0 : if (CodeUtility::is1xx(enumToInt(response_code))) {
211 0 : return "1xx";
212 0 : } else if (CodeUtility::is2xx(enumToInt(response_code))) {
213 0 : return "2xx";
214 0 : } else if (CodeUtility::is3xx(enumToInt(response_code))) {
215 0 : return "3xx";
216 0 : } else if (CodeUtility::is4xx(enumToInt(response_code))) {
217 0 : return "4xx";
218 0 : } else if (CodeUtility::is5xx(enumToInt(response_code))) {
219 0 : return "5xx";
220 0 : } else {
221 0 : return "";
222 0 : }
223 0 : }
224 :
225 1501 : const char* CodeUtility::toString(Code code) {
226 : // clang-format off
227 1501 : switch (code) {
228 : // 1xx
229 13 : case Code::Continue: return "Continue";
230 0 : case Code::SwitchingProtocols: return "Switching Protocols";
231 :
232 : // 2xx
233 267 : case Code::OK: return "OK";
234 0 : case Code::Created: return "Created";
235 0 : case Code::Accepted: return "Accepted";
236 0 : case Code::NonAuthoritativeInformation: return "Non-Authoritative Information";
237 3 : case Code::NoContent: return "No Content";
238 0 : case Code::ResetContent: return "Reset Content";
239 0 : case Code::PartialContent: return "Partial Content";
240 0 : case Code::MultiStatus: return "Multi-Status";
241 0 : case Code::AlreadyReported: return "Already Reported";
242 0 : case Code::IMUsed: return "IM Used";
243 :
244 : // 3xx
245 0 : case Code::MultipleChoices: return "Multiple Choices";
246 0 : case Code::MovedPermanently: return "Moved Permanently";
247 0 : case Code::Found: return "Found";
248 0 : case Code::SeeOther: return "See Other";
249 0 : case Code::NotModified: return "Not Modified";
250 0 : case Code::UseProxy: return "Use Proxy";
251 0 : case Code::TemporaryRedirect: return "Temporary Redirect";
252 0 : case Code::PermanentRedirect: return "Permanent Redirect";
253 :
254 : // 4xx
255 1111 : case Code::BadRequest: return "Bad Request";
256 0 : case Code::Unauthorized: return "Unauthorized";
257 0 : case Code::PaymentRequired: return "Payment Required";
258 0 : case Code::Forbidden: return "Forbidden";
259 2 : case Code::NotFound: return "Not Found";
260 0 : case Code::MethodNotAllowed: return "Method Not Allowed";
261 0 : case Code::NotAcceptable: return "Not Acceptable";
262 0 : case Code::ProxyAuthenticationRequired: return "Proxy Authentication Required";
263 0 : case Code::RequestTimeout: return "Request Timeout";
264 0 : case Code::Conflict: return "Conflict";
265 0 : case Code::Gone: return "Gone";
266 0 : case Code::LengthRequired: return "Length Required";
267 0 : case Code::PreconditionFailed: return "Precondition Failed";
268 0 : case Code::PayloadTooLarge: return "Payload Too Large";
269 0 : case Code::URITooLong: return "URI Too Long";
270 0 : case Code::UnsupportedMediaType: return "Unsupported Media Type";
271 0 : case Code::RangeNotSatisfiable: return "Range Not Satisfiable";
272 0 : case Code::ExpectationFailed: return "Expectation Failed";
273 0 : case Code::MisdirectedRequest: return "Misdirected Request";
274 0 : case Code::UnprocessableEntity: return "Unprocessable Entity";
275 0 : case Code::Locked: return "Locked";
276 0 : case Code::FailedDependency: return "Failed Dependency";
277 38 : case Code::UpgradeRequired: return "Upgrade Required";
278 0 : case Code::PreconditionRequired: return "Precondition Required";
279 0 : case Code::TooManyRequests: return "Too Many Requests";
280 0 : case Code::RequestHeaderFieldsTooLarge: return "Request Header Fields Too Large";
281 0 : case Code::TooEarly: return "Too Early";
282 :
283 : // 5xx
284 0 : case Code::InternalServerError: return "Internal Server Error";
285 50 : case Code::NotImplemented: return "Not Implemented";
286 14 : case Code::BadGateway: return "Bad Gateway";
287 3 : case Code::ServiceUnavailable: return "Service Unavailable";
288 0 : case Code::GatewayTimeout: return "Gateway Timeout";
289 0 : case Code::HTTPVersionNotSupported: return "HTTP Version Not Supported";
290 0 : case Code::VariantAlsoNegotiates: return "Variant Also Negotiates";
291 0 : case Code::InsufficientStorage: return "Insufficient Storage";
292 0 : case Code::LoopDetected: return "Loop Detected";
293 0 : case Code::NotExtended: return "Not Extended";
294 0 : case Code::NetworkAuthenticationRequired: return "Network Authentication Required";
295 1501 : }
296 : // clang-format on
297 :
298 0 : return "Unknown";
299 1501 : }
300 :
301 : } // namespace Http
302 : } // namespace Envoy
|