From 55130917c77111c9f5c82c1d1ed78be13f0edd4f Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Mon, 26 Nov 2018 16:51:56 -0500 Subject: [PATCH 01/10] Created Lambda wrapper for ThreadStats --- datadog/__init__.py | 2 +- datadog/threadstats/__init__.py | 1 + datadog/threadstats/aws_lambda.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 datadog/threadstats/aws_lambda.py diff --git a/datadog/__init__.py b/datadog/__init__.py index 110ddfa5b..581b3b47a 100644 --- a/datadog/__init__.py +++ b/datadog/__init__.py @@ -15,7 +15,7 @@ # datadog from datadog import api from datadog.dogstatsd import DogStatsd, statsd # noqa -from datadog.threadstats import ThreadStats # noqa +from datadog.threadstats import ThreadStats, datadog_lambda_wrapper, lambda_stats # noqa from datadog.util.compat import iteritems, NullHandler from datadog.util.config import get_version from datadog.util.hostname import get_hostname diff --git a/datadog/threadstats/__init__.py b/datadog/threadstats/__init__.py index eec391a83..261dbc905 100644 --- a/datadog/threadstats/__init__.py +++ b/datadog/threadstats/__init__.py @@ -1 +1,2 @@ from datadog.threadstats.base import ThreadStats # noqa +from datadog.threadstats.aws_lambda import lambda_stats, datadog_lambda_wrapper # noqa diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py new file mode 100644 index 000000000..75f0a0365 --- /dev/null +++ b/datadog/threadstats/aws_lambda.py @@ -0,0 +1,54 @@ +from datadog.threadstats import ThreadStats +from threading import Lock + + +""" +Usage: + +from datadog import datadog_lambda_wrapper, lambda_stats + +@datadog_lambda_wrapper +def my_lambda_handle(event, context): + lambda_stats.increment("some_metric", 10) +""" + + +class _LambdaDecorator(object): + _counter = 0 # Number of opened wrappers, flush when 0 + _counter_lock = Lock() + _was_initialized = False + + def __init__(self, func): + self.func = func + + @classmethod + def _enter(cls): + with cls._counter_lock: + if not cls._was_initialized: + cls._was_initialized = True + from datadog import initialize # Got blood on my hands now + initialize() + lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False) + cls._counter = cls._counter + 1 + + @classmethod + def _close(cls): + should_flush = False + with cls._counter_lock: + cls._counter = cls._counter - 1 + + if cls._counter <= 0: # Flush only when all wrappers are closed + should_flush = True + + if should_flush: + lambda_stats.flush(float("inf")) + + def __call__(self, *args, **kw): + _LambdaDecorator._enter() + result = self.func(*args, **kw) + _LambdaDecorator._close() + return result + + +lambda_stats = ThreadStats() +datadog_lambda_wrapper = _LambdaDecorator From c6b7471ae131afb2dfd0c1885019ac75dc3659ac Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Mon, 26 Nov 2018 16:54:31 -0500 Subject: [PATCH 02/10] Added unit tests for lambda wrapper --- tests/unit/threadstats/test_threadstats.py | 67 +++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/tests/unit/threadstats/test_threadstats.py b/tests/unit/threadstats/test_threadstats.py index 5e14d99fc..5a49931f0 100644 --- a/tests/unit/threadstats/test_threadstats.py +++ b/tests/unit/threadstats/test_threadstats.py @@ -14,10 +14,9 @@ import nose.tools as nt # datadog -from datadog import ThreadStats +from datadog import ThreadStats, lambda_stats, datadog_lambda_wrapper from tests.util.contextmanagers import preserve_environment_variable - # Silence the logger. logger = logging.getLogger('dd.datadogpy') logger.setLevel(logging.ERROR) @@ -43,6 +42,12 @@ def flush_events(self, events): self.events += events +@datadog_lambda_wrapper +def wrapped_init(): + """The first opened wrapper calls the "start" method, which would override the MemoryReporter""" + pass + + class TestUnitThreadStats(unittest.TestCase): """ Unit tests for ThreadStats. @@ -740,3 +745,61 @@ def test_metric_type(self): nt.assert_equal(cnt['type'], 'rate') nt.assert_equal(max_['type'], 'gauge') nt.assert_equal(min_['type'], 'gauge') + + + # Test lambda_wrapper (uses ThreadStats under the hood) + + def test_basic_lambda_decorator(self): + + @datadog_lambda_wrapper + def basic_wrapped_function(): # Test custom_metric function + lambda_stats.distribution("lambda.somemetric", 100, 300) + + wrapped_init() # Empty run to make the initialization + + lambda_stats.reporter = self.reporter + basic_wrapped_function() + + dists = self.sort_metrics(lambda_stats.reporter.distributions) + nt.assert_equal(len(dists), 1) + lambda_stats.reporter.distributions = [] + + def test_embedded_lambda_decorator(self): + """ + Test that the lambda decorator flushes metrics correctly and only once + """ + + @datadog_lambda_wrapper + def wrapped_function_1(): + lambda_stats.gauge("lambda.gauge.1", 10, 100) + nt.assert_equal(datadog_lambda_wrapper._counter, 2) + + @datadog_lambda_wrapper + def wrapped_function_2(): + wrapped_function_1() # Embedded wrappers + + # Check that wrapper_function_1() didn't flush + metrics = self.sort_metrics(lambda_stats.reporter.metrics) + nt.assert_equal(len(metrics), 0) + + lambda_stats.gauge("lambda.gauge.2", 30, 200) + nt.assert_equal(datadog_lambda_wrapper._counter, 1) + + wrapped_init() # Empty run to make the initialization + + lambda_stats.reporter = self.reporter + + nt.assert_equal(datadog_lambda_wrapper._counter, 0) + wrapped_function_2() + nt.assert_equal(datadog_lambda_wrapper._counter, 0) + + metrics = self.sort_metrics(lambda_stats.reporter.metrics) + nt.assert_equal(len(metrics), 2) + + (first, second) = metrics + nt.assert_equal(first['metric'], 'lambda.gauge.1') + nt.assert_equal(first['points'][0][0], 100) + nt.assert_equal(first['points'][0][1], 10) + nt.assert_equal(second['metric'], 'lambda.gauge.2') + nt.assert_equal(second['points'][0][0], 200) + nt.assert_equal(second['points'][0][1], 30) From 245f358a7c43b613b9bf35c0395c639a7e415216 Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Mon, 26 Nov 2018 16:54:53 -0500 Subject: [PATCH 03/10] Add thread safety test for lambda wrapper (muted for python2.7) --- .../test_lambda_wrapper_thread_safety.py | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 tests/performance/test_lambda_wrapper_thread_safety.py diff --git a/tests/performance/test_lambda_wrapper_thread_safety.py b/tests/performance/test_lambda_wrapper_thread_safety.py new file mode 100644 index 000000000..d5f486e19 --- /dev/null +++ b/tests/performance/test_lambda_wrapper_thread_safety.py @@ -0,0 +1,101 @@ +import re +import time +# import unittest +import threading +from nose import tools as t + +from datadog import lambda_stats, datadog_lambda_wrapper + + +class MemoryReporter(object): + """ A reporting class that reports to memory for testing. """ + + def __init__(self): + self.metrics = [] + self.events = [] + + def flush_metrics(self, metrics): + self.metrics += metrics + + def flush_events(self, events): + self.events += events + + +@datadog_lambda_wrapper +def wrapped_function(id): + + t.assert_greater(datadog_lambda_wrapper._counter, 1) + # Increment + lambda_stats.increment("counter", timestamp=12345) + time.sleep(0.001) # sleep makes the os continue another thread + + # Gauge + lambda_stats.gauge("gauge_" + str(id), 42) + time.sleep(0.001) # sleep makes the os continue another thread + + # Histogram + lambda_stats.histogram("histogram", id, timestamp=12345) + time.sleep(0.001) # sleep makes the os continue another thread + + # Event + lambda_stats.event("title", "content") + + +@datadog_lambda_wrapper +def wrapped_init(): + pass + + +class TestWrapperThreadSafety(object): + + def test_wrapper_thread_safety(self): + + wrapped_init() # Empty run to make the initialization + lambda_stats.reporter = MemoryReporter() + datadog_lambda_wrapper._counter = 1 + + for i in range(10000): + threading.Thread(target=wrapped_function, args=[i]).start() + # Wait all threads to finish + time.sleep(10) + + # Flush and check + t.assert_equal(datadog_lambda_wrapper._counter, 1) + lambda_stats.flush(float("inf")) + + metrics = lambda_stats.reporter.metrics + events = lambda_stats.reporter.events + + # Overview + t.assert_equal(len(metrics), 10009, len(metrics)) + + # Sort metrics + counter_metrics = [] + gauge_metrics = [] + histogram_metrics = [] + + for m in metrics: + if re.match("gauge_.*", m['metric']): + gauge_metrics.append(m) + elif re.match("histogram.*", m['metric']): + histogram_metrics.append(m) + else: + counter_metrics.append(m) + + # Counter + t.assert_equal(len(counter_metrics), 1, len(counter_metrics)) + counter = counter_metrics[0] + t.assert_equal(counter['points'][0][1], 10000, counter['points'][0][1]) + + # Gauge + t.assert_equal(len(gauge_metrics), 10000, len(gauge_metrics)) + + # Histogram + t.assert_equal(len(histogram_metrics), 8, len(histogram_metrics)) + count_histogram = filter(lambda x: x['metric'] == "histogram.count", histogram_metrics)[0] + t.assert_equal(count_histogram['points'][0][1], 10000, count_histogram['points'][0][1]) + sum_histogram = filter(lambda x: x['metric'] == "histogram.avg", histogram_metrics)[0] + t.assert_equal(sum_histogram['points'][0][1], 4999.5, sum_histogram['points'][0][1]) + + # Events + t.assert_equal(10000, len(events), len(events)) From b8fbe16247270ce9eb5068c83aa0214b19dcb4cc Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 11:28:17 -0500 Subject: [PATCH 04/10] Lambda wrapper - changed init --- datadog/threadstats/aws_lambda.py | 7 ++++--- tests/performance/test_lambda_wrapper_thread_safety.py | 7 ------- tests/unit/threadstats/test_threadstats.py | 10 ---------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py index 75f0a0365..86f7c6517 100644 --- a/datadog/threadstats/aws_lambda.py +++ b/datadog/threadstats/aws_lambda.py @@ -1,5 +1,7 @@ from datadog.threadstats import ThreadStats from threading import Lock +from datadog import api +import os """ @@ -26,9 +28,7 @@ def _enter(cls): with cls._counter_lock: if not cls._was_initialized: cls._was_initialized = True - from datadog import initialize # Got blood on my hands now - initialize() - lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False) + api._api_key = os.environ.get('DATADOG_API_KEY') cls._counter = cls._counter + 1 @classmethod @@ -51,4 +51,5 @@ def __call__(self, *args, **kw): lambda_stats = ThreadStats() +lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False) datadog_lambda_wrapper = _LambdaDecorator diff --git a/tests/performance/test_lambda_wrapper_thread_safety.py b/tests/performance/test_lambda_wrapper_thread_safety.py index d5f486e19..7a9271008 100644 --- a/tests/performance/test_lambda_wrapper_thread_safety.py +++ b/tests/performance/test_lambda_wrapper_thread_safety.py @@ -41,16 +41,9 @@ def wrapped_function(id): lambda_stats.event("title", "content") -@datadog_lambda_wrapper -def wrapped_init(): - pass - - class TestWrapperThreadSafety(object): def test_wrapper_thread_safety(self): - - wrapped_init() # Empty run to make the initialization lambda_stats.reporter = MemoryReporter() datadog_lambda_wrapper._counter = 1 diff --git a/tests/unit/threadstats/test_threadstats.py b/tests/unit/threadstats/test_threadstats.py index 5a49931f0..2388979a7 100644 --- a/tests/unit/threadstats/test_threadstats.py +++ b/tests/unit/threadstats/test_threadstats.py @@ -42,12 +42,6 @@ def flush_events(self, events): self.events += events -@datadog_lambda_wrapper -def wrapped_init(): - """The first opened wrapper calls the "start" method, which would override the MemoryReporter""" - pass - - class TestUnitThreadStats(unittest.TestCase): """ Unit tests for ThreadStats. @@ -755,8 +749,6 @@ def test_basic_lambda_decorator(self): def basic_wrapped_function(): # Test custom_metric function lambda_stats.distribution("lambda.somemetric", 100, 300) - wrapped_init() # Empty run to make the initialization - lambda_stats.reporter = self.reporter basic_wrapped_function() @@ -785,8 +777,6 @@ def wrapped_function_2(): lambda_stats.gauge("lambda.gauge.2", 30, 200) nt.assert_equal(datadog_lambda_wrapper._counter, 1) - wrapped_init() # Empty run to make the initialization - lambda_stats.reporter = self.reporter nt.assert_equal(datadog_lambda_wrapper._counter, 0) From f36399599223b7ccafcbf42df2d90fd56f959067 Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 11:35:56 -0500 Subject: [PATCH 05/10] Lambda Wrapper - add flush lock --- datadog/threadstats/aws_lambda.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py index 86f7c6517..4ca111e48 100644 --- a/datadog/threadstats/aws_lambda.py +++ b/datadog/threadstats/aws_lambda.py @@ -18,6 +18,7 @@ def my_lambda_handle(event, context): class _LambdaDecorator(object): _counter = 0 # Number of opened wrappers, flush when 0 _counter_lock = Lock() + _flush_lock = Lock() _was_initialized = False def __init__(self, func): @@ -41,7 +42,13 @@ def _close(cls): should_flush = True if should_flush: - lambda_stats.flush(float("inf")) + with cls._flush_lock: + # Don't flush if other wrappers were opened while _flush_lock was locked + with cls._counter_lock: + if cls._counter > 0: + should_flush = False + if should_flush: + lambda_stats.flush(float("inf")) def __call__(self, *args, **kw): _LambdaDecorator._enter() From d41ee0412956c1d18105e25a2022a91384d6dace Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 12:04:13 -0500 Subject: [PATCH 06/10] Lambda Wrapper - Renamed lambda_stats & clean up tests --- datadog/threadstats/aws_lambda.py | 11 ++- .../test_lambda_wrapper_thread_safety.py | 88 +++++-------------- tests/unit/threadstats/test_threadstats.py | 42 +++------ 3 files changed, 42 insertions(+), 99 deletions(-) diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py index 4ca111e48..a11504a44 100644 --- a/datadog/threadstats/aws_lambda.py +++ b/datadog/threadstats/aws_lambda.py @@ -48,7 +48,7 @@ def _close(cls): if cls._counter > 0: should_flush = False if should_flush: - lambda_stats.flush(float("inf")) + _lambda_stats.flush(float("inf")) def __call__(self, *args, **kw): _LambdaDecorator._enter() @@ -57,6 +57,11 @@ def __call__(self, *args, **kw): return result -lambda_stats = ThreadStats() -lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False) +_lambda_stats = ThreadStats() +_lambda_stats.start(flush_in_greenlet=False, flush_in_thread=False) datadog_lambda_wrapper = _LambdaDecorator + + +def lambda_stats(*args, **kw): + """ Alias to expose only distributions for lambda functions""" + _lambda_stats.distribution(*args, **kw) diff --git a/tests/performance/test_lambda_wrapper_thread_safety.py b/tests/performance/test_lambda_wrapper_thread_safety.py index 7a9271008..535d1ea66 100644 --- a/tests/performance/test_lambda_wrapper_thread_safety.py +++ b/tests/performance/test_lambda_wrapper_thread_safety.py @@ -1,94 +1,46 @@ -import re import time -# import unittest +import unittest import threading -from nose import tools as t from datadog import lambda_stats, datadog_lambda_wrapper +from datadog.threadstats.aws_lambda import _lambda_stats + + +TOTAL_NUMBER_OF_THREADS = 1000 class MemoryReporter(object): """ A reporting class that reports to memory for testing. """ def __init__(self): - self.metrics = [] - self.events = [] - - def flush_metrics(self, metrics): - self.metrics += metrics + self.distributions = [] + self.dist_flush_counter = 0 - def flush_events(self, events): - self.events += events + def flush_distributions(self, dists): + self.distributions += dists + self.dist_flush_counter = self.dist_flush_counter + 1 @datadog_lambda_wrapper def wrapped_function(id): - - t.assert_greater(datadog_lambda_wrapper._counter, 1) - # Increment - lambda_stats.increment("counter", timestamp=12345) + lambda_stats("dist_" + str(id), 42) time.sleep(0.001) # sleep makes the os continue another thread - # Gauge - lambda_stats.gauge("gauge_" + str(id), 42) - time.sleep(0.001) # sleep makes the os continue another thread + lambda_stats("common_dist", 42) - # Histogram - lambda_stats.histogram("histogram", id, timestamp=12345) - time.sleep(0.001) # sleep makes the os continue another thread - - # Event - lambda_stats.event("title", "content") - -class TestWrapperThreadSafety(object): +class TestWrapperThreadSafety(unittest.TestCase): def test_wrapper_thread_safety(self): - lambda_stats.reporter = MemoryReporter() - datadog_lambda_wrapper._counter = 1 + _lambda_stats.reporter = MemoryReporter() - for i in range(10000): + for i in range(TOTAL_NUMBER_OF_THREADS): threading.Thread(target=wrapped_function, args=[i]).start() # Wait all threads to finish time.sleep(10) - # Flush and check - t.assert_equal(datadog_lambda_wrapper._counter, 1) - lambda_stats.flush(float("inf")) - - metrics = lambda_stats.reporter.metrics - events = lambda_stats.reporter.events - - # Overview - t.assert_equal(len(metrics), 10009, len(metrics)) - - # Sort metrics - counter_metrics = [] - gauge_metrics = [] - histogram_metrics = [] - - for m in metrics: - if re.match("gauge_.*", m['metric']): - gauge_metrics.append(m) - elif re.match("histogram.*", m['metric']): - histogram_metrics.append(m) - else: - counter_metrics.append(m) - - # Counter - t.assert_equal(len(counter_metrics), 1, len(counter_metrics)) - counter = counter_metrics[0] - t.assert_equal(counter['points'][0][1], 10000, counter['points'][0][1]) - - # Gauge - t.assert_equal(len(gauge_metrics), 10000, len(gauge_metrics)) - - # Histogram - t.assert_equal(len(histogram_metrics), 8, len(histogram_metrics)) - count_histogram = filter(lambda x: x['metric'] == "histogram.count", histogram_metrics)[0] - t.assert_equal(count_histogram['points'][0][1], 10000, count_histogram['points'][0][1]) - sum_histogram = filter(lambda x: x['metric'] == "histogram.avg", histogram_metrics)[0] - t.assert_equal(sum_histogram['points'][0][1], 4999.5, sum_histogram['points'][0][1]) - - # Events - t.assert_equal(10000, len(events), len(events)) + # Check that at least one flush happened + self.assertGreater(_lambda_stats.reporter.dist_flush_counter, 0) + + dists = _lambda_stats.reporter.distributions + self.assertEqual(len(dists), TOTAL_NUMBER_OF_THREADS + 1) diff --git a/tests/unit/threadstats/test_threadstats.py b/tests/unit/threadstats/test_threadstats.py index 2388979a7..bbd130486 100644 --- a/tests/unit/threadstats/test_threadstats.py +++ b/tests/unit/threadstats/test_threadstats.py @@ -15,6 +15,7 @@ # datadog from datadog import ThreadStats, lambda_stats, datadog_lambda_wrapper +from datadog.threadstats.aws_lambda import _lambda_stats from tests.util.contextmanagers import preserve_environment_variable # Silence the logger. @@ -31,9 +32,11 @@ def __init__(self): self.distributions = [] self.metrics = [] self.events = [] + self.dist_flush_counter = 0 def flush_distributions(self, distributions): self.distributions += distributions + self.dist_flush_counter = self.dist_flush_counter + 1 def flush_metrics(self, metrics): self.metrics += metrics @@ -747,14 +750,14 @@ def test_basic_lambda_decorator(self): @datadog_lambda_wrapper def basic_wrapped_function(): # Test custom_metric function - lambda_stats.distribution("lambda.somemetric", 100, 300) + lambda_stats("lambda.somemetric", 100) - lambda_stats.reporter = self.reporter + _lambda_stats.reporter = self.reporter basic_wrapped_function() - dists = self.sort_metrics(lambda_stats.reporter.distributions) + nt.assert_equal(_lambda_stats.reporter.dist_flush_counter, 1) + dists = self.sort_metrics(_lambda_stats.reporter.distributions) nt.assert_equal(len(dists), 1) - lambda_stats.reporter.distributions = [] def test_embedded_lambda_decorator(self): """ @@ -763,33 +766,16 @@ def test_embedded_lambda_decorator(self): @datadog_lambda_wrapper def wrapped_function_1(): - lambda_stats.gauge("lambda.gauge.1", 10, 100) - nt.assert_equal(datadog_lambda_wrapper._counter, 2) + lambda_stats("lambda.dist.1", 10) @datadog_lambda_wrapper def wrapped_function_2(): - wrapped_function_1() # Embedded wrappers + wrapped_function_1() + lambda_stats("lambda.dist.2", 30) - # Check that wrapper_function_1() didn't flush - metrics = self.sort_metrics(lambda_stats.reporter.metrics) - nt.assert_equal(len(metrics), 0) - - lambda_stats.gauge("lambda.gauge.2", 30, 200) - nt.assert_equal(datadog_lambda_wrapper._counter, 1) - - lambda_stats.reporter = self.reporter - - nt.assert_equal(datadog_lambda_wrapper._counter, 0) + _lambda_stats.reporter = self.reporter wrapped_function_2() - nt.assert_equal(datadog_lambda_wrapper._counter, 0) + nt.assert_equal(_lambda_stats.reporter.dist_flush_counter, 1) - metrics = self.sort_metrics(lambda_stats.reporter.metrics) - nt.assert_equal(len(metrics), 2) - - (first, second) = metrics - nt.assert_equal(first['metric'], 'lambda.gauge.1') - nt.assert_equal(first['points'][0][0], 100) - nt.assert_equal(first['points'][0][1], 10) - nt.assert_equal(second['metric'], 'lambda.gauge.2') - nt.assert_equal(second['points'][0][0], 200) - nt.assert_equal(second['points'][0][1], 30) + dists = self.sort_metrics(_lambda_stats.reporter.distributions) + nt.assert_equal(len(dists), 2) From f20e2bfd29eacdb195a165ae8c5c5fd5648f46b3 Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 12:06:29 -0500 Subject: [PATCH 07/10] Lambda Wrapper - Homogenize comments --- datadog/threadstats/aws_lambda.py | 8 ++++++-- tests/performance/test_lambda_wrapper_thread_safety.py | 3 ++- tests/unit/threadstats/test_threadstats.py | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py index a11504a44..8aa9c363a 100644 --- a/datadog/threadstats/aws_lambda.py +++ b/datadog/threadstats/aws_lambda.py @@ -16,7 +16,10 @@ def my_lambda_handle(event, context): class _LambdaDecorator(object): - _counter = 0 # Number of opened wrappers, flush when 0 + """ Decorator to automatically init & flush metrics, created for Lambda functions""" + + # Number of opened wrappers, flush when 0 + _counter = 0 _counter_lock = Lock() _flush_lock = Lock() _was_initialized = False @@ -38,7 +41,8 @@ def _close(cls): with cls._counter_lock: cls._counter = cls._counter - 1 - if cls._counter <= 0: # Flush only when all wrappers are closed + # Flush only when all wrappers are closed + if cls._counter <= 0: should_flush = True if should_flush: diff --git a/tests/performance/test_lambda_wrapper_thread_safety.py b/tests/performance/test_lambda_wrapper_thread_safety.py index 535d1ea66..42a1bfff8 100644 --- a/tests/performance/test_lambda_wrapper_thread_safety.py +++ b/tests/performance/test_lambda_wrapper_thread_safety.py @@ -24,7 +24,8 @@ def flush_distributions(self, dists): @datadog_lambda_wrapper def wrapped_function(id): lambda_stats("dist_" + str(id), 42) - time.sleep(0.001) # sleep makes the os continue another thread + # sleep makes the os continue another thread + time.sleep(0.001) lambda_stats("common_dist", 42) diff --git a/tests/unit/threadstats/test_threadstats.py b/tests/unit/threadstats/test_threadstats.py index bbd130486..b6ab94275 100644 --- a/tests/unit/threadstats/test_threadstats.py +++ b/tests/unit/threadstats/test_threadstats.py @@ -749,7 +749,7 @@ def test_metric_type(self): def test_basic_lambda_decorator(self): @datadog_lambda_wrapper - def basic_wrapped_function(): # Test custom_metric function + def basic_wrapped_function(): lambda_stats("lambda.somemetric", 100) _lambda_stats.reporter = self.reporter From 427a2e6facecb4ae6f1c208b8b7f8ccd6aa4e6ec Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 12:14:56 -0500 Subject: [PATCH 08/10] Lambda wrapper - mute thread safety test, python 2.7 issues --- tests/performance/test_lambda_wrapper_thread_safety.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/performance/test_lambda_wrapper_thread_safety.py b/tests/performance/test_lambda_wrapper_thread_safety.py index 42a1bfff8..10f1622c8 100644 --- a/tests/performance/test_lambda_wrapper_thread_safety.py +++ b/tests/performance/test_lambda_wrapper_thread_safety.py @@ -1,5 +1,5 @@ import time -import unittest +# import unittest import threading from datadog import lambda_stats, datadog_lambda_wrapper @@ -30,7 +30,7 @@ def wrapped_function(id): lambda_stats("common_dist", 42) -class TestWrapperThreadSafety(unittest.TestCase): +class TestWrapperThreadSafety(object): def test_wrapper_thread_safety(self): _lambda_stats.reporter = MemoryReporter() From b8017bdaff5a85fa358d384311c7f6c999db329b Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 13:18:54 -0500 Subject: [PATCH 09/10] Lambda wrapper - add api host to init --- datadog/threadstats/aws_lambda.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py index 8aa9c363a..970ff726b 100644 --- a/datadog/threadstats/aws_lambda.py +++ b/datadog/threadstats/aws_lambda.py @@ -33,6 +33,7 @@ def _enter(cls): if not cls._was_initialized: cls._was_initialized = True api._api_key = os.environ.get('DATADOG_API_KEY') + api._api_host = os.environ.get('DATADOG_HOST', 'https://api.datadoghq.com') cls._counter = cls._counter + 1 @classmethod From 212744bffd6e6492be273c1179f20d6fb3f4a274 Mon Sep 17 00:00:00 2001 From: Quentin Pierre Date: Tue, 27 Nov 2018 16:19:02 -0500 Subject: [PATCH 10/10] Lambda Wrapper - Rename lambda_stats -> lambda_metric --- datadog/__init__.py | 2 +- datadog/threadstats/__init__.py | 2 +- datadog/threadstats/aws_lambda.py | 6 +++--- tests/performance/test_lambda_wrapper_thread_safety.py | 6 +++--- tests/unit/threadstats/test_threadstats.py | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/datadog/__init__.py b/datadog/__init__.py index 581b3b47a..5a061a89c 100644 --- a/datadog/__init__.py +++ b/datadog/__init__.py @@ -15,7 +15,7 @@ # datadog from datadog import api from datadog.dogstatsd import DogStatsd, statsd # noqa -from datadog.threadstats import ThreadStats, datadog_lambda_wrapper, lambda_stats # noqa +from datadog.threadstats import ThreadStats, datadog_lambda_wrapper, lambda_metric # noqa from datadog.util.compat import iteritems, NullHandler from datadog.util.config import get_version from datadog.util.hostname import get_hostname diff --git a/datadog/threadstats/__init__.py b/datadog/threadstats/__init__.py index 261dbc905..6b6d3e4a1 100644 --- a/datadog/threadstats/__init__.py +++ b/datadog/threadstats/__init__.py @@ -1,2 +1,2 @@ from datadog.threadstats.base import ThreadStats # noqa -from datadog.threadstats.aws_lambda import lambda_stats, datadog_lambda_wrapper # noqa +from datadog.threadstats.aws_lambda import lambda_metric, datadog_lambda_wrapper # noqa diff --git a/datadog/threadstats/aws_lambda.py b/datadog/threadstats/aws_lambda.py index 970ff726b..6e8c3e057 100644 --- a/datadog/threadstats/aws_lambda.py +++ b/datadog/threadstats/aws_lambda.py @@ -7,11 +7,11 @@ """ Usage: -from datadog import datadog_lambda_wrapper, lambda_stats +from datadog import datadog_lambda_wrapper, lambda_metric @datadog_lambda_wrapper def my_lambda_handle(event, context): - lambda_stats.increment("some_metric", 10) + lambda_metric("some_metric", 10) """ @@ -67,6 +67,6 @@ def __call__(self, *args, **kw): datadog_lambda_wrapper = _LambdaDecorator -def lambda_stats(*args, **kw): +def lambda_metric(*args, **kw): """ Alias to expose only distributions for lambda functions""" _lambda_stats.distribution(*args, **kw) diff --git a/tests/performance/test_lambda_wrapper_thread_safety.py b/tests/performance/test_lambda_wrapper_thread_safety.py index 10f1622c8..bb73f754e 100644 --- a/tests/performance/test_lambda_wrapper_thread_safety.py +++ b/tests/performance/test_lambda_wrapper_thread_safety.py @@ -2,7 +2,7 @@ # import unittest import threading -from datadog import lambda_stats, datadog_lambda_wrapper +from datadog import lambda_metric, datadog_lambda_wrapper from datadog.threadstats.aws_lambda import _lambda_stats @@ -23,11 +23,11 @@ def flush_distributions(self, dists): @datadog_lambda_wrapper def wrapped_function(id): - lambda_stats("dist_" + str(id), 42) + lambda_metric("dist_" + str(id), 42) # sleep makes the os continue another thread time.sleep(0.001) - lambda_stats("common_dist", 42) + lambda_metric("common_dist", 42) class TestWrapperThreadSafety(object): diff --git a/tests/unit/threadstats/test_threadstats.py b/tests/unit/threadstats/test_threadstats.py index b6ab94275..e4c8e6059 100644 --- a/tests/unit/threadstats/test_threadstats.py +++ b/tests/unit/threadstats/test_threadstats.py @@ -14,7 +14,7 @@ import nose.tools as nt # datadog -from datadog import ThreadStats, lambda_stats, datadog_lambda_wrapper +from datadog import ThreadStats, lambda_metric, datadog_lambda_wrapper from datadog.threadstats.aws_lambda import _lambda_stats from tests.util.contextmanagers import preserve_environment_variable @@ -750,7 +750,7 @@ def test_basic_lambda_decorator(self): @datadog_lambda_wrapper def basic_wrapped_function(): - lambda_stats("lambda.somemetric", 100) + lambda_metric("lambda.somemetric", 100) _lambda_stats.reporter = self.reporter basic_wrapped_function() @@ -766,12 +766,12 @@ def test_embedded_lambda_decorator(self): @datadog_lambda_wrapper def wrapped_function_1(): - lambda_stats("lambda.dist.1", 10) + lambda_metric("lambda.dist.1", 10) @datadog_lambda_wrapper def wrapped_function_2(): wrapped_function_1() - lambda_stats("lambda.dist.2", 30) + lambda_metric("lambda.dist.2", 30) _lambda_stats.reporter = self.reporter wrapped_function_2()