deltacat 1.1.1__py3-none-any.whl → 1.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deltacat/__init__.py +1 -1
- deltacat/aws/clients.py +12 -2
- deltacat/aws/constants.py +5 -1
- deltacat/aws/s3u.py +8 -1
- deltacat/compute/compactor_v2/constants.py +28 -0
- deltacat/compute/compactor_v2/deletes/utils.py +3 -0
- deltacat/compute/compactor_v2/steps/hash_bucket.py +9 -2
- deltacat/compute/compactor_v2/steps/merge.py +9 -2
- deltacat/compute/compactor_v2/utils/io.py +3 -0
- deltacat/compute/compactor_v2/utils/merge.py +3 -0
- deltacat/tests/utils/test_metrics.py +502 -0
- deltacat/utils/metrics.py +175 -23
- {deltacat-1.1.1.dist-info → deltacat-1.1.3.dist-info}/METADATA +1 -1
- {deltacat-1.1.1.dist-info → deltacat-1.1.3.dist-info}/RECORD +17 -16
- {deltacat-1.1.1.dist-info → deltacat-1.1.3.dist-info}/LICENSE +0 -0
- {deltacat-1.1.1.dist-info → deltacat-1.1.3.dist-info}/WHEEL +0 -0
- {deltacat-1.1.1.dist-info → deltacat-1.1.3.dist-info}/top_level.txt +0 -0
deltacat/__init__.py
CHANGED
deltacat/aws/clients.py
CHANGED
@@ -4,6 +4,7 @@ from typing import Optional
|
|
4
4
|
from http import HTTPStatus
|
5
5
|
|
6
6
|
import boto3
|
7
|
+
from botocore.exceptions import CredentialRetrievalError
|
7
8
|
from boto3.exceptions import ResourceNotExistsError
|
8
9
|
from boto3.resources.base import ServiceResource
|
9
10
|
from botocore.client import BaseClient
|
@@ -15,6 +16,8 @@ from tenacity import (
|
|
15
16
|
wait_fixed,
|
16
17
|
retry_if_exception,
|
17
18
|
stop_after_delay,
|
19
|
+
retry_if_exception_type,
|
20
|
+
wait_random_exponential,
|
18
21
|
)
|
19
22
|
|
20
23
|
from deltacat import logs
|
@@ -37,6 +40,13 @@ RETRYABLE_HTTP_STATUS_CODES = [
|
|
37
40
|
HTTPStatus.GATEWAY_TIMEOUT,
|
38
41
|
]
|
39
42
|
|
43
|
+
boto_retry_wrapper = Retrying(
|
44
|
+
wait=wait_random_exponential(multiplier=1, max=10),
|
45
|
+
stop=stop_after_delay(60 * 5),
|
46
|
+
# CredentialRetrievalError can still be thrown due to throttling, even if IMDS health checks succeed.
|
47
|
+
retry=retry_if_exception_type(CredentialRetrievalError),
|
48
|
+
)
|
49
|
+
|
40
50
|
|
41
51
|
class RetryIfRetryableHTTPStatusCode(retry_if_exception):
|
42
52
|
"""
|
@@ -183,10 +193,10 @@ def _client(name: str, region: Optional[str], **kwargs) -> BaseClient:
|
|
183
193
|
def resource_cache(name: str, region: Optional[str], **kwargs) -> ServiceResource:
|
184
194
|
# we don't use the @lru_cache decorator because Ray can't pickle it
|
185
195
|
cached_function = lru_cache()(_resource)
|
186
|
-
return cached_function
|
196
|
+
return boto_retry_wrapper(cached_function, name, region, **kwargs)
|
187
197
|
|
188
198
|
|
189
199
|
def client_cache(name: str, region: Optional[str], **kwargs) -> BaseClient:
|
190
200
|
# we don't use the @lru_cache decorator because Ray can't pickle it
|
191
201
|
cached_function = lru_cache()(_client)
|
192
|
-
return cached_function
|
202
|
+
return boto_retry_wrapper(cached_function, name, region, **kwargs)
|
deltacat/aws/constants.py
CHANGED
@@ -3,6 +3,10 @@ from typing import List
|
|
3
3
|
from deltacat.utils.common import env_integer, env_string
|
4
4
|
|
5
5
|
DAFT_MAX_S3_CONNECTIONS_PER_FILE = env_integer("DAFT_MAX_S3_CONNECTIONS_PER_FILE", 8)
|
6
|
-
BOTO_MAX_RETRIES = env_integer("BOTO_MAX_RETRIES",
|
6
|
+
BOTO_MAX_RETRIES = env_integer("BOTO_MAX_RETRIES", 5)
|
7
7
|
TIMEOUT_ERROR_CODES: List[str] = ["ReadTimeoutError", "ConnectTimeoutError"]
|
8
8
|
AWS_REGION = env_string("AWS_REGION", "us-east-1")
|
9
|
+
|
10
|
+
# Metric Names
|
11
|
+
DOWNLOAD_MANIFEST_ENTRY_METRIC_PREFIX = "download_manifest_entry"
|
12
|
+
UPLOAD_SLICED_TABLE_METRIC_PREFIX = "upload_sliced_table"
|
deltacat/aws/s3u.py
CHANGED
@@ -25,7 +25,11 @@ from tenacity import (
|
|
25
25
|
from deltacat.utils.ray_utils.concurrency import invoke_parallel
|
26
26
|
import deltacat.aws.clients as aws_utils
|
27
27
|
from deltacat import logs
|
28
|
-
from deltacat.aws.constants import
|
28
|
+
from deltacat.aws.constants import (
|
29
|
+
TIMEOUT_ERROR_CODES,
|
30
|
+
DOWNLOAD_MANIFEST_ENTRY_METRIC_PREFIX,
|
31
|
+
UPLOAD_SLICED_TABLE_METRIC_PREFIX,
|
32
|
+
)
|
29
33
|
from deltacat.exceptions import NonRetryableError, RetryableError
|
30
34
|
from deltacat.storage import (
|
31
35
|
DistributedDataset,
|
@@ -50,6 +54,7 @@ from deltacat.types.tables import (
|
|
50
54
|
)
|
51
55
|
from deltacat.types.partial_download import PartialFileDownloadParams
|
52
56
|
from deltacat.utils.common import ReadKwargsProvider
|
57
|
+
from deltacat.utils.metrics import metrics
|
53
58
|
|
54
59
|
logger = logs.configure_deltacat_logger(logging.getLogger(__name__))
|
55
60
|
|
@@ -238,6 +243,7 @@ def read_file(
|
|
238
243
|
raise e
|
239
244
|
|
240
245
|
|
246
|
+
@metrics(prefix=UPLOAD_SLICED_TABLE_METRIC_PREFIX)
|
241
247
|
def upload_sliced_table(
|
242
248
|
table: Union[LocalTable, DistributedDataset],
|
243
249
|
s3_url_prefix: str,
|
@@ -346,6 +352,7 @@ def upload_table(
|
|
346
352
|
return manifest_entries
|
347
353
|
|
348
354
|
|
355
|
+
@metrics(prefix=DOWNLOAD_MANIFEST_ENTRY_METRIC_PREFIX)
|
349
356
|
def download_manifest_entry(
|
350
357
|
manifest_entry: ManifestEntry,
|
351
358
|
token_holder: Optional[Dict[str, Any]] = None,
|
@@ -40,3 +40,31 @@ DROP_DUPLICATES = True
|
|
40
40
|
# This is the observed upper bound inflation for parquet
|
41
41
|
# size in metadata to pyarrow table size.
|
42
42
|
PARQUET_TO_PYARROW_INFLATION = 4
|
43
|
+
|
44
|
+
# Metric Names
|
45
|
+
# Time taken for a hash bucket task
|
46
|
+
HASH_BUCKET_TIME_IN_SECONDS = "hash_bucket_time"
|
47
|
+
|
48
|
+
# Hash bucket success count
|
49
|
+
HASH_BUCKET_SUCCESS_COUNT = "hash_bucket_success_count"
|
50
|
+
|
51
|
+
# Hash bucket failure count
|
52
|
+
HASH_BUCKET_FAILURE_COUNT = "hash_bucket_failure_count"
|
53
|
+
|
54
|
+
# Time taken for a merge task
|
55
|
+
MERGE_TIME_IN_SECONDS = "merge_time"
|
56
|
+
|
57
|
+
# Merge success count
|
58
|
+
MERGE_SUCCESS_COUNT = "merge_success_count"
|
59
|
+
|
60
|
+
# Merge failure count
|
61
|
+
MERGE_FAILURE_COUNT = "merge_failure_count"
|
62
|
+
|
63
|
+
# Metric prefix for discover deltas
|
64
|
+
DISCOVER_DELTAS_METRIC_PREFIX = "discover_deltas"
|
65
|
+
|
66
|
+
# Metric prefix for prepare deletes
|
67
|
+
PREPARE_DELETES_METRIC_PREFIX = "prepare_deletes"
|
68
|
+
|
69
|
+
# Metric prefix for materialize
|
70
|
+
MATERIALIZE_METRIC_PREFIX = "delta_materialize"
|
@@ -23,6 +23,8 @@ from deltacat.storage import (
|
|
23
23
|
Delta,
|
24
24
|
)
|
25
25
|
from deltacat import logs
|
26
|
+
from deltacat.utils.metrics import metrics
|
27
|
+
from deltacat.compute.compactor_v2.constants import PREPARE_DELETES_METRIC_PREFIX
|
26
28
|
|
27
29
|
|
28
30
|
logger = logs.configure_deltacat_logger(logging.getLogger(__name__))
|
@@ -115,6 +117,7 @@ def _get_delete_file_envelopes(
|
|
115
117
|
return delete_file_envelopes
|
116
118
|
|
117
119
|
|
120
|
+
@metrics(prefix=PREPARE_DELETES_METRIC_PREFIX)
|
118
121
|
def prepare_deletes(
|
119
122
|
params: CompactPartitionParams,
|
120
123
|
input_deltas: List[Delta],
|
@@ -25,12 +25,17 @@ from deltacat.utils.ray_utils.runtime import (
|
|
25
25
|
)
|
26
26
|
from deltacat.utils.common import ReadKwargsProvider
|
27
27
|
from deltacat.utils.performance import timed_invocation
|
28
|
-
from deltacat.utils.metrics import emit_timer_metrics
|
28
|
+
from deltacat.utils.metrics import emit_timer_metrics, failure_metric, success_metric
|
29
29
|
from deltacat.utils.resources import (
|
30
30
|
get_current_process_peak_memory_usage_in_bytes,
|
31
31
|
ProcessUtilizationOverTimeRange,
|
32
32
|
)
|
33
33
|
from deltacat.constants import BYTES_PER_GIBIBYTE
|
34
|
+
from deltacat.compute.compactor_v2.constants import (
|
35
|
+
HASH_BUCKET_TIME_IN_SECONDS,
|
36
|
+
HASH_BUCKET_FAILURE_COUNT,
|
37
|
+
HASH_BUCKET_SUCCESS_COUNT,
|
38
|
+
)
|
34
39
|
|
35
40
|
if importlib.util.find_spec("memray"):
|
36
41
|
import memray
|
@@ -91,6 +96,8 @@ def _group_file_records_by_pk_hash_bucket(
|
|
91
96
|
return hb_to_delta_file_envelopes, total_record_count, total_size_bytes
|
92
97
|
|
93
98
|
|
99
|
+
@success_metric(name=HASH_BUCKET_SUCCESS_COUNT)
|
100
|
+
@failure_metric(name=HASH_BUCKET_FAILURE_COUNT)
|
94
101
|
def _timed_hash_bucket(input: HashBucketInput):
|
95
102
|
task_id = get_current_ray_task_id()
|
96
103
|
worker_id = get_current_ray_worker_id()
|
@@ -153,7 +160,7 @@ def hash_bucket(input: HashBucketInput) -> HashBucketResult:
|
|
153
160
|
if input.metrics_config:
|
154
161
|
emit_result, latency = timed_invocation(
|
155
162
|
func=emit_timer_metrics,
|
156
|
-
metrics_name=
|
163
|
+
metrics_name=HASH_BUCKET_TIME_IN_SECONDS,
|
157
164
|
value=duration,
|
158
165
|
metrics_config=input.metrics_config,
|
159
166
|
)
|
@@ -24,7 +24,7 @@ from deltacat.utils.ray_utils.runtime import (
|
|
24
24
|
)
|
25
25
|
from deltacat.compute.compactor.utils import system_columns as sc
|
26
26
|
from deltacat.utils.performance import timed_invocation
|
27
|
-
from deltacat.utils.metrics import emit_timer_metrics
|
27
|
+
from deltacat.utils.metrics import emit_timer_metrics, failure_metric, success_metric
|
28
28
|
from deltacat.utils.resources import (
|
29
29
|
get_current_process_peak_memory_usage_in_bytes,
|
30
30
|
ProcessUtilizationOverTimeRange,
|
@@ -42,6 +42,11 @@ from deltacat.storage import (
|
|
42
42
|
)
|
43
43
|
from deltacat.compute.compactor_v2.utils.dedupe import drop_duplicates
|
44
44
|
from deltacat.constants import BYTES_PER_GIBIBYTE
|
45
|
+
from deltacat.compute.compactor_v2.constants import (
|
46
|
+
MERGE_TIME_IN_SECONDS,
|
47
|
+
MERGE_SUCCESS_COUNT,
|
48
|
+
MERGE_FAILURE_COUNT,
|
49
|
+
)
|
45
50
|
|
46
51
|
|
47
52
|
if importlib.util.find_spec("memray"):
|
@@ -479,6 +484,8 @@ def _copy_manifests_from_hash_bucketing(
|
|
479
484
|
return materialized_results
|
480
485
|
|
481
486
|
|
487
|
+
@success_metric(name=MERGE_SUCCESS_COUNT)
|
488
|
+
@failure_metric(name=MERGE_FAILURE_COUNT)
|
482
489
|
def _timed_merge(input: MergeInput) -> MergeResult:
|
483
490
|
task_id = get_current_ray_task_id()
|
484
491
|
worker_id = get_current_ray_worker_id()
|
@@ -578,7 +585,7 @@ def merge(input: MergeInput) -> MergeResult:
|
|
578
585
|
if input.metrics_config:
|
579
586
|
emit_result, latency = timed_invocation(
|
580
587
|
func=emit_timer_metrics,
|
581
|
-
metrics_name=
|
588
|
+
metrics_name=MERGE_TIME_IN_SECONDS,
|
582
589
|
value=duration,
|
583
590
|
metrics_config=input.metrics_config,
|
584
591
|
)
|
@@ -23,10 +23,13 @@ from deltacat.compute.compactor_v2.utils.task_options import (
|
|
23
23
|
from deltacat.compute.compactor_v2.utils.content_type_params import (
|
24
24
|
append_content_type_params,
|
25
25
|
)
|
26
|
+
from deltacat.utils.metrics import metrics
|
27
|
+
from deltacat.compute.compactor_v2.constants import DISCOVER_DELTAS_METRIC_PREFIX
|
26
28
|
|
27
29
|
logger = logs.configure_deltacat_logger(logging.getLogger(__name__))
|
28
30
|
|
29
31
|
|
32
|
+
@metrics(prefix=DISCOVER_DELTAS_METRIC_PREFIX)
|
30
33
|
def discover_deltas(
|
31
34
|
source_partition_locator: PartitionLocator,
|
32
35
|
last_stream_position_to_compact: int,
|
@@ -31,11 +31,14 @@ from deltacat.compute.compactor_v2.deletes.delete_strategy import (
|
|
31
31
|
from deltacat.compute.compactor_v2.deletes.delete_file_envelope import (
|
32
32
|
DeleteFileEnvelope,
|
33
33
|
)
|
34
|
+
from deltacat.utils.metrics import metrics
|
35
|
+
from deltacat.compute.compactor_v2.constants import MATERIALIZE_METRIC_PREFIX
|
34
36
|
|
35
37
|
|
36
38
|
logger = logs.configure_deltacat_logger(logging.getLogger(__name__))
|
37
39
|
|
38
40
|
|
41
|
+
@metrics(prefix=MATERIALIZE_METRIC_PREFIX)
|
39
42
|
def materialize(
|
40
43
|
input: MergeInput,
|
41
44
|
task_index: int,
|
@@ -0,0 +1,502 @@
|
|
1
|
+
import unittest
|
2
|
+
from unittest.mock import MagicMock, ANY, call
|
3
|
+
from deltacat.utils.metrics import (
|
4
|
+
metrics,
|
5
|
+
success_metric,
|
6
|
+
failure_metric,
|
7
|
+
latency_metric,
|
8
|
+
MetricsConfigCache,
|
9
|
+
MetricsConfig,
|
10
|
+
MetricsTarget,
|
11
|
+
METRICS_TARGET_TO_EMITTER_DICT,
|
12
|
+
)
|
13
|
+
from deltacat.utils.performance import timed_invocation
|
14
|
+
|
15
|
+
|
16
|
+
def method_not_annotated(mock_func):
|
17
|
+
mock_func("called")
|
18
|
+
|
19
|
+
|
20
|
+
@metrics
|
21
|
+
def metrics_annotated_method(mock_func):
|
22
|
+
mock_func("called")
|
23
|
+
|
24
|
+
|
25
|
+
@metrics
|
26
|
+
def metrics_annotated_method_error(mock_func):
|
27
|
+
raise ValueError()
|
28
|
+
|
29
|
+
|
30
|
+
@metrics(prefix="test_prefix")
|
31
|
+
def metrics_with_prefix_annotated_method(mock_func):
|
32
|
+
mock_func("called")
|
33
|
+
|
34
|
+
|
35
|
+
@metrics(prefix="test_prefix")
|
36
|
+
def metrics_with_prefix_annotated_method_error(mock_func):
|
37
|
+
raise ValueError()
|
38
|
+
|
39
|
+
|
40
|
+
class TestMetricsAnnotation(unittest.TestCase):
|
41
|
+
def test_metrics_annotation_sanity(self):
|
42
|
+
mock, mock_target = MagicMock(), MagicMock()
|
43
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
44
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
45
|
+
MetricsConfigCache.metrics_config = config
|
46
|
+
|
47
|
+
# action
|
48
|
+
metrics_annotated_method(mock)
|
49
|
+
|
50
|
+
mock.assert_called_once()
|
51
|
+
self.assertEqual(2, mock_target.call_count)
|
52
|
+
mock_target.assert_has_calls(
|
53
|
+
[
|
54
|
+
call(
|
55
|
+
metrics_name="metrics_annotated_method_time",
|
56
|
+
metrics_config=ANY,
|
57
|
+
value=ANY,
|
58
|
+
),
|
59
|
+
call(
|
60
|
+
metrics_name="metrics_annotated_method_success_count",
|
61
|
+
metrics_config=ANY,
|
62
|
+
value=ANY,
|
63
|
+
),
|
64
|
+
],
|
65
|
+
any_order=True,
|
66
|
+
)
|
67
|
+
|
68
|
+
def test_metrics_annotation_when_error(self):
|
69
|
+
mock, mock_target = MagicMock(), MagicMock()
|
70
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
71
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
72
|
+
MetricsConfigCache.metrics_config = config
|
73
|
+
|
74
|
+
# action
|
75
|
+
self.assertRaises(ValueError, lambda: metrics_annotated_method_error(mock))
|
76
|
+
|
77
|
+
mock.assert_not_called()
|
78
|
+
self.assertEqual(3, mock_target.call_count)
|
79
|
+
mock_target.assert_has_calls(
|
80
|
+
[
|
81
|
+
call(
|
82
|
+
metrics_name="metrics_annotated_method_error_time",
|
83
|
+
metrics_config=ANY,
|
84
|
+
value=ANY,
|
85
|
+
),
|
86
|
+
call(
|
87
|
+
metrics_name="metrics_annotated_method_error_failure_count",
|
88
|
+
metrics_config=ANY,
|
89
|
+
value=ANY,
|
90
|
+
),
|
91
|
+
call(
|
92
|
+
metrics_name="metrics_annotated_method_error_failure_count.ValueError",
|
93
|
+
metrics_config=ANY,
|
94
|
+
value=ANY,
|
95
|
+
),
|
96
|
+
],
|
97
|
+
any_order=True,
|
98
|
+
)
|
99
|
+
|
100
|
+
def test_metrics_with_prefix_annotation_sanity(self):
|
101
|
+
mock, mock_target = MagicMock(), MagicMock()
|
102
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
103
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
104
|
+
MetricsConfigCache.metrics_config = config
|
105
|
+
|
106
|
+
# action
|
107
|
+
metrics_with_prefix_annotated_method(mock)
|
108
|
+
|
109
|
+
mock.assert_called_once()
|
110
|
+
self.assertEqual(2, mock_target.call_count)
|
111
|
+
mock_target.assert_has_calls(
|
112
|
+
[
|
113
|
+
call(metrics_name="test_prefix_time", metrics_config=ANY, value=ANY),
|
114
|
+
call(
|
115
|
+
metrics_name="test_prefix_success_count",
|
116
|
+
metrics_config=ANY,
|
117
|
+
value=ANY,
|
118
|
+
),
|
119
|
+
],
|
120
|
+
any_order=True,
|
121
|
+
)
|
122
|
+
|
123
|
+
def test_metrics_annotation_performance(self):
|
124
|
+
mock, mock_target = MagicMock(), MagicMock()
|
125
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
126
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
127
|
+
MetricsConfigCache.metrics_config = config
|
128
|
+
|
129
|
+
# action with annotation
|
130
|
+
_, actual_latency = timed_invocation(metrics_annotated_method, mock)
|
131
|
+
_, second_call_latency = timed_invocation(metrics_annotated_method, mock)
|
132
|
+
|
133
|
+
mock.assert_called()
|
134
|
+
self.assertEqual(4, mock_target.call_count)
|
135
|
+
self.assertLess(
|
136
|
+
second_call_latency,
|
137
|
+
actual_latency,
|
138
|
+
"Second call to actor must be much faster",
|
139
|
+
)
|
140
|
+
|
141
|
+
def test_metrics_with_prefix_annotation_when_error(self):
|
142
|
+
mock, mock_target = MagicMock(), MagicMock()
|
143
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
144
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
145
|
+
MetricsConfigCache.metrics_config = config
|
146
|
+
|
147
|
+
# action
|
148
|
+
self.assertRaises(
|
149
|
+
ValueError, lambda: metrics_with_prefix_annotated_method_error(mock)
|
150
|
+
)
|
151
|
+
|
152
|
+
mock.assert_not_called()
|
153
|
+
self.assertEqual(3, mock_target.call_count)
|
154
|
+
mock_target.assert_has_calls(
|
155
|
+
[
|
156
|
+
call(metrics_name="test_prefix_time", metrics_config=ANY, value=ANY),
|
157
|
+
call(
|
158
|
+
metrics_name="test_prefix_failure_count",
|
159
|
+
metrics_config=ANY,
|
160
|
+
value=ANY,
|
161
|
+
),
|
162
|
+
call(
|
163
|
+
metrics_name="test_prefix_failure_count.ValueError",
|
164
|
+
metrics_config=ANY,
|
165
|
+
value=ANY,
|
166
|
+
),
|
167
|
+
],
|
168
|
+
any_order=True,
|
169
|
+
)
|
170
|
+
|
171
|
+
def test_metrics_with_prefix_annotation_without_config(self):
|
172
|
+
mock, mock_target = MagicMock(), MagicMock()
|
173
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
174
|
+
MetricsConfigCache.metrics_config = None
|
175
|
+
|
176
|
+
# action
|
177
|
+
self.assertRaises(
|
178
|
+
ValueError, lambda: metrics_with_prefix_annotated_method_error(mock)
|
179
|
+
)
|
180
|
+
|
181
|
+
mock.assert_not_called()
|
182
|
+
mock_target.assert_not_called()
|
183
|
+
|
184
|
+
|
185
|
+
@latency_metric
|
186
|
+
def latency_metric_annotated_method(mock_func):
|
187
|
+
mock_func("called")
|
188
|
+
|
189
|
+
|
190
|
+
@latency_metric
|
191
|
+
def latency_metric_annotated_method_error(mock_func):
|
192
|
+
raise ValueError()
|
193
|
+
|
194
|
+
|
195
|
+
@latency_metric(name="test")
|
196
|
+
def latency_metric_with_name_annotated_method(mock_func):
|
197
|
+
mock_func("called")
|
198
|
+
|
199
|
+
|
200
|
+
@latency_metric(name="test")
|
201
|
+
def latency_metric_with_name_annotated_method_error(mock_func):
|
202
|
+
raise ValueError()
|
203
|
+
|
204
|
+
|
205
|
+
class TestLatencyMetricAnnotation(unittest.TestCase):
|
206
|
+
def test_annotation_sanity(self):
|
207
|
+
mock, mock_target = MagicMock(), MagicMock()
|
208
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
209
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
210
|
+
MetricsConfigCache.metrics_config = config
|
211
|
+
|
212
|
+
# action
|
213
|
+
latency_metric_annotated_method(mock)
|
214
|
+
|
215
|
+
mock.assert_called_once()
|
216
|
+
self.assertEqual(1, mock_target.call_count)
|
217
|
+
mock_target.assert_has_calls(
|
218
|
+
[
|
219
|
+
call(
|
220
|
+
metrics_name="latency_metric_annotated_method_time",
|
221
|
+
metrics_config=ANY,
|
222
|
+
value=ANY,
|
223
|
+
)
|
224
|
+
],
|
225
|
+
any_order=True,
|
226
|
+
)
|
227
|
+
|
228
|
+
def test_annotation_when_error(self):
|
229
|
+
mock, mock_target = MagicMock(), MagicMock()
|
230
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
231
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
232
|
+
MetricsConfigCache.metrics_config = config
|
233
|
+
|
234
|
+
# action
|
235
|
+
self.assertRaises(
|
236
|
+
ValueError, lambda: latency_metric_annotated_method_error(mock)
|
237
|
+
)
|
238
|
+
|
239
|
+
mock.assert_not_called()
|
240
|
+
self.assertEqual(1, mock_target.call_count)
|
241
|
+
mock_target.assert_has_calls(
|
242
|
+
[
|
243
|
+
call(
|
244
|
+
metrics_name="latency_metric_annotated_method_error_time",
|
245
|
+
metrics_config=ANY,
|
246
|
+
value=ANY,
|
247
|
+
)
|
248
|
+
],
|
249
|
+
any_order=True,
|
250
|
+
)
|
251
|
+
|
252
|
+
def test_annotation_with_args_sanity(self):
|
253
|
+
mock, mock_target = MagicMock(), MagicMock()
|
254
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
255
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
256
|
+
MetricsConfigCache.metrics_config = config
|
257
|
+
|
258
|
+
# action
|
259
|
+
latency_metric_with_name_annotated_method(mock)
|
260
|
+
|
261
|
+
mock.assert_called_once()
|
262
|
+
self.assertEqual(1, mock_target.call_count)
|
263
|
+
mock_target.assert_has_calls(
|
264
|
+
[call(metrics_name="test", metrics_config=ANY, value=ANY)], any_order=True
|
265
|
+
)
|
266
|
+
|
267
|
+
def test_annotation_with_args_when_error(self):
|
268
|
+
mock, mock_target = MagicMock(), MagicMock()
|
269
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
270
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
271
|
+
MetricsConfigCache.metrics_config = config
|
272
|
+
|
273
|
+
# action
|
274
|
+
self.assertRaises(
|
275
|
+
ValueError, lambda: latency_metric_with_name_annotated_method_error(mock)
|
276
|
+
)
|
277
|
+
|
278
|
+
mock.assert_not_called()
|
279
|
+
self.assertEqual(1, mock_target.call_count)
|
280
|
+
mock_target.assert_has_calls(
|
281
|
+
[call(metrics_name="test", metrics_config=ANY, value=ANY)], any_order=True
|
282
|
+
)
|
283
|
+
|
284
|
+
def test_annotation_without_config(self):
|
285
|
+
mock, mock_target = MagicMock(), MagicMock()
|
286
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
287
|
+
MetricsConfigCache.metrics_config = None
|
288
|
+
|
289
|
+
# action
|
290
|
+
self.assertRaises(
|
291
|
+
ValueError, lambda: latency_metric_with_name_annotated_method_error(mock)
|
292
|
+
)
|
293
|
+
|
294
|
+
mock.assert_not_called()
|
295
|
+
mock_target.assert_not_called()
|
296
|
+
|
297
|
+
|
298
|
+
@success_metric
|
299
|
+
def success_metric_annotated_method(mock_func):
|
300
|
+
mock_func("called")
|
301
|
+
|
302
|
+
|
303
|
+
@success_metric
|
304
|
+
def success_metric_annotated_method_error(mock_func):
|
305
|
+
raise ValueError()
|
306
|
+
|
307
|
+
|
308
|
+
@success_metric(name="test")
|
309
|
+
def success_metric_with_name_annotated_method(mock_func):
|
310
|
+
mock_func("called")
|
311
|
+
|
312
|
+
|
313
|
+
@success_metric(name="test")
|
314
|
+
def success_metric_with_name_annotated_method_error(mock_func):
|
315
|
+
raise ValueError()
|
316
|
+
|
317
|
+
|
318
|
+
class TestSuccessMetricAnnotation(unittest.TestCase):
|
319
|
+
def test_annotation_sanity(self):
|
320
|
+
mock, mock_target = MagicMock(), MagicMock()
|
321
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
322
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
323
|
+
MetricsConfigCache.metrics_config = config
|
324
|
+
|
325
|
+
# action
|
326
|
+
success_metric_annotated_method(mock)
|
327
|
+
|
328
|
+
mock.assert_called_once()
|
329
|
+
self.assertEqual(1, mock_target.call_count)
|
330
|
+
mock_target.assert_has_calls(
|
331
|
+
[
|
332
|
+
call(
|
333
|
+
metrics_name="success_metric_annotated_method_success_count",
|
334
|
+
metrics_config=ANY,
|
335
|
+
value=ANY,
|
336
|
+
)
|
337
|
+
],
|
338
|
+
any_order=True,
|
339
|
+
)
|
340
|
+
|
341
|
+
def test_annotation_when_error(self):
|
342
|
+
mock, mock_target = MagicMock(), MagicMock()
|
343
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
344
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
345
|
+
MetricsConfigCache.metrics_config = config
|
346
|
+
|
347
|
+
# action
|
348
|
+
self.assertRaises(
|
349
|
+
ValueError, lambda: success_metric_annotated_method_error(mock)
|
350
|
+
)
|
351
|
+
|
352
|
+
mock.assert_not_called()
|
353
|
+
mock_target.assert_not_called()
|
354
|
+
|
355
|
+
def test_annotation_with_args_sanity(self):
|
356
|
+
mock, mock_target = MagicMock(), MagicMock()
|
357
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
358
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
359
|
+
MetricsConfigCache.metrics_config = config
|
360
|
+
|
361
|
+
# action
|
362
|
+
success_metric_with_name_annotated_method(mock)
|
363
|
+
|
364
|
+
mock.assert_called_once()
|
365
|
+
self.assertEqual(1, mock_target.call_count)
|
366
|
+
mock_target.assert_has_calls(
|
367
|
+
[call(metrics_name="test", metrics_config=ANY, value=ANY)], any_order=True
|
368
|
+
)
|
369
|
+
|
370
|
+
def test_annotation_with_args_when_error(self):
|
371
|
+
mock, mock_target = MagicMock(), MagicMock()
|
372
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
373
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
374
|
+
MetricsConfigCache.metrics_config = config
|
375
|
+
|
376
|
+
# action
|
377
|
+
self.assertRaises(
|
378
|
+
ValueError, lambda: success_metric_with_name_annotated_method_error(mock)
|
379
|
+
)
|
380
|
+
|
381
|
+
mock.assert_not_called()
|
382
|
+
mock_target.assert_not_called()
|
383
|
+
|
384
|
+
def test_annotation_without_config(self):
|
385
|
+
mock, mock_target = MagicMock(), MagicMock()
|
386
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
387
|
+
MetricsConfigCache.metrics_config = None
|
388
|
+
|
389
|
+
# action
|
390
|
+
success_metric_annotated_method(mock)
|
391
|
+
|
392
|
+
mock.assert_called_once()
|
393
|
+
mock_target.assert_not_called()
|
394
|
+
|
395
|
+
|
396
|
+
@failure_metric
|
397
|
+
def failure_metric_annotated_method(mock_func):
|
398
|
+
mock_func("called")
|
399
|
+
|
400
|
+
|
401
|
+
@failure_metric
|
402
|
+
def failure_metric_annotated_method_error(mock_func):
|
403
|
+
raise ValueError()
|
404
|
+
|
405
|
+
|
406
|
+
@failure_metric(name="test")
|
407
|
+
def failure_metric_with_name_annotated_method(mock_func):
|
408
|
+
mock_func("called")
|
409
|
+
|
410
|
+
|
411
|
+
@failure_metric(name="test")
|
412
|
+
def failure_metric_with_name_annotated_method_error(mock_func):
|
413
|
+
raise ValueError()
|
414
|
+
|
415
|
+
|
416
|
+
class TestFailureMetricAnnotation(unittest.TestCase):
|
417
|
+
def test_annotation_sanity(self):
|
418
|
+
mock, mock_target = MagicMock(), MagicMock()
|
419
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
420
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
421
|
+
MetricsConfigCache.metrics_config = config
|
422
|
+
|
423
|
+
# action
|
424
|
+
failure_metric_annotated_method(mock)
|
425
|
+
|
426
|
+
mock.assert_called_once()
|
427
|
+
mock_target.assert_not_called()
|
428
|
+
|
429
|
+
def test_annotation_when_error(self):
|
430
|
+
mock, mock_target = MagicMock(), MagicMock()
|
431
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
432
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
433
|
+
MetricsConfigCache.metrics_config = config
|
434
|
+
|
435
|
+
# action
|
436
|
+
self.assertRaises(
|
437
|
+
ValueError, lambda: failure_metric_annotated_method_error(mock)
|
438
|
+
)
|
439
|
+
|
440
|
+
mock.assert_not_called()
|
441
|
+
self.assertEqual(2, mock_target.call_count)
|
442
|
+
mock_target.assert_has_calls(
|
443
|
+
[
|
444
|
+
call(
|
445
|
+
metrics_name="failure_metric_annotated_method_error_failure_count.ValueError",
|
446
|
+
metrics_config=ANY,
|
447
|
+
value=ANY,
|
448
|
+
),
|
449
|
+
call(
|
450
|
+
metrics_name="failure_metric_annotated_method_error_failure_count",
|
451
|
+
metrics_config=ANY,
|
452
|
+
value=ANY,
|
453
|
+
),
|
454
|
+
],
|
455
|
+
any_order=True,
|
456
|
+
)
|
457
|
+
|
458
|
+
def test_annotation_with_args_sanity(self):
|
459
|
+
mock, mock_target = MagicMock(), MagicMock()
|
460
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
461
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
462
|
+
MetricsConfigCache.metrics_config = config
|
463
|
+
|
464
|
+
# action
|
465
|
+
failure_metric_with_name_annotated_method(mock)
|
466
|
+
|
467
|
+
mock.assert_called_once()
|
468
|
+
mock_target.assert_not_called()
|
469
|
+
|
470
|
+
def test_annotation_with_args_when_error(self):
|
471
|
+
mock, mock_target = MagicMock(), MagicMock()
|
472
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
473
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
474
|
+
MetricsConfigCache.metrics_config = config
|
475
|
+
|
476
|
+
# action
|
477
|
+
self.assertRaises(
|
478
|
+
ValueError, lambda: failure_metric_with_name_annotated_method_error(mock)
|
479
|
+
)
|
480
|
+
|
481
|
+
mock.assert_not_called()
|
482
|
+
self.assertEqual(2, mock_target.call_count)
|
483
|
+
mock_target.assert_has_calls(
|
484
|
+
[
|
485
|
+
call(metrics_name="test.ValueError", metrics_config=ANY, value=ANY),
|
486
|
+
call(metrics_name="test", metrics_config=ANY, value=ANY),
|
487
|
+
],
|
488
|
+
any_order=True,
|
489
|
+
)
|
490
|
+
|
491
|
+
def test_annotation_without_config(self):
|
492
|
+
mock, mock_target = MagicMock(), MagicMock()
|
493
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
494
|
+
MetricsConfigCache.metrics_config = None
|
495
|
+
|
496
|
+
# action
|
497
|
+
self.assertRaises(
|
498
|
+
ValueError, lambda: failure_metric_with_name_annotated_method_error(mock)
|
499
|
+
)
|
500
|
+
|
501
|
+
mock.assert_not_called()
|
502
|
+
mock_target.assert_not_called()
|
deltacat/utils/metrics.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
+
from typing import Optional
|
2
3
|
|
3
4
|
import logging
|
5
|
+
import time
|
6
|
+
import functools
|
4
7
|
|
5
8
|
from aws_embedded_metrics import metric_scope
|
6
9
|
from aws_embedded_metrics.config import get_config
|
@@ -23,6 +26,7 @@ DEFAULT_DELTACAT_LOG_STREAM_CALLABLE = get_node_ip_address
|
|
23
26
|
class MetricsTarget(str, Enum):
|
24
27
|
CLOUDWATCH = "cloudwatch"
|
25
28
|
CLOUDWATCH_EMF = "cloudwatch_emf"
|
29
|
+
NOOP = "noop"
|
26
30
|
|
27
31
|
|
28
32
|
@dataclass
|
@@ -42,27 +46,16 @@ class MetricsConfig:
|
|
42
46
|
self.metrics_kwargs = metrics_kwargs
|
43
47
|
|
44
48
|
|
45
|
-
class MetricsType(str, Enum):
|
46
|
-
TIMER = "timer"
|
47
|
-
|
48
|
-
|
49
|
-
def _build_metrics_name(metrics_type: Enum, metrics_name: str) -> str:
|
50
|
-
metrics_name_with_type = f"{metrics_name}_{metrics_type}"
|
51
|
-
return metrics_name_with_type
|
52
|
-
|
53
|
-
|
54
49
|
def _build_cloudwatch_metrics(
|
55
50
|
metrics_name: str,
|
56
|
-
metrics_type: Enum,
|
57
51
|
value: str,
|
58
52
|
metrics_dimensions: List[Dict[str, str]],
|
59
53
|
timestamp: datetime,
|
60
54
|
**kwargs,
|
61
55
|
) -> Dict[str, Any]:
|
62
|
-
metrics_name_with_type = _build_metrics_name(metrics_type, metrics_name)
|
63
56
|
return [
|
64
57
|
{
|
65
|
-
"MetricName":
|
58
|
+
"MetricName": metrics_name,
|
66
59
|
"Dimensions": metrics_dimensions,
|
67
60
|
"Timestamp": timestamp,
|
68
61
|
"Value": value,
|
@@ -73,7 +66,6 @@ def _build_cloudwatch_metrics(
|
|
73
66
|
|
74
67
|
def _emit_metrics(
|
75
68
|
metrics_name: str,
|
76
|
-
metrics_type: Enum,
|
77
69
|
metrics_config: MetricsConfig,
|
78
70
|
value: str,
|
79
71
|
**kwargs,
|
@@ -85,7 +77,6 @@ def _emit_metrics(
|
|
85
77
|
if metrics_target in METRICS_TARGET_TO_EMITTER_DICT:
|
86
78
|
METRICS_TARGET_TO_EMITTER_DICT.get(metrics_target)(
|
87
79
|
metrics_name=metrics_name,
|
88
|
-
metrics_type=metrics_type,
|
89
80
|
metrics_config=metrics_config,
|
90
81
|
value=value,
|
91
82
|
**kwargs,
|
@@ -94,9 +85,15 @@ def _emit_metrics(
|
|
94
85
|
logger.warning(f"{metrics_target} is not a supported metrics target type.")
|
95
86
|
|
96
87
|
|
88
|
+
def _emit_noop_metrics(
|
89
|
+
*args,
|
90
|
+
**kwargs,
|
91
|
+
) -> None:
|
92
|
+
pass
|
93
|
+
|
94
|
+
|
97
95
|
def _emit_cloudwatch_metrics(
|
98
96
|
metrics_name: str,
|
99
|
-
metrics_type: Enum,
|
100
97
|
metrics_config: MetricsConfig,
|
101
98
|
value: str,
|
102
99
|
**kwargs,
|
@@ -111,7 +108,6 @@ def _emit_cloudwatch_metrics(
|
|
111
108
|
|
112
109
|
metrics_data = _build_cloudwatch_metrics(
|
113
110
|
metrics_name,
|
114
|
-
metrics_type,
|
115
111
|
value,
|
116
112
|
metrics_dimensions,
|
117
113
|
current_time,
|
@@ -128,14 +124,13 @@ def _emit_cloudwatch_metrics(
|
|
128
124
|
except Exception as e:
|
129
125
|
logger.warning(
|
130
126
|
f"Failed to publish Cloudwatch metrics with name: {metrics_name}, "
|
131
|
-
f"
|
127
|
+
f"with exception: {e}, response: {response}"
|
132
128
|
)
|
133
129
|
|
134
130
|
|
135
131
|
@metric_scope
|
136
132
|
def _emit_cloudwatch_emf_metrics(
|
137
133
|
metrics_name: str,
|
138
|
-
metrics_type: Enum,
|
139
134
|
metrics_config: MetricsConfig,
|
140
135
|
value: str,
|
141
136
|
metrics: MetricsLogger,
|
@@ -155,7 +150,6 @@ def _emit_cloudwatch_emf_metrics(
|
|
155
150
|
dimensions = dict(
|
156
151
|
[(dim["Name"], dim["Value"]) for dim in metrics_config.metrics_dimensions]
|
157
152
|
)
|
158
|
-
metrics_name_with_type = _build_metrics_name(metrics_type, metrics_name)
|
159
153
|
try:
|
160
154
|
metrics.set_timestamp(current_time)
|
161
155
|
metrics.set_dimensions(dimensions)
|
@@ -177,25 +171,183 @@ def _emit_cloudwatch_emf_metrics(
|
|
177
171
|
else DEFAULT_DELTACAT_LOG_STREAM_CALLABLE()
|
178
172
|
)
|
179
173
|
|
180
|
-
metrics.put_metric(
|
174
|
+
metrics.put_metric(metrics_name, value)
|
181
175
|
except Exception as e:
|
182
176
|
logger.warning(
|
183
|
-
f"Failed to publish Cloudwatch EMF metrics with name: {
|
184
|
-
f"type: {metrics_type}, with exception: {e}"
|
177
|
+
f"Failed to publish Cloudwatch EMF metrics with name: {metrics_name}", e
|
185
178
|
)
|
186
179
|
|
187
180
|
|
188
181
|
METRICS_TARGET_TO_EMITTER_DICT: Dict[str, Callable] = {
|
189
182
|
MetricsTarget.CLOUDWATCH: _emit_cloudwatch_metrics,
|
190
183
|
MetricsTarget.CLOUDWATCH_EMF: _emit_cloudwatch_emf_metrics,
|
184
|
+
MetricsTarget.NOOP: _emit_noop_metrics,
|
191
185
|
}
|
192
186
|
|
193
187
|
|
188
|
+
class MetricsConfigCache:
|
189
|
+
metrics_config: MetricsConfig = None
|
190
|
+
|
191
|
+
|
192
|
+
def _emit_ignore_exceptions(name: Optional[str], value: Any):
|
193
|
+
try:
|
194
|
+
config = MetricsConfigCache.metrics_config
|
195
|
+
if config:
|
196
|
+
_emit_metrics(metrics_name=name, value=value, metrics_config=config)
|
197
|
+
except BaseException as ex:
|
198
|
+
logger.warning("Emitting metrics failed", ex)
|
199
|
+
|
200
|
+
|
194
201
|
def emit_timer_metrics(metrics_name, value, metrics_config, **kwargs):
|
195
202
|
_emit_metrics(
|
196
203
|
metrics_name=metrics_name,
|
197
|
-
metrics_type=MetricsType.TIMER,
|
198
204
|
metrics_config=metrics_config,
|
199
205
|
value=value,
|
200
206
|
**kwargs,
|
201
207
|
)
|
208
|
+
|
209
|
+
|
210
|
+
def latency_metric(original_func=None, name: Optional[str] = None):
|
211
|
+
"""
|
212
|
+
A decorator that emits latency metrics of a function
|
213
|
+
based on configured metrics config. Hence, make sure to set it in all worker processes:
|
214
|
+
|
215
|
+
def setup_cache():
|
216
|
+
from deltacat.utils.metrics import MetricsConfigCache
|
217
|
+
MetricsConfigCache.metrics_config = metrics_config
|
218
|
+
|
219
|
+
setup_cache();
|
220
|
+
ray.init(address="auto", runtime_env={"worker_process_setup_hook": setup_cache})
|
221
|
+
"""
|
222
|
+
|
223
|
+
def _decorate(func):
|
224
|
+
metrics_name = name or f"{func.__name__}_time"
|
225
|
+
|
226
|
+
@functools.wraps(func)
|
227
|
+
def wrapper(*args, **kwargs):
|
228
|
+
start = time.monotonic()
|
229
|
+
try:
|
230
|
+
return func(*args, **kwargs)
|
231
|
+
finally:
|
232
|
+
end = time.monotonic()
|
233
|
+
_emit_ignore_exceptions(name=metrics_name, value=end - start)
|
234
|
+
|
235
|
+
return wrapper
|
236
|
+
|
237
|
+
if original_func:
|
238
|
+
return _decorate(original_func)
|
239
|
+
|
240
|
+
return _decorate
|
241
|
+
|
242
|
+
|
243
|
+
def success_metric(original_func=None, name: Optional[str] = None):
|
244
|
+
"""
|
245
|
+
A decorator that emits success metrics of a function
|
246
|
+
based on configured metrics config. Hence, make sure to set it in all worker processes:
|
247
|
+
|
248
|
+
def setup_cache():
|
249
|
+
from deltacat.utils.metrics import MetricsConfigCache
|
250
|
+
MetricsConfigCache.metrics_config = metrics_config
|
251
|
+
|
252
|
+
setup_cache();
|
253
|
+
ray.init(address="auto", runtime_env={"worker_process_setup_hook": setup_cache})
|
254
|
+
"""
|
255
|
+
|
256
|
+
def _decorate(func):
|
257
|
+
metrics_name = name or f"{func.__name__}_success_count"
|
258
|
+
|
259
|
+
@functools.wraps(func)
|
260
|
+
def wrapper(*args, **kwargs):
|
261
|
+
result = func(*args, **kwargs)
|
262
|
+
_emit_ignore_exceptions(name=metrics_name, value=1)
|
263
|
+
return result
|
264
|
+
|
265
|
+
return wrapper
|
266
|
+
|
267
|
+
if original_func:
|
268
|
+
return _decorate(original_func)
|
269
|
+
|
270
|
+
return _decorate
|
271
|
+
|
272
|
+
|
273
|
+
def failure_metric(original_func=None, name: Optional[str] = None):
|
274
|
+
"""
|
275
|
+
A decorator that emits failure metrics of a function
|
276
|
+
based on configured metrics config. Hence, make sure to set it in all worker processes:
|
277
|
+
|
278
|
+
def setup_cache():
|
279
|
+
from deltacat.utils.metrics import MetricsConfigCache
|
280
|
+
MetricsConfigCache.metrics_config = metrics_config
|
281
|
+
|
282
|
+
setup_cache();
|
283
|
+
ray.init(address="auto", runtime_env={"worker_process_setup_hook": setup_cache})
|
284
|
+
"""
|
285
|
+
|
286
|
+
def _decorate(func):
|
287
|
+
metrics_name = name or f"{func.__name__}_failure_count"
|
288
|
+
|
289
|
+
@functools.wraps(func)
|
290
|
+
def wrapper(*args, **kwargs):
|
291
|
+
try:
|
292
|
+
return func(*args, **kwargs)
|
293
|
+
except BaseException as ex:
|
294
|
+
exception_name = type(ex).__name__
|
295
|
+
# We emit two metrics, one for exception class and
|
296
|
+
# another for just the specified metric name.
|
297
|
+
_emit_ignore_exceptions(
|
298
|
+
name=f"{metrics_name}.{exception_name}", value=1
|
299
|
+
)
|
300
|
+
_emit_ignore_exceptions(name=metrics_name, value=1)
|
301
|
+
raise ex
|
302
|
+
|
303
|
+
return wrapper
|
304
|
+
|
305
|
+
if original_func:
|
306
|
+
return _decorate(original_func)
|
307
|
+
|
308
|
+
return _decorate
|
309
|
+
|
310
|
+
|
311
|
+
def metrics(original_func=None, prefix: Optional[str] = None):
|
312
|
+
"""
|
313
|
+
A decorator that emits all metrics for a function. This decorator depends
|
314
|
+
on a metrics config. Hence, make sure to set it in all worker processes:
|
315
|
+
|
316
|
+
def setup_cache():
|
317
|
+
from deltacat.utils.metrics import MetricsConfigCache
|
318
|
+
MetricsConfigCache.metrics_config = metrics_config
|
319
|
+
|
320
|
+
setup_cache();
|
321
|
+
ray.init(address="auto", runtime_env={"worker_process_setup_hook": setup_cache})
|
322
|
+
"""
|
323
|
+
|
324
|
+
def _decorate(func):
|
325
|
+
name = prefix or func.__name__
|
326
|
+
failure_metrics_name = f"{name}_failure_count"
|
327
|
+
success_metrics_name = f"{name}_success_count"
|
328
|
+
latency_metrics_name = f"{name}_time"
|
329
|
+
|
330
|
+
@functools.wraps(func)
|
331
|
+
def wrapper(*args, **kwargs):
|
332
|
+
start = time.monotonic()
|
333
|
+
try:
|
334
|
+
result = func(*args, **kwargs)
|
335
|
+
_emit_ignore_exceptions(name=success_metrics_name, value=1)
|
336
|
+
return result
|
337
|
+
except BaseException as ex:
|
338
|
+
exception_name = type(ex).__name__
|
339
|
+
_emit_ignore_exceptions(
|
340
|
+
name=f"{failure_metrics_name}.{exception_name}", value=1
|
341
|
+
)
|
342
|
+
_emit_ignore_exceptions(name=failure_metrics_name, value=1)
|
343
|
+
raise ex
|
344
|
+
finally:
|
345
|
+
end = time.monotonic()
|
346
|
+
_emit_ignore_exceptions(name=latency_metrics_name, value=end - start)
|
347
|
+
|
348
|
+
return wrapper
|
349
|
+
|
350
|
+
if original_func:
|
351
|
+
return _decorate(original_func)
|
352
|
+
|
353
|
+
return _decorate
|
@@ -1,11 +1,11 @@
|
|
1
|
-
deltacat/__init__.py,sha256=
|
1
|
+
deltacat/__init__.py,sha256=eO5VKENAHF-beW-5X9eJHbQk9EvjaQ09PiyxhQNUuxo,1777
|
2
2
|
deltacat/constants.py,sha256=_6oRI-3yp5c8J1qKGQZrt89I9-ttT_gSSvVsJ0h8Duc,1939
|
3
3
|
deltacat/exceptions.py,sha256=xqZf8CwysNYP2d39pf27OnXGStPREgBgIM-e2Tts-TI,199
|
4
4
|
deltacat/logs.py,sha256=YT26oj-oKZReyBEhiBQU5jc246aSu_d4B2bZcUVBHmE,6550
|
5
5
|
deltacat/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
-
deltacat/aws/clients.py,sha256=
|
7
|
-
deltacat/aws/constants.py,sha256=
|
8
|
-
deltacat/aws/s3u.py,sha256=
|
6
|
+
deltacat/aws/clients.py,sha256=VgddlV3AEjlBGIFmhhHxokYzwJ-lXnmHAeprVyADduI,6948
|
7
|
+
deltacat/aws/constants.py,sha256=Ti8D92AFY3lY2eoOwYBSI1eqS8gMykX5DHf4w3xPybE,492
|
8
|
+
deltacat/aws/s3u.py,sha256=7-ZNz8fui_S5nprxtMO8PGa-G1YOXfidqcguzTTOvL8,24118
|
9
9
|
deltacat/aws/redshift/__init__.py,sha256=7SvjG-dqox8zZUhFicTsUvpG5vXYDl_QQ3ohlHOgTKc,342
|
10
10
|
deltacat/aws/redshift/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
deltacat/aws/redshift/model/manifest.py,sha256=ThgpdwzaWz493Zz9e8HSWwuxEheA1nDuypM3pe4vozk,12987
|
@@ -51,13 +51,13 @@ deltacat/compute/compactor/utils/sort_key.py,sha256=oK6otg-CSsma6zlGPaKg-KNEvcZR
|
|
51
51
|
deltacat/compute/compactor/utils/system_columns.py,sha256=CNIgAGos0xAGEpdaQIH7KfbSRrGZgjRbItXMararqXQ,9399
|
52
52
|
deltacat/compute/compactor_v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
53
53
|
deltacat/compute/compactor_v2/compaction_session.py,sha256=xAxiTOxHffRvPqkv4ObGM-NWdpxRRMyrS8WWZWrgTFQ,25141
|
54
|
-
deltacat/compute/compactor_v2/constants.py,sha256=
|
54
|
+
deltacat/compute/compactor_v2/constants.py,sha256=R5qvsaX-rDrSmSqUl0yLp6A3hVrAD4whYkV2NBkDR9Y,2199
|
55
55
|
deltacat/compute/compactor_v2/deletes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
56
56
|
deltacat/compute/compactor_v2/deletes/delete_file_envelope.py,sha256=AeuH9JRMwp6mvQf6P2cqL92hUEtResQq6qUTS0kIKac,3111
|
57
57
|
deltacat/compute/compactor_v2/deletes/delete_strategy.py,sha256=SMEJOxR-5r92kvKNqtu2w6HmwtmhljcZX1wcNEuS-4w,2833
|
58
58
|
deltacat/compute/compactor_v2/deletes/delete_strategy_equality_delete.py,sha256=U4zxVECXSPs1Nj3iPf_tiRRCs12CF8CHmRt4s_GDzq8,6503
|
59
59
|
deltacat/compute/compactor_v2/deletes/model.py,sha256=kW7kfRe4jVNMnsWQrl0nyKdDpvB9mbJND-MVzAajbAI,558
|
60
|
-
deltacat/compute/compactor_v2/deletes/utils.py,sha256=
|
60
|
+
deltacat/compute/compactor_v2/deletes/utils.py,sha256=9CchSw1_caWGWtRHa4ycy58t5T422EN6UB9XYa1zpbk,6640
|
61
61
|
deltacat/compute/compactor_v2/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
62
|
deltacat/compute/compactor_v2/model/hash_bucket_input.py,sha256=iJy8kLi1dIpFIyfoAjkaAtZvg8Np1z7BsUNGAcWfFm4,3042
|
63
63
|
deltacat/compute/compactor_v2/model/hash_bucket_result.py,sha256=EsY9BPPywhmxlcLKn3kGWzAX4s4BTR2vYyPUB-wAEOc,309
|
@@ -65,14 +65,14 @@ deltacat/compute/compactor_v2/model/merge_file_group.py,sha256=1o86t9lc3K6ZvtViV
|
|
65
65
|
deltacat/compute/compactor_v2/model/merge_input.py,sha256=ITRBR8gMbJpeRkZhRaVzGEEk3F2WqS2LwEkjd5zcaMQ,5416
|
66
66
|
deltacat/compute/compactor_v2/model/merge_result.py,sha256=_IZTCStpb4UKiRCJYA3g6EhAqjrw0t9vmoDAN8kIK-Y,436
|
67
67
|
deltacat/compute/compactor_v2/steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
68
|
-
deltacat/compute/compactor_v2/steps/hash_bucket.py,sha256=
|
69
|
-
deltacat/compute/compactor_v2/steps/merge.py,sha256=
|
68
|
+
deltacat/compute/compactor_v2/steps/hash_bucket.py,sha256=be61umFucTfzeZ03xii1P_NFqXsGmhl55hm_ULuCFPc,6617
|
69
|
+
deltacat/compute/compactor_v2/steps/merge.py,sha256=xjLpv93o9bMjihLXXO1j4XCXhyARc5GQI5eWxQRy0XI,21657
|
70
70
|
deltacat/compute/compactor_v2/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
71
71
|
deltacat/compute/compactor_v2/utils/content_type_params.py,sha256=rNKZisxGrLQOkwX8eHUQiFoTR1V-E66pMqWigtrs618,2156
|
72
72
|
deltacat/compute/compactor_v2/utils/dedupe.py,sha256=62tFCY2iRP7I3-45GCIYs6_SJsQl8C5lBEr8gbNfbsw,1932
|
73
73
|
deltacat/compute/compactor_v2/utils/delta.py,sha256=8hjkDeIIkSX-gAQ2utQSp2sZcO2tWZHMTxpFusZwBHw,3635
|
74
|
-
deltacat/compute/compactor_v2/utils/io.py,sha256=
|
75
|
-
deltacat/compute/compactor_v2/utils/merge.py,sha256=
|
74
|
+
deltacat/compute/compactor_v2/utils/io.py,sha256=autXlE3uHICdCCuJoS7mfdeJbRRiz2_xlz-3izlccB4,5264
|
75
|
+
deltacat/compute/compactor_v2/utils/merge.py,sha256=7p8lEmepxNekFFP5uVxgVLvAQRQnscOmUlSj8kUvW2c,5408
|
76
76
|
deltacat/compute/compactor_v2/utils/primary_key_index.py,sha256=MAscmL35WfwN7Is72aFlD_cGhxtZgjRwwR5kS9Yn2uU,11393
|
77
77
|
deltacat/compute/compactor_v2/utils/task_options.py,sha256=MCY0Sz5NCgNMaY92W8p87FvvDB91mnPQ4AhL8ix3BiA,13780
|
78
78
|
deltacat/compute/merge_on_read/__init__.py,sha256=ckbgngmqPjYBYz_NySsR1vNTOb_hNpeL1sYkZKvBI9M,214
|
@@ -174,6 +174,7 @@ deltacat/tests/test_utils/utils.py,sha256=a32qEwcSSd1lvRi0aJJ4ZLnc1ZyXmoQF_K95za
|
|
174
174
|
deltacat/tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
175
175
|
deltacat/tests/utils/test_cloudpickle.py,sha256=J0pnBY3-PxlUh6MamZAN1PuquKQPr2iyzjiJ7-Rcl0o,1506
|
176
176
|
deltacat/tests/utils/test_daft.py,sha256=Xal84zR42rXsWQI3lImdDYWOzewomKmhmiUQ59m67V0,6488
|
177
|
+
deltacat/tests/utils/test_metrics.py,sha256=Ym9nOz1EtB180pLmvugihj1sDTNDMb5opIjjr5Nmcls,16339
|
177
178
|
deltacat/tests/utils/test_pyarrow.py,sha256=eZAuYp9MUf8lmpIilH57JkURuNsTGZ3IAGC4Gm5hdrM,17307
|
178
179
|
deltacat/tests/utils/test_record_batch_tables.py,sha256=AkG1WyljQmjnl-AxhbFWyo5LnMIKRyLScfgC2B_ES-s,11321
|
179
180
|
deltacat/tests/utils/test_resources.py,sha256=HtpvDrfPZQNtGDXUlsIzc_yd7Vf1cDscZ3YbN0oTvO8,2560
|
@@ -187,7 +188,7 @@ deltacat/utils/arguments.py,sha256=5y1Xz4HSAD8M8Jt83i6gOEKoYjy_fMQe1V43IhIE4hY,1
|
|
187
188
|
deltacat/utils/cloudpickle.py,sha256=XE7YDmQe56ksfl3NdYZkzOAhbHSuhNcBZGOehQpgZr0,1187
|
188
189
|
deltacat/utils/common.py,sha256=RG_-enXNpLKaYrqyx1ne2lL10lxN9vK7F631oJP6SE8,1375
|
189
190
|
deltacat/utils/daft.py,sha256=heg6J8NHnTI1UbcjXvsjGBbFKU6f7mvAio-KZuTSuFI,5506
|
190
|
-
deltacat/utils/metrics.py,sha256=
|
191
|
+
deltacat/utils/metrics.py,sha256=HYKyZSrtVLu8gXezg_TMNUKJp4h1WWI0VEzn0Xlzf-I,10778
|
191
192
|
deltacat/utils/numpy.py,sha256=ZiGREobTVT6IZXgPxkSUpLJFN2Hn8KEZcrqybLDXCIA,2027
|
192
193
|
deltacat/utils/pandas.py,sha256=GfwjYb8FUSEeoBdXZI1_NJkdjxPMbCCUhlyRfGbDkn8,9562
|
193
194
|
deltacat/utils/performance.py,sha256=7ZLaMkS1ehPSIhT5uOQVBHvjC70iKHzoFquFo-KL0PI,645
|
@@ -202,8 +203,8 @@ deltacat/utils/ray_utils/concurrency.py,sha256=JDVwMiQWrmuSlyCWAoiq9ctoJ0XADEfDD
|
|
202
203
|
deltacat/utils/ray_utils/dataset.py,sha256=SIljK3UkSqQ6Ntit_iSiYt9yYjN_gGrCTX6_72XdQ3w,3244
|
203
204
|
deltacat/utils/ray_utils/performance.py,sha256=d7JFM7vTXHzkGx9qNQcZzUWajnqINvYRwaM088_FpsE,464
|
204
205
|
deltacat/utils/ray_utils/runtime.py,sha256=5eaBWTDm0IXVoc5Y6aacoVB-f0Mnv-K2ewyTSjHKHwM,5009
|
205
|
-
deltacat-1.1.
|
206
|
-
deltacat-1.1.
|
207
|
-
deltacat-1.1.
|
208
|
-
deltacat-1.1.
|
209
|
-
deltacat-1.1.
|
206
|
+
deltacat-1.1.3.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
207
|
+
deltacat-1.1.3.dist-info/METADATA,sha256=uj1xTijXxJlOZQy38MbY6Axj7cMjsT75xlKjWZPJyzw,1780
|
208
|
+
deltacat-1.1.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
209
|
+
deltacat-1.1.3.dist-info/top_level.txt,sha256=RWdIcid4Bv2i2ozLVh-70kJpyB61xEKXod9XXGpiono,9
|
210
|
+
deltacat-1.1.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|