-
Notifications
You must be signed in to change notification settings - Fork 314
[AMLII-2019] Max samples per context for Histogram, Distribution and Timing metrics (Experimental Feature) #863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
96 commits
Select commit
Hold shift + click to select a range
6de4f9b
WIP
andrewqian2001datadog c171911
add buffered_metrics object type (#853)
andrewqian2001datadog 572da5c
Merge branch 'add-extended-aggregation' of github.com:DataDog/datadog…
andrewqian2001datadog 79590b0
Merge branch 'master' into add-extended-aggregation
andrewqian2001datadog c112d5b
revert test config change
andrewqian2001datadog 890c657
add buffered_metric_context WIP
andrewqian2001datadog e591bbb
Merge branch 'master' into add-extended-aggregation
andrewqian2001datadog 4c2b238
change naming to sample
andrewqian2001datadog a84af9d
update tests
andrewqian2001datadog 583e287
fix buffered_metric_context and aggregator, update tests
andrewqian2001datadog b01ed1d
use snake case
andrewqian2001datadog bb863c5
histograms, distribution and timing metrics are not aggregated, they …
andrewqian2001datadog ca4981d
remove max_metric_per_context, not in scope?
andrewqian2001datadog 1091569
lint
andrewqian2001datadog 606b271
fix lint
andrewqian2001datadog a7f2a56
Merge branch 'master' into add-extended-aggregation
andrewqian2001datadog a5d4b15
fix syntax
andrewqian2001datadog 7cf00b1
replace secrets with random
andrewqian2001datadog 92481b9
lint
andrewqian2001datadog 6aa0243
update tests
andrewqian2001datadog 58af7c0
lint
andrewqian2001datadog f5a4cd1
lint
andrewqian2001datadog 3725fca
update test
andrewqian2001datadog 2f8c3fe
test
andrewqian2001datadog 26255de
test
andrewqian2001datadog 44352ac
test2
andrewqian2001datadog 02e3b23
remove comment
andrewqian2001datadog 148ef17
base.py uses aggregator
andrewqian2001datadog c5d9563
lint
andrewqian2001datadog 08091d5
lint
andrewqian2001datadog b482e72
timing metric can be aggregated
andrewqian2001datadog 3f9168a
lint
andrewqian2001datadog a46b5d2
lint
andrewqian2001datadog 1e6d213
remove prints
andrewqian2001datadog 5bbf6c5
add test for testing maybe_keep_sample
andrewqian2001datadog 6809466
fix flushing logic
andrewqian2001datadog 4039b6b
fix tests
andrewqian2001datadog 21b5e08
explictly check if rate is none
andrewqian2001datadog 50337ba
add test for buffered metrics
andrewqian2001datadog fe0c521
rerun test
andrewqian2001datadog 4dcee39
rerun tests 3x
andrewqian2001datadog 0444e99
rerun tests x4
andrewqian2001datadog fc87fa5
what
andrewqian2001datadog f0c4db0
???
andrewqian2001datadog e7b62d2
rerun tests
andrewqian2001datadog 9d4f24a
rerun tests
andrewqian2001datadog 620ea1e
remove unused function
andrewqian2001datadog a54c136
Merge branch 'master' into add-extended-aggregation
andrewqian2001datadog 7d78065
add flag for enabling/disabling extended aggregation
andrewqian2001datadog 393201b
make max_metric_sampels configurable
andrewqian2001datadog dd7e743
remove unecessary lock
andrewqian2001datadog a442722
remove extended aggregation
andrewqian2001datadog 5d872a3
remove extended aggregation
andrewqian2001datadog 0e06501
remove test, not in scope
andrewqian2001datadog 10e9ebe
lint
andrewqian2001datadog 1e2eed8
rename max_metric_samples to max_metric_samples_per_context
andrewqian2001datadog bad35ba
rename buffered_metrics to max_sample_metric, change base.py so that …
andrewqian2001datadog bca7448
more renaming
andrewqian2001datadog 90b083d
lint
andrewqian2001datadog 22c2ac9
more renaming
andrewqian2001datadog 08d150a
lint
andrewqian2001datadog dfd1a29
lint
andrewqian2001datadog 5c86590
use statsd_max_samples_per_context to set the max samples per context…
andrewqian2001datadog 0276bfa
lint
andrewqian2001datadog e50a85f
set max_samples_per_context through Aggregator constructor
andrewqian2001datadog f6d963d
add comments
andrewqian2001datadog 4dbaba5
lint
andrewqian2001datadog b2f714b
remove print
andrewqian2001datadog b46b539
update base.py to not use new feature unless max_samples_per_context …
andrewqian2001datadog b80da4c
remove comment
andrewqian2001datadog c27bf8d
add flag for _report function to enable/disable sampling
andrewqian2001datadog 52fd25f
fix one off error
andrewqian2001datadog ec9a487
remove unused code
andrewqian2001datadog 363d940
use specified rate
andrewqian2001datadog f274b9e
prelocate data in array
andrewqian2001datadog 810353a
change append
andrewqian2001datadog 351a8cc
lint
andrewqian2001datadog a94afa5
lint x2
andrewqian2001datadog d334346
modify test
andrewqian2001datadog ac7c86d
rerun tests
andrewqian2001datadog a4c055a
ensure value is not None
andrewqian2001datadog e3e4c6d
change loop
andrewqian2001datadog aa64e24
use a deep copy
andrewqian2001datadog 721b375
remove unecessary code
andrewqian2001datadog a9c55ce
rerun tests
andrewqian2001datadog 0c551ed
add lock
andrewqian2001datadog f13b48c
remove deep copy
andrewqian2001datadog 60143c6
locks :)
andrewqian2001datadog 5f3bb5b
add unsafe to method name
andrewqian2001datadog 09ee570
Merge branch 'master' into add-extended-aggregation
andrewqian2001datadog c1bc06d
remove prints
andrewqian2001datadog 911b942
change rate calculation
andrewqian2001datadog cbbb938
use list comprehesion
andrewqian2001datadog d3ba223
lint
andrewqian2001datadog 737d48b
lint x2
andrewqian2001datadog f65051e
Merge branch 'master' into add-extended-aggregation
andrewqian2001datadog File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -160,6 +160,7 @@ def __init__( | |
| telemetry_port=None, # type: Union[str, int] | ||
| telemetry_socket_path=None, # type: Text | ||
| max_buffer_len=0, # type: int | ||
| max_metric_samples_per_context=0, # type: int | ||
| container_id=None, # type: Optional[Text] | ||
| origin_detection_enabled=True, # type: bool | ||
| socket_timeout=0, # type: Optional[float] | ||
|
|
@@ -236,9 +237,14 @@ def __init__( | |
| it overrides the default value. | ||
| :type flush_interval: float | ||
|
|
||
| :disable_aggregation: If true, metrics (Count, Gauge, Set) are no longered aggregated by the client | ||
| :disable_aggregation: If true, metrics (Count, Gauge, Set) are no longer aggregated by the client | ||
| :type disable_aggregation: bool | ||
|
|
||
| :max_metric_samples_per_context: Sets the maximum amount of samples for Histogram, Distribution | ||
| and Timings metrics (default 0). This feature should be used alongside aggregation. This feature | ||
| is experimental. | ||
| :type max_metric_samples_per_context: int | ||
|
|
||
| :disable_buffering: If set, metrics are no longered buffered by the client and | ||
| all data is sent synchronously to the server | ||
| :type disable_buffering: bool | ||
|
|
@@ -450,7 +456,7 @@ def __init__( | |
| self._flush_interval = flush_interval | ||
| self._flush_thread = None | ||
| self._flush_thread_stop = threading.Event() | ||
| self.aggregator = Aggregator() | ||
| self.aggregator = Aggregator(max_metric_samples_per_context) | ||
| # Indicates if the process is about to fork, so we shouldn't start any new threads yet. | ||
| self._forking = False | ||
|
|
||
|
|
@@ -643,10 +649,11 @@ def disable_aggregation(self): | |
| self._stop_flush_thread() | ||
| log.debug("Statsd aggregation is disabled") | ||
|
|
||
| def enable_aggregation(self, flush_interval=DEFAULT_BUFFERING_FLUSH_INTERVAL): | ||
| def enable_aggregation(self, flush_interval=DEFAULT_BUFFERING_FLUSH_INTERVAL, max_samples_per_context=0): | ||
| with self._config_lock: | ||
| if not self._disable_aggregation: | ||
| return | ||
| self.aggregator.set_max_samples_per_context(max_samples_per_context) | ||
| self._disable_aggregation = False | ||
| self._flush_interval = flush_interval | ||
| if self._disable_buffering: | ||
|
|
@@ -826,6 +833,10 @@ def flush_aggregated_metrics(self): | |
| for m in metrics: | ||
| self._report(m.name, m.metric_type, m.value, m.tags, m.rate, m.timestamp) | ||
|
|
||
| sampled_metrics = self.aggregator.flush_aggregated_sampled_metrics() | ||
| for m in sampled_metrics: | ||
| self._report(m.name, m.metric_type, m.value, m.tags, m.rate, m.timestamp, False) | ||
|
|
||
| def gauge( | ||
| self, | ||
| metric, # type: Text | ||
|
|
@@ -960,7 +971,10 @@ def histogram( | |
| >>> statsd.histogram("uploaded.file.size", 1445) | ||
| >>> statsd.histogram("album.photo.count", 26, tags=["gender:female"]) | ||
| """ | ||
| self._report(metric, "h", value, tags, sample_rate) | ||
| if not self._disable_aggregation and self.aggregator.max_samples_per_context != 0: | ||
| self.aggregator.histogram(metric, value, tags, sample_rate) | ||
| else: | ||
| self._report(metric, "h", value, tags, sample_rate) | ||
|
|
||
| def distribution( | ||
| self, | ||
|
|
@@ -975,7 +989,10 @@ def distribution( | |
| >>> statsd.distribution("uploaded.file.size", 1445) | ||
| >>> statsd.distribution("album.photo.count", 26, tags=["gender:female"]) | ||
| """ | ||
| self._report(metric, "d", value, tags, sample_rate) | ||
| if not self._disable_aggregation and self.aggregator.max_samples_per_context != 0: | ||
| self.aggregator.distribution(metric, value, tags, sample_rate) | ||
| else: | ||
| self._report(metric, "d", value, tags, sample_rate) | ||
|
|
||
| def timing( | ||
| self, | ||
|
|
@@ -989,7 +1006,10 @@ def timing( | |
|
|
||
| >>> statsd.timing("query.response.time", 1234) | ||
| """ | ||
| self._report(metric, "ms", value, tags, sample_rate) | ||
| if not self._disable_aggregation and self.aggregator.max_samples_per_context != 0: | ||
| self.aggregator.timing(metric, value, tags, sample_rate) | ||
| else: | ||
| self._report(metric, "ms", value, tags, sample_rate) | ||
|
|
||
| def timed(self, metric=None, tags=None, sample_rate=None, use_ms=None): | ||
| """ | ||
|
|
@@ -1093,7 +1113,7 @@ def _serialize_metric( | |
| ("|T" + text(timestamp)) if timestamp > 0 else "", | ||
| ) | ||
|
|
||
| def _report(self, metric, metric_type, value, tags, sample_rate, timestamp=0): | ||
| def _report(self, metric, metric_type, value, tags, sample_rate, timestamp=0, sampling=True): | ||
| """ | ||
| Create a metric packet and send it. | ||
|
|
||
|
|
@@ -1109,11 +1129,12 @@ def _report(self, metric, metric_type, value, tags, sample_rate, timestamp=0): | |
| if self._telemetry: | ||
| self.metrics_count += 1 | ||
|
|
||
| if sample_rate is None: | ||
| sample_rate = self.default_sample_rate | ||
| if sampling: | ||
| if sample_rate is None: | ||
| sample_rate = self.default_sample_rate | ||
|
|
||
| if sample_rate != 1 and random() > sample_rate: | ||
| return | ||
| if sample_rate != 1 and random() > sample_rate: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Code Vulnerabilitydo not use random (...read more)Make sure to use values that are actually random. The Learn More |
||
| return | ||
| # timestamps (protocol v1.3) only allowed on gauges and counts | ||
| allows_timestamp = metric_type == MetricType.GAUGE or metric_type == MetricType.COUNT | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import random | ||
| from datadog.dogstatsd.metric_types import MetricType | ||
| from datadog.dogstatsd.metrics import MetricAggregator | ||
| from threading import Lock | ||
|
|
||
|
|
||
| class MaxSampleMetric(object): | ||
| def __init__(self, name, tags, metric_type, specified_rate=1.0, max_metric_samples=0): | ||
| self.name = name | ||
| self.tags = tags | ||
| self.lock = Lock() | ||
| self.metric_type = metric_type | ||
| self.max_metric_samples = max_metric_samples | ||
| self.specified_rate = specified_rate | ||
| self.data = [None] * max_metric_samples if max_metric_samples > 0 else [] | ||
| self.stored_metric_samples = 0 | ||
| self.total_metric_samples = 0 | ||
|
|
||
| def sample(self, value): | ||
| if self.max_metric_samples == 0: | ||
| self.data.append(value) | ||
| else: | ||
| self.data[self.stored_metric_samples] = value | ||
| self.stored_metric_samples += 1 | ||
| self.total_metric_samples += 1 | ||
|
|
||
| def maybe_keep_sample_work_unsafe(self, value): | ||
| if self.max_metric_samples > 0: | ||
ddrthall marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| self.total_metric_samples += 1 | ||
| if self.stored_metric_samples < self.max_metric_samples: | ||
| self.data[self.stored_metric_samples] = value | ||
| self.stored_metric_samples += 1 | ||
| else: | ||
| i = random.randint(0, self.total_metric_samples - 1) | ||
ddrthall marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if i < self.max_metric_samples: | ||
| self.data[i] = value | ||
| else: | ||
| self.sample(value) | ||
|
|
||
| def skip_sample(self): | ||
| self.total_metric_samples += 1 | ||
|
|
||
| def flush(self): | ||
| rate = self.stored_metric_samples / self.total_metric_samples | ||
| with self.lock: | ||
| return [ | ||
| MetricAggregator(self.name, self.tags, rate, self.metric_type, self.data[i]) | ||
| for i in range(self.stored_metric_samples) | ||
| ] | ||
|
|
||
|
|
||
| class HistogramMetric(MaxSampleMetric): | ||
| def __init__(self, name, tags, rate=1.0, max_metric_samples=0): | ||
| super(HistogramMetric, self).__init__(name, tags, MetricType.HISTOGRAM, rate, max_metric_samples) | ||
|
|
||
|
|
||
| class DistributionMetric(MaxSampleMetric): | ||
| def __init__(self, name, tags, rate=1.0, max_metric_samples=0): | ||
| super(DistributionMetric, self).__init__(name, tags, MetricType.DISTRIBUTION, rate, max_metric_samples) | ||
|
|
||
|
|
||
| class TimingMetric(MaxSampleMetric): | ||
| def __init__(self, name, tags, rate=1.0, max_metric_samples=0): | ||
| super(TimingMetric, self).__init__(name, tags, MetricType.TIMING, rate, max_metric_samples) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| from threading import Lock | ||
| import random | ||
|
|
||
|
|
||
| class MaxSampleMetricContexts: | ||
| def __init__(self, max_sample_metric_type): | ||
| self.lock = Lock() | ||
| self.values = {} | ||
| self.max_sample_metric_type = max_sample_metric_type | ||
|
|
||
| def flush(self): | ||
| metrics = [] | ||
| """Flush the metrics and reset the stored values.""" | ||
| with self.lock: | ||
| temp = self.values | ||
| self.values = {} | ||
| for _, metric in temp.items(): | ||
| metrics.append(metric.flush()) | ||
| return metrics | ||
|
|
||
| def sample(self, name, value, tags, rate, context_key, max_samples_per_context): | ||
| """Sample a metric and store it if it meets the criteria.""" | ||
| keeping_sample = self.should_sample(rate) | ||
| with self.lock: | ||
| if context_key not in self.values: | ||
| # Create a new metric if it doesn't exist | ||
| self.values[context_key] = self.max_sample_metric_type(name, tags, max_samples_per_context) | ||
| metric = self.values[context_key] | ||
| metric.lock.acquire() | ||
| if keeping_sample: | ||
andrewqian2001datadog marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| metric.maybe_keep_sample_work_unsafe(value) | ||
| else: | ||
| metric.skip_sample() | ||
| metric.lock.release() | ||
|
|
||
| def should_sample(self, rate): | ||
| """Determine if a sample should be kept based on the specified rate.""" | ||
| if rate >= 1: | ||
| return True | ||
| return random.random() < rate | ||
andrewqian2001datadog marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,6 @@ class MetricType: | |
| COUNT = "c" | ||
| GAUGE = "g" | ||
| SET = "s" | ||
| HISTOGRAM = "h" | ||
| DISTRIBUTION = "d" | ||
| TIMING = "ms" | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.