LCOV - code coverage report
Current view: top level - datadog - span_sampler_config.cpp (source / functions) Hit Total Coverage
Test: filtered.info Lines: 180 188 95.7 %
Date: 2024-01-03 20:30:12 Functions: 4 5 80.0 %

          Line data    Source code
       1             : #include "span_sampler_config.h"
       2             : 
       3             : #include <cmath>
       4             : #include <fstream>
       5             : #include <sstream>
       6             : #include <unordered_set>
       7             : 
       8             : #include "environment.h"
       9             : #include "expected.h"
      10             : #include "json.hpp"
      11             : #include "logger.h"
      12             : 
      13             : namespace datadog {
      14             : namespace tracing {
      15             : namespace {
      16             : 
      17             : // `env_var` is the name of the environment variable from which `rules_raw` was
      18             : // obtained.  It's used for error messages.
      19          16 : Expected<std::vector<SpanSamplerConfig::Rule>> parse_rules(StringView rules_raw,
      20             :                                                            StringView env_var) {
      21          16 :   std::vector<SpanSamplerConfig::Rule> rules;
      22          16 :   nlohmann::json json_rules;
      23             : 
      24             :   try {
      25          19 :     json_rules = nlohmann::json::parse(rules_raw);
      26           3 :   } catch (const nlohmann::json::parse_error &error) {
      27           3 :     std::string message;
      28           3 :     message += "Unable to parse JSON from ";
      29           3 :     append(message, env_var);
      30           3 :     message += " value ";
      31           3 :     append(message, rules_raw);
      32           3 :     message += ": ";
      33           3 :     message += error.what();
      34           3 :     return Error{Error::SPAN_SAMPLING_RULES_INVALID_JSON, std::move(message)};
      35           3 :   }
      36             : 
      37          13 :   std::string type = json_rules.type_name();
      38          13 :   if (type != "array") {
      39           1 :     std::string message;
      40           1 :     message += "Trace sampling rules must be an array, but JSON in ";
      41           1 :     append(message, env_var);
      42           1 :     message += " has type \"";
      43           1 :     message += type;
      44           1 :     message += "\": ";
      45           1 :     append(message, rules_raw);
      46           1 :     return Error{Error::SPAN_SAMPLING_RULES_WRONG_TYPE, std::move(message)};
      47           1 :   }
      48             : 
      49             :   const std::unordered_set<std::string> allowed_properties{
      50         108 :       "service", "name", "resource", "tags", "sample_rate", "max_per_second"};
      51             : 
      52          19 :   for (const auto &json_rule : json_rules) {
      53          16 :     auto matcher = SpanMatcher::from_json(json_rule);
      54          16 :     if (auto *error = matcher.if_error()) {
      55           6 :       std::string prefix;
      56           6 :       prefix += "Unable to create a rule from ";
      57           6 :       append(prefix, env_var);
      58           6 :       prefix += " JSON ";
      59           6 :       append(prefix, rules_raw);
      60           6 :       prefix += ": ";
      61           6 :       return error->with_prefix(prefix);
      62           6 :     }
      63             : 
      64          10 :     SpanSamplerConfig::Rule rule{*matcher};
      65             : 
      66          10 :     auto sample_rate = json_rule.find("sample_rate");
      67          10 :     if (sample_rate != json_rule.end()) {
      68           4 :       type = sample_rate->type_name();
      69           4 :       if (type != "number") {
      70           1 :         std::string message;
      71           1 :         message += "Unable to parse a rule from ";
      72           1 :         append(message, env_var);
      73           1 :         message += " JSON ";
      74           1 :         append(message, rules_raw);
      75           1 :         message += ".  The \"sample_rate\" property of the rule ";
      76           1 :         message += json_rule.dump();
      77           1 :         message += " is not a number, but instead has type \"";
      78           1 :         message += type;
      79           1 :         message += "\".";
      80           2 :         return Error{Error::SPAN_SAMPLING_RULES_SAMPLE_RATE_WRONG_TYPE,
      81           2 :                      std::move(message)};
      82           1 :       }
      83           3 :       rule.sample_rate = *sample_rate;
      84             :     }
      85             : 
      86           9 :     auto max_per_second = json_rule.find("max_per_second");
      87           9 :     if (max_per_second != json_rule.end()) {
      88           6 :       type = max_per_second->type_name();
      89           6 :       if (type != "number") {
      90           1 :         std::string message;
      91           1 :         message += "Unable to parse a rule from ";
      92           1 :         append(message, env_var);
      93           1 :         message += " JSON ";
      94           1 :         append(message, rules_raw);
      95           1 :         message += ".  The \"max_per_second\" property of the rule ";
      96           1 :         message += json_rule.dump();
      97           1 :         message += " is not a number, but instead has type \"";
      98           1 :         message += type;
      99           1 :         message += "\".";
     100           2 :         return Error{Error::SPAN_SAMPLING_RULES_MAX_PER_SECOND_WRONG_TYPE,
     101           2 :                      std::move(message)};
     102           1 :       }
     103           5 :       rule.max_per_second = *max_per_second;
     104             :     }
     105             : 
     106             :     // Look for unexpected properties.
     107          20 :     for (const auto &[key, value] : json_rule.items()) {
     108          13 :       if (allowed_properties.count(key)) {
     109          12 :         continue;
     110             :       }
     111           1 :       std::string message;
     112           1 :       message += "Unexpected property \"";
     113           1 :       message += key;
     114           1 :       message += "\" having value ";
     115           1 :       message += value.dump();
     116           1 :       message += " in trace sampling rule ";
     117           1 :       message += json_rule.dump();
     118           1 :       message += ".  Error occurred while parsing from ";
     119           1 :       append(message, env_var);
     120           1 :       message += ": ";
     121           1 :       append(message, rules_raw);
     122           2 :       return Error{Error::SPAN_SAMPLING_RULES_UNKNOWN_PROPERTY,
     123           2 :                    std::move(message)};
     124          10 :     }
     125             : 
     126           7 :     rules.emplace_back(std::move(rule));
     127          19 :   }
     128             : 
     129           3 :   return rules;
     130          16 : }
     131             : 
     132             : }  // namespace
     133             : 
     134          10 : SpanSamplerConfig::Rule::Rule(const SpanMatcher &base) : SpanMatcher(base) {}
     135             : 
     136         743 : Expected<FinalizedSpanSamplerConfig> finalize_config(
     137             :     const SpanSamplerConfig &config, Logger &logger) {
     138         743 :   FinalizedSpanSamplerConfig result;
     139             : 
     140         743 :   std::vector<SpanSamplerConfig::Rule> rules = config.rules;
     141             : 
     142         743 :   auto rules_env = lookup(environment::DD_SPAN_SAMPLING_RULES);
     143         743 :   if (rules_env) {
     144             :     auto maybe_rules =
     145          14 :         parse_rules(*rules_env, name(environment::DD_SPAN_SAMPLING_RULES));
     146          14 :     if (auto *error = maybe_rules.if_error()) {
     147          12 :       return std::move(*error);
     148             :     }
     149           2 :     rules = std::move(*maybe_rules);
     150          14 :   }
     151             : 
     152         731 :   if (auto file_env = lookup(environment::DD_SPAN_SAMPLING_RULES_FILE)) {
     153           4 :     if (rules_env) {
     154             :       const auto rules_file_name =
     155           1 :           name(environment::DD_SPAN_SAMPLING_RULES_FILE);
     156           1 :       const auto rules_name = name(environment::DD_SPAN_SAMPLING_RULES);
     157           1 :       std::string message;
     158           1 :       append(message, rules_file_name);
     159           1 :       message += " is overridden by ";
     160           1 :       append(message, rules_name);
     161           1 :       message += ".  Since both are set, ";
     162           1 :       append(message, rules_name);
     163           1 :       message += " takes precedence, and ";
     164           1 :       append(message, rules_file_name);
     165           1 :       message += " will be ignored.";
     166           1 :       logger.log_error(message);
     167           1 :     } else {
     168           3 :       const auto span_rules_file = std::string(*file_env);
     169             : 
     170           1 :       const auto file_error = [&](const char *operation) {
     171           1 :         std::string message;
     172           1 :         message += "Unable to ";
     173           1 :         message += operation;
     174           1 :         message += " file \"";
     175           1 :         message += span_rules_file;
     176           1 :         message += "\" specified as value of environment variable ";
     177           1 :         append(message, name(environment::DD_SPAN_SAMPLING_RULES_FILE));
     178             : 
     179           2 :         return Error{Error::SPAN_SAMPLING_RULES_FILE_IO, std::move(message)};
     180           1 :       };
     181             : 
     182           3 :       std::ifstream file(span_rules_file);
     183           3 :       if (!file) {
     184           1 :         return file_error("open");
     185             :       }
     186             : 
     187           2 :       std::ostringstream rules_stream;
     188           2 :       rules_stream << file.rdbuf();
     189           2 :       if (!file) {
     190           0 :         return file_error("read");
     191             :       }
     192             : 
     193             :       auto maybe_rules = parse_rules(
     194           2 :           rules_stream.str(), name(environment::DD_SPAN_SAMPLING_RULES_FILE));
     195           2 :       if (auto *error = maybe_rules.if_error()) {
     196           1 :         std::string prefix;
     197           1 :         prefix += "With ";
     198           1 :         append(prefix, name(environment::DD_SPAN_SAMPLING_RULES_FILE));
     199           1 :         prefix += '=';
     200           1 :         append(prefix, *file_env);
     201           1 :         prefix += ": ";
     202           1 :         return error->with_prefix(prefix);
     203           1 :       }
     204             : 
     205           1 :       rules = std::move(*maybe_rules);
     206           7 :     }
     207             :   }
     208             : 
     209         750 :   for (const auto &rule : rules) {
     210          32 :     auto maybe_rate = Rate::from(rule.sample_rate);
     211          32 :     if (auto *error = maybe_rate.if_error()) {
     212           6 :       std::string prefix;
     213             :       prefix +=
     214             :           "Unable to parse sample_rate in span sampling rule with span "
     215           6 :           "pattern ";
     216           6 :       prefix += rule.to_json().dump();
     217           6 :       prefix += ": ";
     218           6 :       return error->with_prefix(prefix);
     219           6 :     }
     220             : 
     221          26 :     const auto allowed_types = {FP_NORMAL, FP_SUBNORMAL};
     222          38 :     if (rule.max_per_second &&
     223          20 :         (!(*rule.max_per_second > 0) ||
     224           8 :          std::find(std::begin(allowed_types), std::end(allowed_types),
     225          34 :                    std::fpclassify(*rule.max_per_second)) ==
     226          34 :              std::end(allowed_types))) {
     227           5 :       std::string message;
     228           5 :       message += "Span sampling rule with pattern ";
     229           5 :       message += rule.to_json().dump();
     230             :       message +=
     231             :           " should have a max_per_second value greater than zero, but the "
     232           5 :           "following value was given: ";
     233           5 :       message += std::to_string(*rule.max_per_second);
     234           5 :       return Error{Error::MAX_PER_SECOND_OUT_OF_RANGE, std::move(message)};
     235           5 :     }
     236             : 
     237          21 :     FinalizedSpanSamplerConfig::Rule finalized;
     238          21 :     static_cast<SpanMatcher &>(finalized) = rule;
     239          21 :     finalized.sample_rate = *maybe_rate;
     240          21 :     finalized.max_per_second = rule.max_per_second;
     241          21 :     result.rules.push_back(std::move(finalized));
     242          32 :   }
     243             : 
     244         718 :   return result;
     245         743 : }
     246             : 
     247           0 : nlohmann::json to_json(const FinalizedSpanSamplerConfig::Rule &rule) {
     248             :   // Get the base class's fields, then add our own.
     249           0 :   auto result = static_cast<const SpanMatcher &>(rule).to_json();
     250           0 :   result["sample_rate"] = double(rule.sample_rate);
     251           0 :   if (rule.max_per_second) {
     252           0 :     result["max_per_second"] = *rule.max_per_second;
     253             :   }
     254             : 
     255           0 :   return result;
     256           0 : }
     257             : 
     258             : }  // namespace tracing
     259             : }  // namespace datadog

Generated by: LCOV version 1.16