LCOV - code coverage report
Current view: top level - source/extensions/tracers/xray - localized_sampling.cc (source / functions) Hit Total Coverage
Test: coverage.dat Lines: 0 141 0.0 %
Date: 2024-01-05 06:35:25 Functions: 0 9 0.0 %

          Line data    Source code
       1             : #include "source/extensions/tracers/xray/localized_sampling.h"
       2             : 
       3             : #include "source/common/http/exception.h"
       4             : #include "source/common/protobuf/utility.h"
       5             : #include "source/extensions/tracers/xray/util.h"
       6             : 
       7             : namespace Envoy {
       8             : namespace Extensions {
       9             : namespace Tracers {
      10             : namespace XRay {
      11             : 
      12             : // Corresponds to 5% sampling rate when no custom rules are applied.
      13             : constexpr double DefaultRate = 0.05;
      14             : // Determines how many requests to sample per second before default
      15             : // sampling rate kicks in when no custom rules are applied.
      16             : constexpr int DefaultFixedTarget = 1;
      17             : // The required 'version' of sampling manifest file when localized sampling is applied.
      18             : constexpr int SamplingFileVersion = 2;
      19             : constexpr auto VersionJsonKey = "version";
      20             : constexpr auto DefaultRuleJsonKey = "default";
      21             : constexpr auto FixedTargetJsonKey = "fixed_target";
      22             : constexpr auto RateJsonKey = "rate";
      23             : constexpr auto CustomRulesJsonKey = "rules";
      24             : constexpr auto HostJsonKey = "host";
      25             : constexpr auto HttpMethodJsonKey = "http_method";
      26             : constexpr auto UrlPathJsonKey = "url_path";
      27             : 
      28             : namespace {
      29           0 : void fail(absl::string_view msg) {
      30           0 :   auto& logger = Logger::Registry::getLog(Logger::Id::tracing);
      31           0 :   ENVOY_LOG_TO_LOGGER(logger, error, "Failed to parse sampling rules - {}", msg);
      32           0 : }
      33             : 
      34           0 : bool isValidRate(double n) { return n >= 0 && n <= 1.0; }
      35           0 : bool isValidFixedTarget(double n) { return n >= 0 && static_cast<uint32_t>(n) == n; }
      36             : 
      37           0 : bool validateRule(const ProtobufWkt::Struct& rule) {
      38           0 :   using ProtobufWkt::Value;
      39             : 
      40           0 :   const auto host_it = rule.fields().find(HostJsonKey);
      41           0 :   if (host_it != rule.fields().end() &&
      42           0 :       host_it->second.kind_case() != Value::KindCase::kStringValue) {
      43           0 :     fail("host must be a string");
      44           0 :     return false;
      45           0 :   }
      46             : 
      47           0 :   const auto http_method_it = rule.fields().find(HttpMethodJsonKey);
      48           0 :   if (http_method_it != rule.fields().end() &&
      49           0 :       http_method_it->second.kind_case() != Value::KindCase::kStringValue) {
      50           0 :     fail("HTTP method must be a string");
      51           0 :     return false;
      52           0 :   }
      53             : 
      54           0 :   const auto url_path_it = rule.fields().find(UrlPathJsonKey);
      55           0 :   if (url_path_it != rule.fields().end() &&
      56           0 :       url_path_it->second.kind_case() != Value::KindCase::kStringValue) {
      57           0 :     fail("URL path must be a string");
      58           0 :     return false;
      59           0 :   }
      60             : 
      61           0 :   const auto fixed_target_it = rule.fields().find(FixedTargetJsonKey);
      62           0 :   if (fixed_target_it == rule.fields().end() ||
      63           0 :       fixed_target_it->second.kind_case() != Value::KindCase::kNumberValue ||
      64           0 :       !isValidFixedTarget(fixed_target_it->second.number_value())) {
      65           0 :     fail("fixed target is missing or not a valid positive integer");
      66           0 :     return false;
      67           0 :   }
      68             : 
      69           0 :   const auto rate_it = rule.fields().find(RateJsonKey);
      70           0 :   if (rate_it == rule.fields().end() ||
      71           0 :       rate_it->second.kind_case() != Value::KindCase::kNumberValue ||
      72           0 :       !isValidRate(rate_it->second.number_value())) {
      73           0 :     fail("rate is missing or not a valid positive floating number");
      74           0 :     return false;
      75           0 :   }
      76           0 :   return true;
      77           0 : }
      78             : } // namespace
      79             : 
      80           0 : LocalizedSamplingRule LocalizedSamplingRule::createDefault() {
      81           0 :   return {DefaultFixedTarget, DefaultRate};
      82           0 : }
      83             : 
      84           0 : bool LocalizedSamplingRule::appliesTo(const SamplingRequest& request) const {
      85           0 :   return (request.host_.empty() || wildcardMatch(host_, request.host_)) &&
      86           0 :          (request.http_method_.empty() || wildcardMatch(http_method_, request.http_method_)) &&
      87           0 :          (request.http_url_.empty() || wildcardMatch(url_path_, request.http_url_));
      88           0 : }
      89             : 
      90             : LocalizedSamplingManifest::LocalizedSamplingManifest(const std::string& rule_json)
      91           0 :     : default_rule_(LocalizedSamplingRule::createDefault()) {
      92           0 :   if (rule_json.empty()) {
      93           0 :     return;
      94           0 :   }
      95             : 
      96           0 :   ProtobufWkt::Struct document;
      97           0 :   TRY_NEEDS_AUDIT { MessageUtil::loadFromJson(rule_json, document); }
      98           0 :   END_TRY catch (EnvoyException& e) {
      99           0 :     fail("invalid JSON format");
     100           0 :     return;
     101           0 :   }
     102             : 
     103           0 :   const auto version_it = document.fields().find(VersionJsonKey);
     104           0 :   if (version_it == document.fields().end()) {
     105           0 :     fail("missing version number");
     106           0 :     return;
     107           0 :   }
     108             : 
     109           0 :   if (version_it->second.kind_case() != ProtobufWkt::Value::KindCase::kNumberValue ||
     110           0 :       version_it->second.number_value() != SamplingFileVersion) {
     111           0 :     fail("wrong version number");
     112           0 :     return;
     113           0 :   }
     114             : 
     115           0 :   const auto default_rule_it = document.fields().find(DefaultRuleJsonKey);
     116           0 :   if (default_rule_it == document.fields().end() ||
     117           0 :       default_rule_it->second.kind_case() != ProtobufWkt::Value::KindCase::kStructValue) {
     118           0 :     fail("missing default rule");
     119           0 :     return;
     120           0 :   }
     121             : 
     122             :   // extract default rule members
     123           0 :   auto& default_rule_object = default_rule_it->second.struct_value();
     124           0 :   if (!validateRule(default_rule_object)) {
     125           0 :     return;
     126           0 :   }
     127             : 
     128           0 :   default_rule_.setRate(default_rule_object.fields().find(RateJsonKey)->second.number_value());
     129           0 :   default_rule_.setFixedTarget(static_cast<uint32_t>(
     130           0 :       default_rule_object.fields().find(FixedTargetJsonKey)->second.number_value()));
     131             : 
     132           0 :   const auto custom_rules_it = document.fields().find(CustomRulesJsonKey);
     133           0 :   if (custom_rules_it == document.fields().end()) {
     134           0 :     return;
     135           0 :   }
     136             : 
     137           0 :   if (custom_rules_it->second.kind_case() != ProtobufWkt::Value::KindCase::kListValue) {
     138           0 :     fail("rules must be JSON array");
     139           0 :     return;
     140           0 :   }
     141             : 
     142           0 :   for (auto& el : custom_rules_it->second.list_value().values()) {
     143           0 :     if (el.kind_case() != ProtobufWkt::Value::KindCase::kStructValue) {
     144           0 :       fail("rules array must be objects");
     145           0 :       return;
     146           0 :     }
     147             : 
     148           0 :     auto& rule_json = el.struct_value();
     149           0 :     if (!validateRule(rule_json)) {
     150           0 :       return;
     151           0 :     }
     152             : 
     153           0 :     LocalizedSamplingRule rule = LocalizedSamplingRule::createDefault();
     154           0 :     const auto host_it = rule_json.fields().find(HostJsonKey);
     155           0 :     if (host_it != rule_json.fields().end()) {
     156           0 :       rule.setHost(host_it->second.string_value());
     157           0 :     }
     158             : 
     159           0 :     const auto http_method_it = rule_json.fields().find(HttpMethodJsonKey);
     160           0 :     if (http_method_it != rule_json.fields().end()) {
     161           0 :       rule.setHttpMethod(http_method_it->second.string_value());
     162           0 :     }
     163             : 
     164           0 :     const auto url_path_it = rule_json.fields().find(UrlPathJsonKey);
     165           0 :     if (url_path_it != rule_json.fields().end()) {
     166           0 :       rule.setUrlPath(url_path_it->second.string_value());
     167           0 :     }
     168             : 
     169             :     // rate and fixed_target must exist because we validated this rule
     170           0 :     rule.setRate(rule_json.fields().find(RateJsonKey)->second.number_value());
     171           0 :     rule.setFixedTarget(
     172           0 :         static_cast<uint32_t>(rule_json.fields().find(FixedTargetJsonKey)->second.number_value()));
     173             : 
     174           0 :     custom_rules_.push_back(std::move(rule));
     175           0 :   }
     176           0 : }
     177             : 
     178           0 : bool LocalizedSamplingStrategy::shouldTrace(const SamplingRequest& sampling_request) {
     179           0 :   if (!manifest_.hasCustomRules()) {
     180           0 :     return shouldTrace(manifest_.defaultRule());
     181           0 :   }
     182             : 
     183           0 :   for (auto&& rule : manifest_.customRules()) {
     184           0 :     if (rule.appliesTo(sampling_request)) {
     185           0 :       return shouldTrace(rule);
     186           0 :     }
     187           0 :   }
     188           0 :   return shouldTrace(manifest_.defaultRule());
     189           0 : }
     190             : 
     191           0 : bool LocalizedSamplingStrategy::shouldTrace(LocalizedSamplingRule& rule) {
     192           0 :   const auto now = time_source_.monotonicTime();
     193           0 :   if (rule.reservoir().take(now)) {
     194           0 :     return true;
     195           0 :   }
     196             : 
     197             :   // rule.rate() is a rational number between 0 and 1
     198           0 :   auto toss = random() % 100;
     199           0 :   if (toss < (100 * rule.rate())) {
     200           0 :     return true;
     201           0 :   }
     202             : 
     203           0 :   return false;
     204           0 : }
     205             : 
     206             : } // namespace XRay
     207             : } // namespace Tracers
     208             : } // namespace Extensions
     209             : } // namespace Envoy

Generated by: LCOV version 1.15