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
51410
    : stat_name_pool_(symbol_table), symbol_table_(symbol_table),
23
51410
      canary_(stat_name_pool_.add("canary")), external_(stat_name_pool_.add("external")),
24
51410
      internal_(stat_name_pool_.add("internal")),
25
51410
      upstream_rq_1xx_(stat_name_pool_.add("upstream_rq_1xx")),
26
51410
      upstream_rq_2xx_(stat_name_pool_.add("upstream_rq_2xx")),
27
51410
      upstream_rq_3xx_(stat_name_pool_.add("upstream_rq_3xx")),
28
51410
      upstream_rq_4xx_(stat_name_pool_.add("upstream_rq_4xx")),
29
51410
      upstream_rq_5xx_(stat_name_pool_.add("upstream_rq_5xx")),
30
51410
      upstream_rq_unknown_(stat_name_pool_.add("upstream_rq_unknown")), // Covers invalid http
31
                                                                        // response codes e.g. 600.
32
51410
      upstream_rq_completed_(stat_name_pool_.add("upstream_rq_completed")),
33
51410
      upstream_rq_time_(stat_name_pool_.add("upstream_rq_time")),
34
51410
      vcluster_(stat_name_pool_.add("vcluster")), vhost_(stat_name_pool_.add("vhost")),
35
51410
      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
51410
  upstreamRqStatName(Code::OK);
42
51410
  upstreamRqStatName(Code::NotFound);
43
51410
  upstreamRqStatName(Code::ServiceUnavailable);
44
51410
}
45

            
46
247125
void CodeStatsImpl::incCounter(Stats::Scope& scope, const Stats::StatNameVec& names) const {
47
247125
  const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join(names);
48
247125
  scope.counterFromStatName(Stats::StatName(stat_name_storage.get())).inc();
49
247125
}
50

            
51
124235
void CodeStatsImpl::incCounter(Stats::Scope& scope, Stats::StatName a, Stats::StatName b) const {
52
124235
  const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join({a, b});
53
124235
  scope.counterFromStatName(Stats::StatName(stat_name_storage.get())).inc();
54
124235
}
55

            
56
void CodeStatsImpl::recordHistogram(Stats::Scope& scope, const Stats::StatNameVec& names,
57
107818
                                    Stats::Histogram::Unit unit, uint64_t count) const {
58
107818
  const Stats::SymbolTable::StoragePtr stat_name_storage = symbol_table_.join(names);
59
107818
  scope.histogramFromStatName(Stats::StatName(stat_name_storage.get()), unit).recordValue(count);
60
107818
}
61

            
62
void CodeStatsImpl::chargeBasicResponseStat(Stats::Scope& scope, Stats::StatName prefix,
63
                                            Code response_code,
64
41422
                                            bool exclude_http_code_stats) const {
65
41422
  ASSERT(&symbol_table_ == &scope.symbolTable());
66

            
67
  // Build a dynamic stat for the response code and increment it.
68
41422
  incCounter(scope, prefix, upstream_rq_completed_);
69

            
70
41422
  if (!exclude_http_code_stats) {
71
41421
    const Stats::StatName rq_group = upstreamRqGroup(response_code);
72
41421
    if (!rq_group.empty()) {
73
41392
      incCounter(scope, prefix, rq_group);
74
41392
    }
75
41421
    incCounter(scope, prefix, upstreamRqStatName(response_code));
76
41421
  }
77
41422
}
78

            
79
void CodeStatsImpl::chargeResponseStat(const ResponseStatInfo& info,
80
41248
                                       bool exclude_http_code_stats) const {
81
41248
  const Code code = static_cast<Code>(info.response_status_code_);
82

            
83
41248
  ASSERT(&info.cluster_scope_.symbolTable() == &symbol_table_);
84
41248
  chargeBasicResponseStat(info.cluster_scope_, info.prefix_, code, exclude_http_code_stats);
85

            
86
41248
  const Stats::StatName rq_group = upstreamRqGroup(code);
87
41248
  const Stats::StatName rq_code = upstreamRqStatName(code);
88

            
89
  // If the response is from a canary, also create canary stats.
90
41248
  if (info.upstream_canary_) {
91
8
    writeCategory(info, rq_group, rq_code, canary_);
92
8
  }
93

            
94
  // Split stats into external vs. internal.
95
41248
  if (info.internal_request_) {
96
4248
    writeCategory(info, rq_group, rq_code, internal_);
97
39089
  } else {
98
37000
    writeCategory(info, rq_group, rq_code, external_);
99
37000
  }
100

            
101
  // Handle request virtual cluster.
102
41248
  if (!info.request_vcluster_name_.empty()) {
103
185
    incCounter(info.global_scope_, {vhost_, info.request_vhost_name_, vcluster_,
104
185
                                    info.request_vcluster_name_, upstream_rq_completed_});
105
185
    incCounter(info.global_scope_, {vhost_, info.request_vhost_name_, vcluster_,
106
185
                                    info.request_vcluster_name_, rq_group});
107
185
    incCounter(info.global_scope_,
108
185
               {vhost_, info.request_vhost_name_, vcluster_, info.request_vcluster_name_, rq_code});
109
185
  }
110

            
111
  // Handle route level stats.
112
41248
  if (!info.request_route_name_.empty()) {
113
12
    incCounter(info.global_scope_, {vhost_, info.request_vhost_name_, route_,
114
12
                                    info.request_route_name_, upstream_rq_completed_});
115
12
    incCounter(info.global_scope_,
116
12
               {vhost_, info.request_vhost_name_, route_, info.request_route_name_, rq_group});
117
12
    incCounter(info.global_scope_,
118
12
               {vhost_, info.request_vhost_name_, route_, info.request_route_name_, rq_code});
119
12
  }
120

            
121
  // Handle per zone stats.
122
41248
  if (!info.from_zone_.empty() && !info.to_zone_.empty()) {
123
40932
    incCounter(info.cluster_scope_,
124
40932
               {info.prefix_, zone_, info.from_zone_, info.to_zone_, upstream_rq_completed_});
125
40932
    incCounter(info.cluster_scope_,
126
40932
               {info.prefix_, zone_, info.from_zone_, info.to_zone_, rq_group});
127
40932
    incCounter(info.cluster_scope_, {info.prefix_, zone_, info.from_zone_, info.to_zone_, rq_code});
128
40932
  }
129
41248
}
130

            
131
void CodeStatsImpl::writeCategory(const ResponseStatInfo& info, Stats::StatName rq_group,
132
41256
                                  Stats::StatName rq_code, Stats::StatName category) const {
133
41256
  incCounter(info.cluster_scope_, {info.prefix_, category, upstream_rq_completed_});
134
41256
  if (!rq_group.empty()) {
135
41226
    incCounter(info.cluster_scope_, {info.prefix_, category, rq_group});
136
41226
  }
137
41256
  incCounter(info.cluster_scope_, {info.prefix_, category, rq_code});
138
41256
}
139

            
140
35895
void CodeStatsImpl::chargeResponseTiming(const ResponseTimingInfo& info) const {
141
35895
  const uint64_t count = info.response_time_.count();
142
35895
  recordHistogram(info.cluster_scope_, {info.prefix_, upstream_rq_time_},
143
35895
                  Stats::Histogram::Unit::Milliseconds, count);
144
35895
  if (info.upstream_canary_) {
145
5
    recordHistogram(info.cluster_scope_, {info.prefix_, canary_, upstream_rq_time_},
146
5
                    Stats::Histogram::Unit::Milliseconds, count);
147
5
  }
148

            
149
35895
  if (info.internal_request_) {
150
1658
    recordHistogram(info.cluster_scope_, {info.prefix_, internal_, upstream_rq_time_},
151
1658
                    Stats::Histogram::Unit::Milliseconds, count);
152
35253
  } else {
153
34237
    recordHistogram(info.cluster_scope_, {info.prefix_, external_, upstream_rq_time_},
154
34237
                    Stats::Histogram::Unit::Milliseconds, count);
155
34237
  }
156

            
157
35895
  if (!info.request_vcluster_name_.empty()) {
158
129
    recordHistogram(info.global_scope_,
159
129
                    {vhost_, info.request_vhost_name_, vcluster_, info.request_vcluster_name_,
160
129
                     upstream_rq_time_},
161
129
                    Stats::Histogram::Unit::Milliseconds, count);
162
129
  }
163

            
164
35895
  if (!info.request_route_name_.empty()) {
165
12
    recordHistogram(
166
12
        info.global_scope_,
167
12
        {vhost_, info.request_vhost_name_, route_, info.request_route_name_, upstream_rq_time_},
168
12
        Stats::Histogram::Unit::Milliseconds, count);
169
12
  }
170

            
171
  // Handle per zone stats.
172
35895
  if (!info.from_zone_.empty() && !info.to_zone_.empty()) {
173
35882
    recordHistogram(info.cluster_scope_,
174
35882
                    {info.prefix_, zone_, info.from_zone_, info.to_zone_, upstream_rq_time_},
175
35882
                    Stats::Histogram::Unit::Milliseconds, count);
176
35882
  }
177
35895
}
178

            
179
82669
Stats::StatName CodeStatsImpl::upstreamRqGroup(Code response_code) const {
180
82669
  switch (enumToInt(response_code) / 100) {
181
468
  case 1:
182
468
    return upstream_rq_1xx_;
183
77571
  case 2:
184
77571
    return upstream_rq_2xx_;
185
380
  case 3:
186
380
    return upstream_rq_3xx_;
187
358
  case 4:
188
358
    return upstream_rq_4xx_;
189
3834
  case 5:
190
3834
    return upstream_rq_5xx_;
191
82669
  }
192
58
  return empty_; // Unknown codes do not go into a group.
193
82669
}
194

            
195
236899
Stats::StatName CodeStatsImpl::upstreamRqStatName(Code response_code) const {
196
  // Take a lock only if we've never seen this response-code before.
197
236899
  const uint32_t rc_index = static_cast<uint32_t>(response_code) - HttpCodeOffset;
198
236899
  if (rc_index >= NumHttpCodes) {
199
58
    return upstream_rq_unknown_;
200
58
  }
201
236841
  return Stats::StatName(rc_stat_names_.get(rc_index, [this, response_code]() -> const uint8_t* {
202
155251
    return stat_name_pool_.addReturningStorage(
203
155251
        absl::StrCat("upstream_rq_", enumToInt(response_code)));
204
155251
  }));
205
236899
}
206

            
207
6
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
6
  if (CodeUtility::is1xx(enumToInt(response_code))) {
211
1
    return "1xx";
212
5
  } else if (CodeUtility::is2xx(enumToInt(response_code))) {
213
1
    return "2xx";
214
4
  } else if (CodeUtility::is3xx(enumToInt(response_code))) {
215
1
    return "3xx";
216
3
  } else if (CodeUtility::is4xx(enumToInt(response_code))) {
217
1
    return "4xx";
218
2
  } else if (CodeUtility::is5xx(enumToInt(response_code))) {
219
1
    return "5xx";
220
1
  } else {
221
1
    return "";
222
1
  }
223
6
}
224

            
225
44529
const char* CodeUtility::toString(Code code) {
226
  // clang-format off
227
44529
  switch (code) {
228
  // 1xx
229
100
  case Code::Continue:                      return "Continue";
230
63
  case Code::SwitchingProtocols:            return "Switching Protocols";
231

            
232
  // 2xx
233
36594
  case Code::OK:                            return "OK";
234
14
  case Code::Created:                       return "Created";
235
33
  case Code::Accepted:                      return "Accepted";
236
1
  case Code::NonAuthoritativeInformation:   return "Non-Authoritative Information";
237
15
  case Code::NoContent:                     return "No Content";
238
1
  case Code::ResetContent:                  return "Reset Content";
239
18
  case Code::PartialContent:                return "Partial Content";
240
1
  case Code::MultiStatus:                   return "Multi-Status";
241
1
  case Code::AlreadyReported:               return "Already Reported";
242
9
  case Code::IMUsed:                        return "IM Used";
243

            
244
  // 3xx
245
1
  case Code::MultipleChoices:               return "Multiple Choices";
246
19
  case Code::MovedPermanently:              return "Moved Permanently";
247
138
  case Code::Found:                         return "Found";
248
9
  case Code::SeeOther:                      return "See Other";
249
74
  case Code::NotModified:                   return "Not Modified";
250
1
  case Code::UseProxy:                      return "Use Proxy";
251
3
  case Code::TemporaryRedirect:             return "Temporary Redirect";
252
1
  case Code::PermanentRedirect:             return "Permanent Redirect";
253

            
254
  // 4xx
255
3912
  case Code::BadRequest:                    return "Bad Request";
256
80
  case Code::Unauthorized:                  return "Unauthorized";
257
9
  case Code::PaymentRequired:               return "Payment Required";
258
201
  case Code::Forbidden:                     return "Forbidden";
259
168
  case Code::NotFound:                      return "Not Found";
260
3
  case Code::MethodNotAllowed:              return "Method Not Allowed";
261
5
  case Code::NotAcceptable:                 return "Not Acceptable";
262
1
  case Code::ProxyAuthenticationRequired:   return "Proxy Authentication Required";
263
51
  case Code::RequestTimeout:                return "Request Timeout";
264
12
  case Code::Conflict:                      return "Conflict";
265
1
  case Code::Gone:                          return "Gone";
266
1
  case Code::LengthRequired:                return "Length Required";
267
1
  case Code::PreconditionFailed:            return "Precondition Failed";
268
145
  case Code::PayloadTooLarge:               return "Payload Too Large";
269
1
  case Code::URITooLong:                    return "URI Too Long";
270
1
  case Code::UnsupportedMediaType:          return "Unsupported Media Type";
271
3
  case Code::RangeNotSatisfiable:           return "Range Not Satisfiable";
272
1
  case Code::ExpectationFailed:             return "Expectation Failed";
273
1
  case Code::MisdirectedRequest:            return "Misdirected Request";
274
2
  case Code::UnprocessableEntity:           return "Unprocessable Entity";
275
1
  case Code::Locked:                        return "Locked";
276
1
  case Code::FailedDependency:              return "Failed Dependency";
277
27
  case Code::UpgradeRequired:               return "Upgrade Required";
278
1
  case Code::PreconditionRequired:          return "Precondition Required";
279
34
  case Code::TooManyRequests:               return "Too Many Requests";
280
37
  case Code::RequestHeaderFieldsTooLarge:   return "Request Header Fields Too Large";
281
2
  case Code::TooEarly:                      return "Too Early";
282

            
283
  // 5xx
284
302
  case Code::InternalServerError:           return "Internal Server Error";
285
15
  case Code::NotImplemented:                return "Not Implemented";
286
101
  case Code::BadGateway:                    return "Bad Gateway";
287
2100
  case Code::ServiceUnavailable:            return "Service Unavailable";
288
62
  case Code::GatewayTimeout:                return "Gateway Timeout";
289
1
  case Code::HTTPVersionNotSupported:       return "HTTP Version Not Supported";
290
1
  case Code::VariantAlsoNegotiates:         return "Variant Also Negotiates";
291
5
  case Code::InsufficientStorage:           return "Insufficient Storage";
292
1
  case Code::LoopDetected:                  return "Loop Detected";
293
1
  case Code::NotExtended:                   return "Not Extended";
294
1
  case Code::NetworkAuthenticationRequired: return "Network Authentication Required";
295
  case Code::LastUnassignedServerErrorCode: return "Last Unassigned Server Error Code";
296
44529
  }
297
  // clang-format on
298

            
299
141
  return "Unknown";
300
44529
}
301

            
302
} // namespace Http
303
} // namespace Envoy