deltacat 1.1.1__py3-none-any.whl → 1.1.2__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/compaction_session.py +10 -0
- 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 +575 -0
- deltacat/utils/metrics.py +158 -23
- {deltacat-1.1.1.dist-info → deltacat-1.1.2.dist-info}/METADATA +1 -1
- {deltacat-1.1.1.dist-info → deltacat-1.1.2.dist-info}/RECORD +18 -17
- {deltacat-1.1.1.dist-info → deltacat-1.1.2.dist-info}/LICENSE +0 -0
- {deltacat-1.1.1.dist-info → deltacat-1.1.2.dist-info}/WHEEL +0 -0
- {deltacat-1.1.1.dist-info → deltacat-1.1.2.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,
|
@@ -65,6 +65,7 @@ from deltacat.compute.compactor_v2.utils.task_options import (
|
|
65
65
|
local_merge_resource_options_provider,
|
66
66
|
)
|
67
67
|
from deltacat.compute.compactor.model.compactor_version import CompactorVersion
|
68
|
+
from deltacat.utils.metrics import MetricsActor, METRICS_CONFIG_ACTOR_NAME
|
68
69
|
|
69
70
|
if importlib.util.find_spec("memray"):
|
70
71
|
import memray
|
@@ -118,6 +119,15 @@ def _execute_compaction(
|
|
118
119
|
params: CompactPartitionParams, **kwargs
|
119
120
|
) -> Tuple[Optional[Partition], Optional[RoundCompletionInfo], Optional[str]]:
|
120
121
|
|
122
|
+
if params.metrics_config:
|
123
|
+
logger.info(
|
124
|
+
f"Setting metrics config with target: {params.metrics_config.metrics_target}"
|
125
|
+
)
|
126
|
+
metrics_actor = MetricsActor.options(
|
127
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
128
|
+
).remote()
|
129
|
+
ray.get(metrics_actor.set_metrics_config.remote(params.metrics_config))
|
130
|
+
|
121
131
|
rcf_source_partition_locator = (
|
122
132
|
params.rebase_source_partition_locator or params.source_partition_locator
|
123
133
|
)
|
@@ -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,575 @@
|
|
1
|
+
import unittest
|
2
|
+
from unittest.mock import MagicMock, ANY, call
|
3
|
+
import ray
|
4
|
+
from deltacat.utils.metrics import (
|
5
|
+
metrics,
|
6
|
+
success_metric,
|
7
|
+
failure_metric,
|
8
|
+
latency_metric,
|
9
|
+
MetricsActor,
|
10
|
+
METRICS_CONFIG_ACTOR_NAME,
|
11
|
+
MetricsConfig,
|
12
|
+
MetricsTarget,
|
13
|
+
METRICS_TARGET_TO_EMITTER_DICT,
|
14
|
+
)
|
15
|
+
from deltacat.utils.performance import timed_invocation
|
16
|
+
|
17
|
+
|
18
|
+
def method_not_annotated(mock_func):
|
19
|
+
mock_func("called")
|
20
|
+
|
21
|
+
|
22
|
+
@metrics
|
23
|
+
def metrics_annotated_method(mock_func):
|
24
|
+
mock_func("called")
|
25
|
+
|
26
|
+
|
27
|
+
@metrics
|
28
|
+
def metrics_annotated_method_error(mock_func):
|
29
|
+
raise ValueError()
|
30
|
+
|
31
|
+
|
32
|
+
@metrics(prefix="test_prefix")
|
33
|
+
def metrics_with_prefix_annotated_method(mock_func):
|
34
|
+
mock_func("called")
|
35
|
+
|
36
|
+
|
37
|
+
@metrics(prefix="test_prefix")
|
38
|
+
def metrics_with_prefix_annotated_method_error(mock_func):
|
39
|
+
raise ValueError()
|
40
|
+
|
41
|
+
|
42
|
+
class TestMetricsAnnotation(unittest.TestCase):
|
43
|
+
def test_metrics_annotation_sanity(self):
|
44
|
+
mock, mock_target = MagicMock(), MagicMock()
|
45
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
46
|
+
metrics_actor = MetricsActor.options(
|
47
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
48
|
+
).remote()
|
49
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
50
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
51
|
+
|
52
|
+
# action
|
53
|
+
metrics_annotated_method(mock)
|
54
|
+
|
55
|
+
mock.assert_called_once()
|
56
|
+
self.assertEqual(2, mock_target.call_count)
|
57
|
+
mock_target.assert_has_calls(
|
58
|
+
[
|
59
|
+
call(
|
60
|
+
metrics_name="metrics_annotated_method_time",
|
61
|
+
metrics_config=ANY,
|
62
|
+
value=ANY,
|
63
|
+
),
|
64
|
+
call(
|
65
|
+
metrics_name="metrics_annotated_method_success_count",
|
66
|
+
metrics_config=ANY,
|
67
|
+
value=ANY,
|
68
|
+
),
|
69
|
+
],
|
70
|
+
any_order=True,
|
71
|
+
)
|
72
|
+
|
73
|
+
def test_metrics_annotation_when_error(self):
|
74
|
+
mock, mock_target = MagicMock(), MagicMock()
|
75
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
76
|
+
metrics_actor = MetricsActor.options(
|
77
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
78
|
+
).remote()
|
79
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
80
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
81
|
+
|
82
|
+
# action
|
83
|
+
self.assertRaises(ValueError, lambda: metrics_annotated_method_error(mock))
|
84
|
+
|
85
|
+
mock.assert_not_called()
|
86
|
+
self.assertEqual(3, mock_target.call_count)
|
87
|
+
mock_target.assert_has_calls(
|
88
|
+
[
|
89
|
+
call(
|
90
|
+
metrics_name="metrics_annotated_method_error_time",
|
91
|
+
metrics_config=ANY,
|
92
|
+
value=ANY,
|
93
|
+
),
|
94
|
+
call(
|
95
|
+
metrics_name="metrics_annotated_method_error_failure_count",
|
96
|
+
metrics_config=ANY,
|
97
|
+
value=ANY,
|
98
|
+
),
|
99
|
+
call(
|
100
|
+
metrics_name="metrics_annotated_method_error_failure_count.ValueError",
|
101
|
+
metrics_config=ANY,
|
102
|
+
value=ANY,
|
103
|
+
),
|
104
|
+
],
|
105
|
+
any_order=True,
|
106
|
+
)
|
107
|
+
|
108
|
+
def test_metrics_with_prefix_annotation_sanity(self):
|
109
|
+
mock, mock_target = MagicMock(), MagicMock()
|
110
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
111
|
+
metrics_actor = MetricsActor.options(
|
112
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
113
|
+
).remote()
|
114
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
115
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
116
|
+
|
117
|
+
# action
|
118
|
+
metrics_with_prefix_annotated_method(mock)
|
119
|
+
|
120
|
+
mock.assert_called_once()
|
121
|
+
self.assertEqual(2, mock_target.call_count)
|
122
|
+
mock_target.assert_has_calls(
|
123
|
+
[
|
124
|
+
call(metrics_name="test_prefix_time", metrics_config=ANY, value=ANY),
|
125
|
+
call(
|
126
|
+
metrics_name="test_prefix_success_count",
|
127
|
+
metrics_config=ANY,
|
128
|
+
value=ANY,
|
129
|
+
),
|
130
|
+
],
|
131
|
+
any_order=True,
|
132
|
+
)
|
133
|
+
|
134
|
+
def test_metrics_annotation_performance(self):
|
135
|
+
mock, mock_target = MagicMock(), MagicMock()
|
136
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
137
|
+
metrics_actor = MetricsActor.options(
|
138
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
139
|
+
).remote()
|
140
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
141
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
142
|
+
|
143
|
+
# action with annotation
|
144
|
+
_, actual_latency = timed_invocation(metrics_annotated_method, mock)
|
145
|
+
_, second_call_latency = timed_invocation(metrics_annotated_method, mock)
|
146
|
+
|
147
|
+
mock.assert_called()
|
148
|
+
self.assertEqual(4, mock_target.call_count)
|
149
|
+
self.assertLess(
|
150
|
+
second_call_latency,
|
151
|
+
actual_latency,
|
152
|
+
"Second call to actor must be much faster",
|
153
|
+
)
|
154
|
+
|
155
|
+
def test_metrics_with_prefix_annotation_when_error(self):
|
156
|
+
mock, mock_target = MagicMock(), MagicMock()
|
157
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
158
|
+
metrics_actor = MetricsActor.options(
|
159
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
160
|
+
).remote()
|
161
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
162
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
163
|
+
|
164
|
+
# action
|
165
|
+
self.assertRaises(
|
166
|
+
ValueError, lambda: metrics_with_prefix_annotated_method_error(mock)
|
167
|
+
)
|
168
|
+
|
169
|
+
mock.assert_not_called()
|
170
|
+
self.assertEqual(3, mock_target.call_count)
|
171
|
+
mock_target.assert_has_calls(
|
172
|
+
[
|
173
|
+
call(metrics_name="test_prefix_time", metrics_config=ANY, value=ANY),
|
174
|
+
call(
|
175
|
+
metrics_name="test_prefix_failure_count",
|
176
|
+
metrics_config=ANY,
|
177
|
+
value=ANY,
|
178
|
+
),
|
179
|
+
call(
|
180
|
+
metrics_name="test_prefix_failure_count.ValueError",
|
181
|
+
metrics_config=ANY,
|
182
|
+
value=ANY,
|
183
|
+
),
|
184
|
+
],
|
185
|
+
any_order=True,
|
186
|
+
)
|
187
|
+
|
188
|
+
def test_metrics_with_prefix_annotation_without_actor(self):
|
189
|
+
mock, mock_target = MagicMock(), MagicMock()
|
190
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
191
|
+
metrics_actor = MetricsActor.options(
|
192
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
193
|
+
).remote()
|
194
|
+
|
195
|
+
# explicitly making sure actor is killed
|
196
|
+
ray.kill(metrics_actor)
|
197
|
+
|
198
|
+
# action
|
199
|
+
self.assertRaises(
|
200
|
+
ValueError, lambda: metrics_with_prefix_annotated_method_error(mock)
|
201
|
+
)
|
202
|
+
|
203
|
+
mock.assert_not_called()
|
204
|
+
mock_target.assert_not_called()
|
205
|
+
|
206
|
+
|
207
|
+
@latency_metric
|
208
|
+
def latency_metric_annotated_method(mock_func):
|
209
|
+
mock_func("called")
|
210
|
+
|
211
|
+
|
212
|
+
@latency_metric
|
213
|
+
def latency_metric_annotated_method_error(mock_func):
|
214
|
+
raise ValueError()
|
215
|
+
|
216
|
+
|
217
|
+
@latency_metric(name="test")
|
218
|
+
def latency_metric_with_name_annotated_method(mock_func):
|
219
|
+
mock_func("called")
|
220
|
+
|
221
|
+
|
222
|
+
@latency_metric(name="test")
|
223
|
+
def latency_metric_with_name_annotated_method_error(mock_func):
|
224
|
+
raise ValueError()
|
225
|
+
|
226
|
+
|
227
|
+
class TestLatencyMetricAnnotation(unittest.TestCase):
|
228
|
+
def test_annotation_sanity(self):
|
229
|
+
mock, mock_target = MagicMock(), MagicMock()
|
230
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
231
|
+
metrics_actor = MetricsActor.options(
|
232
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
233
|
+
).remote()
|
234
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
235
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
236
|
+
|
237
|
+
# action
|
238
|
+
latency_metric_annotated_method(mock)
|
239
|
+
|
240
|
+
mock.assert_called_once()
|
241
|
+
self.assertEqual(1, mock_target.call_count)
|
242
|
+
mock_target.assert_has_calls(
|
243
|
+
[
|
244
|
+
call(
|
245
|
+
metrics_name="latency_metric_annotated_method_time",
|
246
|
+
metrics_config=ANY,
|
247
|
+
value=ANY,
|
248
|
+
)
|
249
|
+
],
|
250
|
+
any_order=True,
|
251
|
+
)
|
252
|
+
|
253
|
+
def test_annotation_when_error(self):
|
254
|
+
mock, mock_target = MagicMock(), MagicMock()
|
255
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
256
|
+
metrics_actor = MetricsActor.options(
|
257
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
258
|
+
).remote()
|
259
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
260
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
261
|
+
|
262
|
+
# action
|
263
|
+
self.assertRaises(
|
264
|
+
ValueError, lambda: latency_metric_annotated_method_error(mock)
|
265
|
+
)
|
266
|
+
|
267
|
+
mock.assert_not_called()
|
268
|
+
self.assertEqual(1, mock_target.call_count)
|
269
|
+
mock_target.assert_has_calls(
|
270
|
+
[
|
271
|
+
call(
|
272
|
+
metrics_name="latency_metric_annotated_method_error_time",
|
273
|
+
metrics_config=ANY,
|
274
|
+
value=ANY,
|
275
|
+
)
|
276
|
+
],
|
277
|
+
any_order=True,
|
278
|
+
)
|
279
|
+
|
280
|
+
def test_annotation_with_args_sanity(self):
|
281
|
+
mock, mock_target = MagicMock(), MagicMock()
|
282
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
283
|
+
metrics_actor = MetricsActor.options(
|
284
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
285
|
+
).remote()
|
286
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
287
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
288
|
+
|
289
|
+
# action
|
290
|
+
latency_metric_with_name_annotated_method(mock)
|
291
|
+
|
292
|
+
mock.assert_called_once()
|
293
|
+
self.assertEqual(1, mock_target.call_count)
|
294
|
+
mock_target.assert_has_calls(
|
295
|
+
[call(metrics_name="test", metrics_config=ANY, value=ANY)], any_order=True
|
296
|
+
)
|
297
|
+
|
298
|
+
def test_annotation_with_args_when_error(self):
|
299
|
+
mock, mock_target = MagicMock(), MagicMock()
|
300
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
301
|
+
metrics_actor = MetricsActor.options(
|
302
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
303
|
+
).remote()
|
304
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
305
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
306
|
+
|
307
|
+
# action
|
308
|
+
self.assertRaises(
|
309
|
+
ValueError, lambda: latency_metric_with_name_annotated_method_error(mock)
|
310
|
+
)
|
311
|
+
|
312
|
+
mock.assert_not_called()
|
313
|
+
self.assertEqual(1, mock_target.call_count)
|
314
|
+
mock_target.assert_has_calls(
|
315
|
+
[call(metrics_name="test", metrics_config=ANY, value=ANY)], any_order=True
|
316
|
+
)
|
317
|
+
|
318
|
+
def test_annotation_without_actor(self):
|
319
|
+
mock, mock_target = MagicMock(), MagicMock()
|
320
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
321
|
+
metrics_actor = MetricsActor.options(
|
322
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
323
|
+
).remote()
|
324
|
+
|
325
|
+
# explicitly making sure actor is killed
|
326
|
+
ray.kill(metrics_actor)
|
327
|
+
|
328
|
+
# action
|
329
|
+
self.assertRaises(
|
330
|
+
ValueError, lambda: latency_metric_with_name_annotated_method_error(mock)
|
331
|
+
)
|
332
|
+
|
333
|
+
mock.assert_not_called()
|
334
|
+
mock_target.assert_not_called()
|
335
|
+
|
336
|
+
|
337
|
+
@success_metric
|
338
|
+
def success_metric_annotated_method(mock_func):
|
339
|
+
mock_func("called")
|
340
|
+
|
341
|
+
|
342
|
+
@success_metric
|
343
|
+
def success_metric_annotated_method_error(mock_func):
|
344
|
+
raise ValueError()
|
345
|
+
|
346
|
+
|
347
|
+
@success_metric(name="test")
|
348
|
+
def success_metric_with_name_annotated_method(mock_func):
|
349
|
+
mock_func("called")
|
350
|
+
|
351
|
+
|
352
|
+
@success_metric(name="test")
|
353
|
+
def success_metric_with_name_annotated_method_error(mock_func):
|
354
|
+
raise ValueError()
|
355
|
+
|
356
|
+
|
357
|
+
class TestSuccessMetricAnnotation(unittest.TestCase):
|
358
|
+
def test_annotation_sanity(self):
|
359
|
+
mock, mock_target = MagicMock(), MagicMock()
|
360
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
361
|
+
metrics_actor = MetricsActor.options(
|
362
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
363
|
+
).remote()
|
364
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
365
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
366
|
+
|
367
|
+
# action
|
368
|
+
success_metric_annotated_method(mock)
|
369
|
+
|
370
|
+
mock.assert_called_once()
|
371
|
+
self.assertEqual(1, mock_target.call_count)
|
372
|
+
mock_target.assert_has_calls(
|
373
|
+
[
|
374
|
+
call(
|
375
|
+
metrics_name="success_metric_annotated_method_success_count",
|
376
|
+
metrics_config=ANY,
|
377
|
+
value=ANY,
|
378
|
+
)
|
379
|
+
],
|
380
|
+
any_order=True,
|
381
|
+
)
|
382
|
+
|
383
|
+
def test_annotation_when_error(self):
|
384
|
+
mock, mock_target = MagicMock(), MagicMock()
|
385
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
386
|
+
metrics_actor = MetricsActor.options(
|
387
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
388
|
+
).remote()
|
389
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
390
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
391
|
+
|
392
|
+
# action
|
393
|
+
self.assertRaises(
|
394
|
+
ValueError, lambda: success_metric_annotated_method_error(mock)
|
395
|
+
)
|
396
|
+
|
397
|
+
mock.assert_not_called()
|
398
|
+
mock_target.assert_not_called()
|
399
|
+
|
400
|
+
def test_annotation_with_args_sanity(self):
|
401
|
+
mock, mock_target = MagicMock(), MagicMock()
|
402
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
403
|
+
metrics_actor = MetricsActor.options(
|
404
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
405
|
+
).remote()
|
406
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
407
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
408
|
+
|
409
|
+
# action
|
410
|
+
success_metric_with_name_annotated_method(mock)
|
411
|
+
|
412
|
+
mock.assert_called_once()
|
413
|
+
self.assertEqual(1, mock_target.call_count)
|
414
|
+
mock_target.assert_has_calls(
|
415
|
+
[call(metrics_name="test", metrics_config=ANY, value=ANY)], any_order=True
|
416
|
+
)
|
417
|
+
|
418
|
+
def test_annotation_with_args_when_error(self):
|
419
|
+
mock, mock_target = MagicMock(), MagicMock()
|
420
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
421
|
+
metrics_actor = MetricsActor.options(
|
422
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
423
|
+
).remote()
|
424
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
425
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
426
|
+
|
427
|
+
# action
|
428
|
+
self.assertRaises(
|
429
|
+
ValueError, lambda: success_metric_with_name_annotated_method_error(mock)
|
430
|
+
)
|
431
|
+
|
432
|
+
mock.assert_not_called()
|
433
|
+
mock_target.assert_not_called()
|
434
|
+
|
435
|
+
def test_annotation_without_actor(self):
|
436
|
+
mock, mock_target = MagicMock(), MagicMock()
|
437
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
438
|
+
metrics_actor = MetricsActor.options(
|
439
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
440
|
+
).remote()
|
441
|
+
|
442
|
+
# explicitly making sure actor is killed
|
443
|
+
ray.kill(metrics_actor)
|
444
|
+
|
445
|
+
# action
|
446
|
+
success_metric_annotated_method(mock)
|
447
|
+
|
448
|
+
mock.assert_called_once()
|
449
|
+
mock_target.assert_not_called()
|
450
|
+
|
451
|
+
|
452
|
+
@failure_metric
|
453
|
+
def failure_metric_annotated_method(mock_func):
|
454
|
+
mock_func("called")
|
455
|
+
|
456
|
+
|
457
|
+
@failure_metric
|
458
|
+
def failure_metric_annotated_method_error(mock_func):
|
459
|
+
raise ValueError()
|
460
|
+
|
461
|
+
|
462
|
+
@failure_metric(name="test")
|
463
|
+
def failure_metric_with_name_annotated_method(mock_func):
|
464
|
+
mock_func("called")
|
465
|
+
|
466
|
+
|
467
|
+
@failure_metric(name="test")
|
468
|
+
def failure_metric_with_name_annotated_method_error(mock_func):
|
469
|
+
raise ValueError()
|
470
|
+
|
471
|
+
|
472
|
+
class TestFailureMetricAnnotation(unittest.TestCase):
|
473
|
+
def test_annotation_sanity(self):
|
474
|
+
mock, mock_target = MagicMock(), MagicMock()
|
475
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
476
|
+
metrics_actor = MetricsActor.options(
|
477
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
478
|
+
).remote()
|
479
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
480
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
481
|
+
|
482
|
+
# action
|
483
|
+
failure_metric_annotated_method(mock)
|
484
|
+
|
485
|
+
mock.assert_called_once()
|
486
|
+
mock_target.assert_not_called()
|
487
|
+
|
488
|
+
def test_annotation_when_error(self):
|
489
|
+
mock, mock_target = MagicMock(), MagicMock()
|
490
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
491
|
+
metrics_actor = MetricsActor.options(
|
492
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
493
|
+
).remote()
|
494
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
495
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
496
|
+
|
497
|
+
# action
|
498
|
+
self.assertRaises(
|
499
|
+
ValueError, lambda: failure_metric_annotated_method_error(mock)
|
500
|
+
)
|
501
|
+
|
502
|
+
mock.assert_not_called()
|
503
|
+
self.assertEqual(2, mock_target.call_count)
|
504
|
+
mock_target.assert_has_calls(
|
505
|
+
[
|
506
|
+
call(
|
507
|
+
metrics_name="failure_metric_annotated_method_error_failure_count.ValueError",
|
508
|
+
metrics_config=ANY,
|
509
|
+
value=ANY,
|
510
|
+
),
|
511
|
+
call(
|
512
|
+
metrics_name="failure_metric_annotated_method_error_failure_count",
|
513
|
+
metrics_config=ANY,
|
514
|
+
value=ANY,
|
515
|
+
),
|
516
|
+
],
|
517
|
+
any_order=True,
|
518
|
+
)
|
519
|
+
|
520
|
+
def test_annotation_with_args_sanity(self):
|
521
|
+
mock, mock_target = MagicMock(), MagicMock()
|
522
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
523
|
+
metrics_actor = MetricsActor.options(
|
524
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
525
|
+
).remote()
|
526
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
527
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
528
|
+
|
529
|
+
# action
|
530
|
+
failure_metric_with_name_annotated_method(mock)
|
531
|
+
|
532
|
+
mock.assert_called_once()
|
533
|
+
mock_target.assert_not_called()
|
534
|
+
|
535
|
+
def test_annotation_with_args_when_error(self):
|
536
|
+
mock, mock_target = MagicMock(), MagicMock()
|
537
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
538
|
+
metrics_actor = MetricsActor.options(
|
539
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
540
|
+
).remote()
|
541
|
+
config = MetricsConfig("us-east-1", MetricsTarget.NOOP)
|
542
|
+
ray.get(metrics_actor.set_metrics_config.remote(config))
|
543
|
+
|
544
|
+
# action
|
545
|
+
self.assertRaises(
|
546
|
+
ValueError, lambda: failure_metric_with_name_annotated_method_error(mock)
|
547
|
+
)
|
548
|
+
|
549
|
+
mock.assert_not_called()
|
550
|
+
self.assertEqual(2, mock_target.call_count)
|
551
|
+
mock_target.assert_has_calls(
|
552
|
+
[
|
553
|
+
call(metrics_name="test.ValueError", metrics_config=ANY, value=ANY),
|
554
|
+
call(metrics_name="test", metrics_config=ANY, value=ANY),
|
555
|
+
],
|
556
|
+
any_order=True,
|
557
|
+
)
|
558
|
+
|
559
|
+
def test_annotation_without_actor(self):
|
560
|
+
mock, mock_target = MagicMock(), MagicMock()
|
561
|
+
METRICS_TARGET_TO_EMITTER_DICT[MetricsTarget.NOOP] = mock_target
|
562
|
+
metrics_actor = MetricsActor.options(
|
563
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
564
|
+
).remote()
|
565
|
+
|
566
|
+
# explicitly making sure actor is killed
|
567
|
+
ray.kill(metrics_actor)
|
568
|
+
|
569
|
+
# action
|
570
|
+
self.assertRaises(
|
571
|
+
ValueError, lambda: failure_metric_with_name_annotated_method_error(mock)
|
572
|
+
)
|
573
|
+
|
574
|
+
mock.assert_not_called()
|
575
|
+
mock_target.assert_not_called()
|
deltacat/utils/metrics.py
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
+
from typing import Optional
|
2
3
|
|
3
4
|
import logging
|
5
|
+
import ray
|
6
|
+
import time
|
7
|
+
import functools
|
4
8
|
|
5
9
|
from aws_embedded_metrics import metric_scope
|
6
10
|
from aws_embedded_metrics.config import get_config
|
@@ -18,11 +22,13 @@ logger = logs.configure_deltacat_logger(logging.getLogger(__name__))
|
|
18
22
|
DEFAULT_DELTACAT_METRICS_NAMESPACE = "ray-deltacat-metrics"
|
19
23
|
DEFAULT_DELTACAT_LOG_GROUP_NAME = "ray-deltacat-metrics-EMF-logs"
|
20
24
|
DEFAULT_DELTACAT_LOG_STREAM_CALLABLE = get_node_ip_address
|
25
|
+
METRICS_CONFIG_ACTOR_NAME = "metrics-config-actor"
|
21
26
|
|
22
27
|
|
23
28
|
class MetricsTarget(str, Enum):
|
24
29
|
CLOUDWATCH = "cloudwatch"
|
25
30
|
CLOUDWATCH_EMF = "cloudwatch_emf"
|
31
|
+
NOOP = "noop"
|
26
32
|
|
27
33
|
|
28
34
|
@dataclass
|
@@ -42,27 +48,16 @@ class MetricsConfig:
|
|
42
48
|
self.metrics_kwargs = metrics_kwargs
|
43
49
|
|
44
50
|
|
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
51
|
def _build_cloudwatch_metrics(
|
55
52
|
metrics_name: str,
|
56
|
-
metrics_type: Enum,
|
57
53
|
value: str,
|
58
54
|
metrics_dimensions: List[Dict[str, str]],
|
59
55
|
timestamp: datetime,
|
60
56
|
**kwargs,
|
61
57
|
) -> Dict[str, Any]:
|
62
|
-
metrics_name_with_type = _build_metrics_name(metrics_type, metrics_name)
|
63
58
|
return [
|
64
59
|
{
|
65
|
-
"MetricName":
|
60
|
+
"MetricName": metrics_name,
|
66
61
|
"Dimensions": metrics_dimensions,
|
67
62
|
"Timestamp": timestamp,
|
68
63
|
"Value": value,
|
@@ -73,7 +68,6 @@ def _build_cloudwatch_metrics(
|
|
73
68
|
|
74
69
|
def _emit_metrics(
|
75
70
|
metrics_name: str,
|
76
|
-
metrics_type: Enum,
|
77
71
|
metrics_config: MetricsConfig,
|
78
72
|
value: str,
|
79
73
|
**kwargs,
|
@@ -85,7 +79,6 @@ def _emit_metrics(
|
|
85
79
|
if metrics_target in METRICS_TARGET_TO_EMITTER_DICT:
|
86
80
|
METRICS_TARGET_TO_EMITTER_DICT.get(metrics_target)(
|
87
81
|
metrics_name=metrics_name,
|
88
|
-
metrics_type=metrics_type,
|
89
82
|
metrics_config=metrics_config,
|
90
83
|
value=value,
|
91
84
|
**kwargs,
|
@@ -94,9 +87,15 @@ def _emit_metrics(
|
|
94
87
|
logger.warning(f"{metrics_target} is not a supported metrics target type.")
|
95
88
|
|
96
89
|
|
90
|
+
def _emit_noop_metrics(
|
91
|
+
*args,
|
92
|
+
**kwargs,
|
93
|
+
) -> None:
|
94
|
+
pass
|
95
|
+
|
96
|
+
|
97
97
|
def _emit_cloudwatch_metrics(
|
98
98
|
metrics_name: str,
|
99
|
-
metrics_type: Enum,
|
100
99
|
metrics_config: MetricsConfig,
|
101
100
|
value: str,
|
102
101
|
**kwargs,
|
@@ -111,7 +110,6 @@ def _emit_cloudwatch_metrics(
|
|
111
110
|
|
112
111
|
metrics_data = _build_cloudwatch_metrics(
|
113
112
|
metrics_name,
|
114
|
-
metrics_type,
|
115
113
|
value,
|
116
114
|
metrics_dimensions,
|
117
115
|
current_time,
|
@@ -128,14 +126,13 @@ def _emit_cloudwatch_metrics(
|
|
128
126
|
except Exception as e:
|
129
127
|
logger.warning(
|
130
128
|
f"Failed to publish Cloudwatch metrics with name: {metrics_name}, "
|
131
|
-
f"
|
129
|
+
f"with exception: {e}, response: {response}"
|
132
130
|
)
|
133
131
|
|
134
132
|
|
135
133
|
@metric_scope
|
136
134
|
def _emit_cloudwatch_emf_metrics(
|
137
135
|
metrics_name: str,
|
138
|
-
metrics_type: Enum,
|
139
136
|
metrics_config: MetricsConfig,
|
140
137
|
value: str,
|
141
138
|
metrics: MetricsLogger,
|
@@ -155,7 +152,6 @@ def _emit_cloudwatch_emf_metrics(
|
|
155
152
|
dimensions = dict(
|
156
153
|
[(dim["Name"], dim["Value"]) for dim in metrics_config.metrics_dimensions]
|
157
154
|
)
|
158
|
-
metrics_name_with_type = _build_metrics_name(metrics_type, metrics_name)
|
159
155
|
try:
|
160
156
|
metrics.set_timestamp(current_time)
|
161
157
|
metrics.set_dimensions(dimensions)
|
@@ -177,25 +173,164 @@ def _emit_cloudwatch_emf_metrics(
|
|
177
173
|
else DEFAULT_DELTACAT_LOG_STREAM_CALLABLE()
|
178
174
|
)
|
179
175
|
|
180
|
-
metrics.put_metric(
|
176
|
+
metrics.put_metric(metrics_name, value)
|
181
177
|
except Exception as e:
|
182
178
|
logger.warning(
|
183
|
-
f"Failed to publish Cloudwatch EMF metrics with name: {
|
184
|
-
f"type: {metrics_type}, with exception: {e}"
|
179
|
+
f"Failed to publish Cloudwatch EMF metrics with name: {metrics_name}", e
|
185
180
|
)
|
186
181
|
|
187
182
|
|
188
183
|
METRICS_TARGET_TO_EMITTER_DICT: Dict[str, Callable] = {
|
189
184
|
MetricsTarget.CLOUDWATCH: _emit_cloudwatch_metrics,
|
190
185
|
MetricsTarget.CLOUDWATCH_EMF: _emit_cloudwatch_emf_metrics,
|
186
|
+
MetricsTarget.NOOP: _emit_noop_metrics,
|
191
187
|
}
|
192
188
|
|
193
189
|
|
190
|
+
@ray.remote
|
191
|
+
class MetricsActor:
|
192
|
+
metrics_config: MetricsConfig = None
|
193
|
+
|
194
|
+
def set_metrics_config(self, config: MetricsConfig) -> None:
|
195
|
+
self.metrics_config = config
|
196
|
+
|
197
|
+
def get_metrics_config(self) -> MetricsConfig:
|
198
|
+
return self.metrics_config
|
199
|
+
|
200
|
+
|
201
|
+
def _emit_ignore_exceptions(name: Optional[str], value: Any):
|
202
|
+
try:
|
203
|
+
config_cache: MetricsActor = MetricsActor.options(
|
204
|
+
name=METRICS_CONFIG_ACTOR_NAME, get_if_exists=True
|
205
|
+
).remote()
|
206
|
+
config = ray.get(config_cache.get_metrics_config.remote())
|
207
|
+
if config:
|
208
|
+
_emit_metrics(metrics_name=name, value=value, metrics_config=config)
|
209
|
+
except BaseException as ex:
|
210
|
+
logger.warning("Emitting metrics failed", ex)
|
211
|
+
|
212
|
+
|
194
213
|
def emit_timer_metrics(metrics_name, value, metrics_config, **kwargs):
|
195
214
|
_emit_metrics(
|
196
215
|
metrics_name=metrics_name,
|
197
|
-
metrics_type=MetricsType.TIMER,
|
198
216
|
metrics_config=metrics_config,
|
199
217
|
value=value,
|
200
218
|
**kwargs,
|
201
219
|
)
|
220
|
+
|
221
|
+
|
222
|
+
def latency_metric(original_func=None, name: Optional[str] = None):
|
223
|
+
"""
|
224
|
+
A decorator that emits latency metrics of a function
|
225
|
+
based on configured metrics config.
|
226
|
+
"""
|
227
|
+
|
228
|
+
def _decorate(func):
|
229
|
+
metrics_name = name or f"{func.__name__}_time"
|
230
|
+
|
231
|
+
@functools.wraps(func)
|
232
|
+
def wrapper(*args, **kwargs):
|
233
|
+
start = time.monotonic()
|
234
|
+
try:
|
235
|
+
return func(*args, **kwargs)
|
236
|
+
finally:
|
237
|
+
end = time.monotonic()
|
238
|
+
_emit_ignore_exceptions(name=metrics_name, value=end - start)
|
239
|
+
|
240
|
+
return wrapper
|
241
|
+
|
242
|
+
if original_func:
|
243
|
+
return _decorate(original_func)
|
244
|
+
|
245
|
+
return _decorate
|
246
|
+
|
247
|
+
|
248
|
+
def success_metric(original_func=None, name: Optional[str] = None):
|
249
|
+
"""
|
250
|
+
A decorator that emits success metrics of a function
|
251
|
+
based on configured metrics config.
|
252
|
+
"""
|
253
|
+
|
254
|
+
def _decorate(func):
|
255
|
+
metrics_name = name or f"{func.__name__}_success_count"
|
256
|
+
|
257
|
+
@functools.wraps(func)
|
258
|
+
def wrapper(*args, **kwargs):
|
259
|
+
result = func(*args, **kwargs)
|
260
|
+
_emit_ignore_exceptions(name=metrics_name, value=1)
|
261
|
+
return result
|
262
|
+
|
263
|
+
return wrapper
|
264
|
+
|
265
|
+
if original_func:
|
266
|
+
return _decorate(original_func)
|
267
|
+
|
268
|
+
return _decorate
|
269
|
+
|
270
|
+
|
271
|
+
def failure_metric(original_func=None, name: Optional[str] = None):
|
272
|
+
"""
|
273
|
+
A decorator that emits failure metrics of a function
|
274
|
+
based on configured metrics config.
|
275
|
+
"""
|
276
|
+
|
277
|
+
def _decorate(func):
|
278
|
+
metrics_name = name or f"{func.__name__}_failure_count"
|
279
|
+
|
280
|
+
@functools.wraps(func)
|
281
|
+
def wrapper(*args, **kwargs):
|
282
|
+
try:
|
283
|
+
return func(*args, **kwargs)
|
284
|
+
except BaseException as ex:
|
285
|
+
exception_name = type(ex).__name__
|
286
|
+
# We emit two metrics, one for exception class and
|
287
|
+
# another for just the specified metric name.
|
288
|
+
_emit_ignore_exceptions(
|
289
|
+
name=f"{metrics_name}.{exception_name}", value=1
|
290
|
+
)
|
291
|
+
_emit_ignore_exceptions(name=metrics_name, value=1)
|
292
|
+
raise ex
|
293
|
+
|
294
|
+
return wrapper
|
295
|
+
|
296
|
+
if original_func:
|
297
|
+
return _decorate(original_func)
|
298
|
+
|
299
|
+
return _decorate
|
300
|
+
|
301
|
+
|
302
|
+
def metrics(original_func=None, prefix: Optional[str] = None):
|
303
|
+
"""
|
304
|
+
A decorator that emits all metrics for a function.
|
305
|
+
"""
|
306
|
+
|
307
|
+
def _decorate(func):
|
308
|
+
name = prefix or func.__name__
|
309
|
+
failure_metrics_name = f"{name}_failure_count"
|
310
|
+
success_metrics_name = f"{name}_success_count"
|
311
|
+
latency_metrics_name = f"{name}_time"
|
312
|
+
|
313
|
+
@functools.wraps(func)
|
314
|
+
def wrapper(*args, **kwargs):
|
315
|
+
start = time.monotonic()
|
316
|
+
try:
|
317
|
+
result = func(*args, **kwargs)
|
318
|
+
_emit_ignore_exceptions(name=success_metrics_name, value=1)
|
319
|
+
return result
|
320
|
+
except BaseException as ex:
|
321
|
+
exception_name = type(ex).__name__
|
322
|
+
_emit_ignore_exceptions(
|
323
|
+
name=f"{failure_metrics_name}.{exception_name}", value=1
|
324
|
+
)
|
325
|
+
_emit_ignore_exceptions(name=failure_metrics_name, value=1)
|
326
|
+
raise ex
|
327
|
+
finally:
|
328
|
+
end = time.monotonic()
|
329
|
+
_emit_ignore_exceptions(name=latency_metrics_name, value=end - start)
|
330
|
+
|
331
|
+
return wrapper
|
332
|
+
|
333
|
+
if original_func:
|
334
|
+
return _decorate(original_func)
|
335
|
+
|
336
|
+
return _decorate
|
@@ -1,11 +1,11 @@
|
|
1
|
-
deltacat/__init__.py,sha256=
|
1
|
+
deltacat/__init__.py,sha256=1qjk3DyuuYXNfuzWlEqKVNNPDDpBkV8gwsSgJiMCq1s,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
|
@@ -50,14 +50,14 @@ deltacat/compute/compactor/utils/round_completion_file.py,sha256=DmZfHeAXlQn0DDd
|
|
50
50
|
deltacat/compute/compactor/utils/sort_key.py,sha256=oK6otg-CSsma6zlGPaKg-KNEvcZRG2NqBlCw1X3_FBc,2397
|
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
|
-
deltacat/compute/compactor_v2/compaction_session.py,sha256=
|
54
|
-
deltacat/compute/compactor_v2/constants.py,sha256=
|
53
|
+
deltacat/compute/compactor_v2/compaction_session.py,sha256=lXLoOiVjvsG-VEFD9afeKMjcExmOMg9I4-JTrTCVSo4,25576
|
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=kznfueuChhTG3kKnfbVOtxks2uwIWrXlDjY3b2qQaNc,19429
|
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=PZeBV4rhlA_7WjucXDe2DyZj5A8pYCQsYoKDwgyqb7E,9937
|
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.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
207
|
+
deltacat-1.1.2.dist-info/METADATA,sha256=r16g6Lc01Nlsb0Dr5DPona2hiOsWLIcscEXkQyfpD3o,1780
|
208
|
+
deltacat-1.1.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
209
|
+
deltacat-1.1.2.dist-info/top_level.txt,sha256=RWdIcid4Bv2i2ozLVh-70kJpyB61xEKXod9XXGpiono,9
|
210
|
+
deltacat-1.1.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|