Skip to content

[2/2] feat: active configuration - report configuration to telemetry#98

Merged
dmehala merged 9 commits into
dmehala/active-config-v2from
dmehala/active-config-v2-part2
Mar 12, 2024
Merged

[2/2] feat: active configuration - report configuration to telemetry#98
dmehala merged 9 commits into
dmehala/active-config-v2from
dmehala/active-config-v2-part2

Conversation

@dmehala
Copy link
Copy Markdown
Contributor

@dmehala dmehala commented Feb 9, 2024

Description

This PR is second and final part of active configuration. This particular PR report configurations in app-started event and also report configuration updates received through Remote Configuration.

For reviewers

TODO

  • Write unit tests.
  • Validate with system-tests.

@dmehala dmehala requested review from cgilmour and dgoffredo February 9, 2024 13:23
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/tracer_config.cpp Outdated
Comment thread src/datadog/tracer_config.h Outdated
Comment thread src/datadog/tracer_telemetry.h
@pr-commenter
Copy link
Copy Markdown

pr-commenter Bot commented Feb 12, 2024

Benchmarks

Benchmark execution time: 2024-03-12 10:09:00

Comparing candidate commit 8861eb6 in PR branch dmehala/active-config-v2-part2 with baseline commit 04a350f in branch dmehala/active-config-v2.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 1 metrics, 0 unstable metrics.

Copy link
Copy Markdown
Contributor

@dgoffredo dgoffredo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a first pass review for our meeting today.

My thoughts so far:

  • Let's consider batching configuration updates, sending them only whenever the telemetry timer fires.
  • Let's think more about how ConfigManager stores and modifies its state. I need to spend more time looking at it.
  • Take a look at the possible deadlock that I commented on inline.
  • How are we going to test this? I'll have to look more at our existing tests.

Comment thread src/datadog/datadog_agent.cpp
Comment on lines +344 to +345
auto configuration_change =
generate_telemetry_body("app-client-configuration-change");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be sure to mention this in the comments at the top of tracer_telemetry.h. That is also a good time to audit the code for any telemetry event types that we forgot to mention before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in e2bb8f4

Copy link
Copy Markdown
Contributor

@dgoffredo dgoffredo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's an incomplete review, but I want to share these comments for our meeting today.

Comment thread src/datadog/tracer_config.cpp Outdated
return lower == "0" || lower == "false" || lower == "no";
std::string to_string(bool b) { return b ? "true" : "false"; }

// TODO: use `to_chars`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why setprecision(1)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid:
Screenshot 2024-03-01 at 11 16 16

Comment thread src/datadog/tracer_config.cpp Outdated
Comment thread src/datadog/tracer_config.cpp Outdated
Comment thread src/datadog/tracer_config.cpp Outdated
Comment thread src/datadog/tracer_config.cpp
Comment thread src/datadog/tracer_config.h Outdated
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/config_manager.cpp Outdated
Comment thread src/datadog/config_manager.cpp Outdated
}
} else {
current_trace_sampler_ = default_trace_sampler_;
if (!trace_sampler_.is_default()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For our meeting: What is the meaning of "default" in this context?

Comment thread src/datadog/config_manager.cpp Outdated
@dmehala dmehala force-pushed the dmehala/active-config-v2-part2 branch from 98b9046 to e2bb8f4 Compare March 1, 2024 14:11
Copy link
Copy Markdown
Contributor

@dgoffredo dgoffredo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that I have two tabs open with two different code reviews. Flushing comments...

Comment thread src/datadog/config_manager.h Outdated
namespace tracing {

class ConfigManager {
// A template class for managing dynamic configuration values.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// A template class for managing dynamic configuration values.
// A class template for managing dynamic configuration values.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit of gate-keeping that I learned a while ago. It's not a class, it's a template.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL

Comment thread src/datadog/config_manager.h Outdated
//
// Additionally, it provides methods for accessing the current value and
// checking whether it has been modified from its original state.
template <typename T>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider Value or Configurable instead of T. This is a matter of style, though.

Comment thread src/datadog/config_manager.h Outdated
template <typename T>
class DynamicConfig {
T original_value_;
Optional<T> current_value_ = nullopt;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you know, Optional<T>'s default value is nullopt, regardless of T.

Comment thread src/datadog/string_util.cpp Outdated
Comment on lines +33 to +44
auto to_string = [](PropagationStyle style) {
switch (style) {
case PropagationStyle::B3:
return "b3";
case PropagationStyle::DATADOG:
return "datadog";
case PropagationStyle::W3C:
return "tracecontext";
case PropagationStyle::NONE:
return "none";
}
return ""; ///< unlikely
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to centralize information about PropagationStyle is the following:

// in propagation_style.h
struct {
  StringView display_name;
  StringView telemetry_name;
} inline propagation_style_info[] = {
  {"Datadog", "datadog"},
  {"B3", "b3"}, // TODO: "b3multi"?
  {"W3C", "tracecontext"},
  {"none", "none"}
};

Then propagation_style_info[style].telemetry_name.

Really we could change the JSON encoder to use the telemetry names, so that there's only one representation.

Food for thought.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, inline could be extern, but then you need a name for the size of the array 😠 (and possibly a name for the struct).


return res;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't resist...

namespace {

template <typename Sequence, typename Func>
std::string join(const Sequence& elements, StringView separator, Func&& append_element) {
    auto iter = std::begin(elements);
    const auto end = std::end(elements);
    std::string result;
    if (iter == end) {
        return result;
    }
    append_element(result, *iter);
    for (++iter; iter != end; ++iter) {
        append(result, separator);
        append_element(result, *iter);
    }
    return result;
}

} // namespace

std::string join_tags(
    const std::unordered_map<std::string, std::string>& values) {
  return join(values, ",", [](std::string& result, const auto& entry) {
    const auto& [key, value] = entry;
    result += key;
    result += ':';
    result += value;
  });
}

std::string join(const std::vector<StringView>& values,
                 StringView separator) {
    return join(values, separator, [](std::string& result, StringView value) {
        append(result, value);
    });
}

std::string join_propagation_styles(
    const std::vector<PropagationStyle>& values) {
  return join(values, ",", [](std::string& result, PropagationStyle style) {
    switch (style) {
      case PropagationStyle::B3:
        result += "b3"; break;
      case PropagationStyle::DATADOG:
        result += "datadog"; break;
      case PropagationStyle::W3C:
        result += "tracecontext"; break;
      case PropagationStyle::NONE:
        result += "none"; break;
    }
  });
}

Comment thread src/datadog/string_util.h Outdated
namespace tracing {

// Converts a boolean value to a string
// The resulted string is either `true` or `false`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say "true" or "false", but the meaning is clear either way.

Comment thread src/datadog/string_util.h

// Converts a boolean value to a string
// The resulted string is either `true` or `false`
std::string to_string(bool b);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you don't refer to the variable name in the contract, then it's safe to omit it, especially here where the interpretation of the parameter is obvious.

Alternatively, you could go full pedant and write it this way:

// Return a string representation of the specified boolean `value`.
// The result is "true" for `true` and "false" for `false`.
std::string to_string(bool value);

Copy link
Copy Markdown
Contributor

@dgoffredo dgoffredo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flushing comments...

edit: Oh, weird. Hope we didn't lose any.

Copy link
Copy Markdown
Contributor

@dgoffredo dgoffredo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work, thanks for all of the thought put into it.

I'm wondering about alternative ways to factor ConfigManager's data members. Right now, you have one data member for remotely configurable thing, and share some code via class DynamicConfig. Then the work of managing the new values is in ConfigManager::update.

You set DD_TAGS by reforming the shared_ptr<const SpanDefaults>, because those tags live in SpanDefaults.

What happens when we need to separately configure a different property of SpanDefaults? Well, we'll almost certainly never need to. The other properties are more about span identity than about span annotation.

Still, I wonder if another protocol is warranted.

// TODO: Bad name, 0/10, do not recommend.
class DynamicallyConfigurable {
public:
  virtual std::vector<ConfigMetadata> update(const ConfigUpdate&) = 0;
  virtual void reset() { update({}); } // shrug
};

The benefit is that you can organize things by having constructors take dependencies, if any.

class ConfigManager {
  shared_ptr<const SpanDefaults> span_defaults_;
  shared_ptr<TraceSampler> trace_sampler_;
  SampleRateManager sample_rate_manager_;
  SamplingRulesManager sampling_rules_manager_;
  TagsManager tags_manager_;
  FooManager foo_manager_;
  ReportTracesManager report_traces_manager_;

  // ...
public:

  ConfigManager(...)
  : sample_rate_manager_(&trace_sampler_)
  , sampling_rules_manager_(&trace_sampler_)
  , tags_manager_(&span_defaults_)
  , foo_manager_(&span_defaults_)
  { ... }
};

vector<ConfigMetadata> ConfigManager::update(const 
vector<ConfigMetadata> ConfigManager::update(const ConfigUpdate& conf) {
  vector<ConfigMetadata> metadata;

  lock_guard lock(mutex_);
  append(metadata, sample_rate_manager_.update(conf));
  append(metadata, sampling_rules_manager_.update(conf));
  ...

The bookkeeping about the original value and such happens within each DynamicallyConfigurable derived class implementation.

Need to figure out how metadata gets updated when a reset (explicit or otherwise) changes the origin, so the "each manager returns a vector" isn't quite right.

I'm not confident that this is an improvement, but it's an idea.

Anyway, LGTM.

@dmehala dmehala merged commit 2482ff1 into dmehala/active-config-v2 Mar 12, 2024
@dmehala dmehala deleted the dmehala/active-config-v2-part2 branch March 12, 2024 11:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants