kafka-python 3.0.0__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.
- kafka/__init__.py +34 -0
- kafka/__main__.py +5 -0
- kafka/admin/__init__.py +29 -0
- kafka/admin/__main__.py +5 -0
- kafka/admin/_acls.py +355 -0
- kafka/admin/_cluster.py +359 -0
- kafka/admin/_configs.py +479 -0
- kafka/admin/_groups.py +754 -0
- kafka/admin/_partitions.py +595 -0
- kafka/admin/_topics.py +281 -0
- kafka/admin/_transactions.py +450 -0
- kafka/admin/_users.py +194 -0
- kafka/admin/client.py +373 -0
- kafka/benchmarks/__init__.py +0 -0
- kafka/benchmarks/consumer_performance.py +138 -0
- kafka/benchmarks/load_example.py +109 -0
- kafka/benchmarks/producer_encode_path.py +201 -0
- kafka/benchmarks/producer_performance.py +161 -0
- kafka/benchmarks/profile_protocol.py +138 -0
- kafka/benchmarks/protocol_old_vs_new.py +447 -0
- kafka/benchmarks/record_batch_compose.py +77 -0
- kafka/benchmarks/record_batch_read.py +82 -0
- kafka/benchmarks/varint_speed.py +426 -0
- kafka/cli/__init__.py +36 -0
- kafka/cli/admin/__init__.py +117 -0
- kafka/cli/admin/acls/__init__.py +9 -0
- kafka/cli/admin/acls/common.py +76 -0
- kafka/cli/admin/acls/create.py +19 -0
- kafka/cli/admin/acls/delete.py +23 -0
- kafka/cli/admin/acls/describe.py +16 -0
- kafka/cli/admin/cluster/__init__.py +14 -0
- kafka/cli/admin/cluster/describe.py +11 -0
- kafka/cli/admin/cluster/describe_quorum.py +11 -0
- kafka/cli/admin/cluster/features.py +52 -0
- kafka/cli/admin/cluster/log_dirs.py +43 -0
- kafka/cli/admin/cluster/versions.py +33 -0
- kafka/cli/admin/configs/__init__.py +10 -0
- kafka/cli/admin/configs/alter.py +43 -0
- kafka/cli/admin/configs/common.py +17 -0
- kafka/cli/admin/configs/describe.py +30 -0
- kafka/cli/admin/configs/list.py +16 -0
- kafka/cli/admin/configs/reset.py +20 -0
- kafka/cli/admin/groups/__init__.py +16 -0
- kafka/cli/admin/groups/alter_offsets.py +30 -0
- kafka/cli/admin/groups/delete.py +11 -0
- kafka/cli/admin/groups/delete_offsets.py +29 -0
- kafka/cli/admin/groups/describe.py +11 -0
- kafka/cli/admin/groups/list.py +28 -0
- kafka/cli/admin/groups/list_offsets.py +29 -0
- kafka/cli/admin/groups/remove_members.py +40 -0
- kafka/cli/admin/groups/reset_offsets.py +139 -0
- kafka/cli/admin/partitions/__init__.py +21 -0
- kafka/cli/admin/partitions/alter_reassignments.py +37 -0
- kafka/cli/admin/partitions/create.py +27 -0
- kafka/cli/admin/partitions/delete_records.py +31 -0
- kafka/cli/admin/partitions/describe.py +36 -0
- kafka/cli/admin/partitions/elect_leaders.py +53 -0
- kafka/cli/admin/partitions/list_offsets.py +88 -0
- kafka/cli/admin/partitions/list_reassignments.py +35 -0
- kafka/cli/admin/topics/__init__.py +10 -0
- kafka/cli/admin/topics/create.py +13 -0
- kafka/cli/admin/topics/delete.py +19 -0
- kafka/cli/admin/topics/describe.py +18 -0
- kafka/cli/admin/topics/list.py +11 -0
- kafka/cli/admin/transactions/__init__.py +17 -0
- kafka/cli/admin/transactions/abort.py +38 -0
- kafka/cli/admin/transactions/describe.py +24 -0
- kafka/cli/admin/transactions/describe_producers.py +29 -0
- kafka/cli/admin/transactions/find_hanging.py +26 -0
- kafka/cli/admin/transactions/list.py +37 -0
- kafka/cli/admin/users/__init__.py +8 -0
- kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
- kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
- kafka/cli/common.py +95 -0
- kafka/cli/consumer/__init__.py +63 -0
- kafka/cli/producer/__init__.py +57 -0
- kafka/cluster.py +824 -0
- kafka/codec.py +325 -0
- kafka/consumer/__init__.py +5 -0
- kafka/consumer/__main__.py +5 -0
- kafka/consumer/fetcher.py +2012 -0
- kafka/consumer/group.py +1347 -0
- kafka/consumer/subscription_state.py +897 -0
- kafka/coordinator/__init__.py +0 -0
- kafka/coordinator/assignors/__init__.py +0 -0
- kafka/coordinator/assignors/abstract.py +90 -0
- kafka/coordinator/assignors/cooperative_sticky.py +167 -0
- kafka/coordinator/assignors/range.py +81 -0
- kafka/coordinator/assignors/roundrobin.py +101 -0
- kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
- kafka/coordinator/assignors/sticky/__init__.py +0 -0
- kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
- kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
- kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
- kafka/coordinator/assignors/sticky/user_data.py +8 -0
- kafka/coordinator/base.py +1215 -0
- kafka/coordinator/consumer.py +1224 -0
- kafka/coordinator/heartbeat.py +82 -0
- kafka/coordinator/subscription.py +34 -0
- kafka/errors.py +1004 -0
- kafka/future.py +166 -0
- kafka/metrics/__init__.py +13 -0
- kafka/metrics/compound_stat.py +33 -0
- kafka/metrics/dict_reporter.py +81 -0
- kafka/metrics/kafka_metric.py +36 -0
- kafka/metrics/measurable.py +27 -0
- kafka/metrics/measurable_stat.py +13 -0
- kafka/metrics/metric_config.py +33 -0
- kafka/metrics/metric_name.py +105 -0
- kafka/metrics/metrics.py +261 -0
- kafka/metrics/metrics_reporter.py +53 -0
- kafka/metrics/quota.py +41 -0
- kafka/metrics/stat.py +19 -0
- kafka/metrics/stats/__init__.py +15 -0
- kafka/metrics/stats/avg.py +24 -0
- kafka/metrics/stats/count.py +17 -0
- kafka/metrics/stats/histogram.py +99 -0
- kafka/metrics/stats/max_stat.py +17 -0
- kafka/metrics/stats/min_stat.py +19 -0
- kafka/metrics/stats/percentile.py +14 -0
- kafka/metrics/stats/percentiles.py +75 -0
- kafka/metrics/stats/rate.py +118 -0
- kafka/metrics/stats/sampled_stat.py +99 -0
- kafka/metrics/stats/sensor.py +136 -0
- kafka/metrics/stats/total.py +15 -0
- kafka/net/__init__.py +19 -0
- kafka/net/compat.py +165 -0
- kafka/net/connection.py +593 -0
- kafka/net/http_connect.py +144 -0
- kafka/net/inet.py +122 -0
- kafka/net/manager.py +451 -0
- kafka/net/metrics.py +149 -0
- kafka/net/sasl/__init__.py +32 -0
- kafka/net/sasl/abc.py +28 -0
- kafka/net/sasl/gssapi.py +95 -0
- kafka/net/sasl/msk.py +245 -0
- kafka/net/sasl/oauth.py +98 -0
- kafka/net/sasl/plain.py +42 -0
- kafka/net/sasl/scram.py +135 -0
- kafka/net/sasl/sspi.py +111 -0
- kafka/net/selector.py +644 -0
- kafka/net/socks5.py +262 -0
- kafka/net/transport.py +415 -0
- kafka/net/wakeup_notifier.py +72 -0
- kafka/partitioner/__init__.py +8 -0
- kafka/partitioner/abc.py +8 -0
- kafka/partitioner/default.py +89 -0
- kafka/partitioner/sticky.py +109 -0
- kafka/producer/__init__.py +5 -0
- kafka/producer/__main__.py +5 -0
- kafka/producer/future.py +101 -0
- kafka/producer/kafka.py +1123 -0
- kafka/producer/producer_batch.py +192 -0
- kafka/producer/record_accumulator.py +647 -0
- kafka/producer/sender.py +884 -0
- kafka/producer/transaction_manager.py +1326 -0
- kafka/protocol/__init__.py +0 -0
- kafka/protocol/admin/__init__.py +29 -0
- kafka/protocol/admin/acl.py +83 -0
- kafka/protocol/admin/acl.pyi +375 -0
- kafka/protocol/admin/client_quotas.py +14 -0
- kafka/protocol/admin/client_quotas.pyi +265 -0
- kafka/protocol/admin/cluster.py +31 -0
- kafka/protocol/admin/cluster.pyi +620 -0
- kafka/protocol/admin/configs.py +22 -0
- kafka/protocol/admin/configs.pyi +437 -0
- kafka/protocol/admin/groups.py +24 -0
- kafka/protocol/admin/groups.pyi +261 -0
- kafka/protocol/admin/topics.py +53 -0
- kafka/protocol/admin/topics.pyi +982 -0
- kafka/protocol/admin/transactions.py +18 -0
- kafka/protocol/admin/transactions.pyi +311 -0
- kafka/protocol/admin/users.py +14 -0
- kafka/protocol/admin/users.pyi +223 -0
- kafka/protocol/api_data.py +125 -0
- kafka/protocol/api_header.py +55 -0
- kafka/protocol/api_key.py +97 -0
- kafka/protocol/api_message.py +277 -0
- kafka/protocol/broker_version_data.py +246 -0
- kafka/protocol/consumer/__init__.py +13 -0
- kafka/protocol/consumer/fetch.py +16 -0
- kafka/protocol/consumer/fetch.pyi +298 -0
- kafka/protocol/consumer/group.py +38 -0
- kafka/protocol/consumer/group.pyi +824 -0
- kafka/protocol/consumer/metadata.py +30 -0
- kafka/protocol/consumer/metadata.pyi +89 -0
- kafka/protocol/consumer/offsets.py +75 -0
- kafka/protocol/consumer/offsets.pyi +288 -0
- kafka/protocol/data_container.py +166 -0
- kafka/protocol/frame.py +30 -0
- kafka/protocol/generate_stubs.py +468 -0
- kafka/protocol/metadata/__init__.py +10 -0
- kafka/protocol/metadata/api_versions.py +41 -0
- kafka/protocol/metadata/api_versions.pyi +128 -0
- kafka/protocol/metadata/find_coordinator.py +19 -0
- kafka/protocol/metadata/find_coordinator.pyi +105 -0
- kafka/protocol/metadata/metadata.py +34 -0
- kafka/protocol/metadata/metadata.pyi +160 -0
- kafka/protocol/old/__init__.py +0 -0
- kafka/protocol/old/abstract.py +17 -0
- kafka/protocol/old/add_offsets_to_txn.py +54 -0
- kafka/protocol/old/add_partitions_to_txn.py +71 -0
- kafka/protocol/old/admin.py +1086 -0
- kafka/protocol/old/api.py +205 -0
- kafka/protocol/old/api_versions.py +133 -0
- kafka/protocol/old/commit.py +355 -0
- kafka/protocol/old/consumer_protocol.py +36 -0
- kafka/protocol/old/end_txn.py +53 -0
- kafka/protocol/old/fetch.py +408 -0
- kafka/protocol/old/find_coordinator.py +72 -0
- kafka/protocol/old/group.py +451 -0
- kafka/protocol/old/init_producer_id.py +42 -0
- kafka/protocol/old/list_offsets.py +186 -0
- kafka/protocol/old/metadata.py +290 -0
- kafka/protocol/old/offset_for_leader_epoch.py +133 -0
- kafka/protocol/old/produce.py +247 -0
- kafka/protocol/old/sasl_authenticate.py +38 -0
- kafka/protocol/old/sasl_handshake.py +39 -0
- kafka/protocol/old/struct.py +87 -0
- kafka/protocol/old/txn_offset_commit.py +73 -0
- kafka/protocol/old/types.py +440 -0
- kafka/protocol/parser.py +191 -0
- kafka/protocol/producer/__init__.py +7 -0
- kafka/protocol/producer/produce.py +17 -0
- kafka/protocol/producer/produce.pyi +197 -0
- kafka/protocol/producer/transaction.py +30 -0
- kafka/protocol/producer/transaction.pyi +663 -0
- kafka/protocol/sasl.py +52 -0
- kafka/protocol/sasl.pyi +126 -0
- kafka/protocol/schemas/__init__.py +7 -0
- kafka/protocol/schemas/fields/__init__.py +7 -0
- kafka/protocol/schemas/fields/array.py +127 -0
- kafka/protocol/schemas/fields/base.py +156 -0
- kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
- kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
- kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
- kafka/protocol/schemas/fields/codecs/types.py +505 -0
- kafka/protocol/schemas/fields/codegen.py +40 -0
- kafka/protocol/schemas/fields/simple.py +127 -0
- kafka/protocol/schemas/fields/struct.py +357 -0
- kafka/protocol/schemas/fields/struct_array.py +142 -0
- kafka/protocol/schemas/load_json.py +42 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
- kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
- kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
- kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
- kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
- kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
- kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
- kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
- kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
- kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
- kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
- kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
- kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
- kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
- kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
- kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
- kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
- kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
- kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
- kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
- kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
- kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
- kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
- kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
- kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
- kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
- kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
- kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
- kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
- kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
- kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
- kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
- kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
- kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
- kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
- kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
- kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
- kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
- kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
- kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
- kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
- kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
- kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
- kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
- kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
- kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
- kafka/protocol/schemas/resources/FetchRequest.json +125 -0
- kafka/protocol/schemas/resources/FetchResponse.json +124 -0
- kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
- kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
- kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
- kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
- kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
- kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
- kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
- kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
- kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
- kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
- kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
- kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
- kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
- kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
- kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
- kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
- kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
- kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
- kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
- kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
- kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
- kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
- kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
- kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
- kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
- kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
- kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
- kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
- kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
- kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
- kafka/protocol/schemas/resources/RequestHeader.json +44 -0
- kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
- kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
- kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
- kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
- kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
- kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
- kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
- kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
- kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
- kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
- kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
- kafka/protocol/schemas/resources/__init__.py +0 -0
- kafka/record/__init__.py +3 -0
- kafka/record/_crc32c.py +161 -0
- kafka/record/abc.py +144 -0
- kafka/record/default_records.py +782 -0
- kafka/record/legacy_records.py +587 -0
- kafka/record/memory_records.py +255 -0
- kafka/record/util.py +135 -0
- kafka/serializer/__init__.py +4 -0
- kafka/serializer/abstract.py +20 -0
- kafka/serializer/default.py +16 -0
- kafka/serializer/json.py +17 -0
- kafka/serializer/wrapper.py +21 -0
- kafka/structs.py +69 -0
- kafka/util.py +159 -0
- kafka/vendor/__init__.py +0 -0
- kafka/version.py +1 -0
- kafka_python-3.0.0.dist-info/METADATA +319 -0
- kafka_python-3.0.0.dist-info/RECORD +373 -0
- kafka_python-3.0.0.dist-info/WHEEL +5 -0
- kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
- kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
- kafka_python-3.0.0.dist-info/top_level.txt +1 -0
kafka/metrics/metrics.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
from kafka.metrics import AnonMeasurable, KafkaMetric, MetricConfig, MetricName
|
|
7
|
+
from kafka.metrics.stats import Sensor
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Metrics:
|
|
13
|
+
"""
|
|
14
|
+
A registry of sensors and metrics.
|
|
15
|
+
|
|
16
|
+
A metric is a named, numerical measurement. A sensor is a handle to
|
|
17
|
+
record numerical measurements as they occur. Each Sensor has zero or
|
|
18
|
+
more associated metrics. For example a Sensor might represent message
|
|
19
|
+
sizes and we might associate with this sensor a metric for the average,
|
|
20
|
+
maximum, or other statistics computed off the sequence of message sizes
|
|
21
|
+
that are recorded by the sensor.
|
|
22
|
+
|
|
23
|
+
Usage looks something like this:
|
|
24
|
+
# set up metrics:
|
|
25
|
+
metrics = Metrics() # the global repository of metrics and sensors
|
|
26
|
+
sensor = metrics.sensor('message-sizes')
|
|
27
|
+
metric_name = MetricName('message-size-avg', 'producer-metrics')
|
|
28
|
+
sensor.add(metric_name, Avg())
|
|
29
|
+
metric_name = MetricName('message-size-max', 'producer-metrics')
|
|
30
|
+
sensor.add(metric_name, Max())
|
|
31
|
+
|
|
32
|
+
# as messages are sent we record the sizes
|
|
33
|
+
sensor.record(message_size);
|
|
34
|
+
"""
|
|
35
|
+
def __init__(self, default_config=None, reporters=None,
|
|
36
|
+
enable_expiration=False):
|
|
37
|
+
"""
|
|
38
|
+
Create a metrics repository with a default config, given metric
|
|
39
|
+
reporters and the ability to expire eligible sensors
|
|
40
|
+
|
|
41
|
+
Arguments:
|
|
42
|
+
default_config (MetricConfig, optional): The default config
|
|
43
|
+
reporters (list of AbstractMetricsReporter, optional):
|
|
44
|
+
The metrics reporters
|
|
45
|
+
enable_expiration (bool, optional): true if the metrics instance
|
|
46
|
+
can garbage collect inactive sensors, false otherwise
|
|
47
|
+
"""
|
|
48
|
+
self._lock = threading.RLock()
|
|
49
|
+
self._config = default_config or MetricConfig()
|
|
50
|
+
self._sensors = {}
|
|
51
|
+
self._metrics = {}
|
|
52
|
+
self._children_sensors = {}
|
|
53
|
+
self._reporters = reporters or []
|
|
54
|
+
for reporter in self._reporters:
|
|
55
|
+
reporter.init([])
|
|
56
|
+
self._closed = False
|
|
57
|
+
|
|
58
|
+
if enable_expiration:
|
|
59
|
+
def expire_loop():
|
|
60
|
+
while not self._closed:
|
|
61
|
+
# delay 30 seconds
|
|
62
|
+
time.sleep(30)
|
|
63
|
+
self.ExpireSensorTask.run(self)
|
|
64
|
+
metrics_scheduler = threading.Thread(target=expire_loop)
|
|
65
|
+
# Creating a daemon thread to not block shutdown
|
|
66
|
+
metrics_scheduler.daemon = True
|
|
67
|
+
metrics_scheduler.start()
|
|
68
|
+
|
|
69
|
+
self.add_metric(self.metric_name('count', 'kafka-metrics-count',
|
|
70
|
+
'total number of registered metrics'),
|
|
71
|
+
AnonMeasurable(lambda config, now: len(self._metrics)))
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def config(self):
|
|
75
|
+
return self._config
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def metrics(self):
|
|
79
|
+
"""
|
|
80
|
+
Get all the metrics currently maintained and indexed by metricName
|
|
81
|
+
"""
|
|
82
|
+
return self._metrics
|
|
83
|
+
|
|
84
|
+
def metric_name(self, name, group, description='', tags=None):
|
|
85
|
+
"""
|
|
86
|
+
Create a MetricName with the given name, group, description and tags,
|
|
87
|
+
plus default tags specified in the metric configuration.
|
|
88
|
+
Tag in tags takes precedence if the same tag key is specified in
|
|
89
|
+
the default metric configuration.
|
|
90
|
+
|
|
91
|
+
Arguments:
|
|
92
|
+
name (str): The name of the metric
|
|
93
|
+
group (str): logical group name of the metrics to which this
|
|
94
|
+
metric belongs
|
|
95
|
+
description (str, optional): A human-readable description to
|
|
96
|
+
include in the metric
|
|
97
|
+
tags (dict, optionals): additional key/value attributes of
|
|
98
|
+
the metric
|
|
99
|
+
"""
|
|
100
|
+
combined_tags = dict(self.config.tags)
|
|
101
|
+
combined_tags.update(tags or {})
|
|
102
|
+
return MetricName(name, group, description, combined_tags)
|
|
103
|
+
|
|
104
|
+
def get_sensor(self, name):
|
|
105
|
+
"""
|
|
106
|
+
Get the sensor with the given name if it exists
|
|
107
|
+
|
|
108
|
+
Arguments:
|
|
109
|
+
name (str): The name of the sensor
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Sensor: The sensor or None if no such sensor exists
|
|
113
|
+
"""
|
|
114
|
+
if not name:
|
|
115
|
+
raise ValueError('name must be non-empty')
|
|
116
|
+
return self._sensors.get(name, None)
|
|
117
|
+
|
|
118
|
+
def sensor(self, name, config=None,
|
|
119
|
+
inactive_sensor_expiration_time_seconds=sys.maxsize,
|
|
120
|
+
parents=None):
|
|
121
|
+
"""
|
|
122
|
+
Get or create a sensor with the given unique name and zero or
|
|
123
|
+
more parent sensors. All parent sensors will receive every value
|
|
124
|
+
recorded with this sensor.
|
|
125
|
+
|
|
126
|
+
Arguments:
|
|
127
|
+
name (str): The name of the sensor
|
|
128
|
+
config (MetricConfig, optional): A default configuration to use
|
|
129
|
+
for this sensor for metrics that don't have their own config
|
|
130
|
+
inactive_sensor_expiration_time_seconds (int, optional):
|
|
131
|
+
If no value if recorded on the Sensor for this duration of
|
|
132
|
+
time, it is eligible for removal
|
|
133
|
+
parents (list of Sensor): The parent sensors
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Sensor: The sensor that is created
|
|
137
|
+
"""
|
|
138
|
+
sensor = self.get_sensor(name)
|
|
139
|
+
if sensor:
|
|
140
|
+
return sensor
|
|
141
|
+
|
|
142
|
+
with self._lock:
|
|
143
|
+
sensor = self.get_sensor(name)
|
|
144
|
+
if not sensor:
|
|
145
|
+
sensor = Sensor(self, name, parents, config or self.config,
|
|
146
|
+
inactive_sensor_expiration_time_seconds)
|
|
147
|
+
self._sensors[name] = sensor
|
|
148
|
+
if parents:
|
|
149
|
+
for parent in parents:
|
|
150
|
+
children = self._children_sensors.get(parent)
|
|
151
|
+
if not children:
|
|
152
|
+
children = []
|
|
153
|
+
self._children_sensors[parent] = children
|
|
154
|
+
children.append(sensor)
|
|
155
|
+
logger.debug('Added sensor with name %s', name)
|
|
156
|
+
return sensor
|
|
157
|
+
|
|
158
|
+
def remove_sensor(self, name):
|
|
159
|
+
"""
|
|
160
|
+
Remove a sensor (if it exists), associated metrics and its children.
|
|
161
|
+
|
|
162
|
+
Arguments:
|
|
163
|
+
name (str): The name of the sensor to be removed
|
|
164
|
+
"""
|
|
165
|
+
with self._lock:
|
|
166
|
+
sensor = self._sensors.get(name)
|
|
167
|
+
if sensor:
|
|
168
|
+
child_sensors = None
|
|
169
|
+
with sensor._lock:
|
|
170
|
+
val = self._sensors.pop(name, None)
|
|
171
|
+
if val and val == sensor:
|
|
172
|
+
for metric in sensor.metrics:
|
|
173
|
+
self.remove_metric(metric.metric_name)
|
|
174
|
+
logger.debug('Removed sensor with name %s', name)
|
|
175
|
+
child_sensors = self._children_sensors.pop(sensor, None)
|
|
176
|
+
if child_sensors:
|
|
177
|
+
for child_sensor in child_sensors:
|
|
178
|
+
self.remove_sensor(child_sensor.name)
|
|
179
|
+
|
|
180
|
+
def add_metric(self, metric_name, measurable, config=None):
|
|
181
|
+
"""
|
|
182
|
+
Add a metric to monitor an object that implements measurable.
|
|
183
|
+
This metric won't be associated with any sensor.
|
|
184
|
+
This is a way to expose existing values as metrics.
|
|
185
|
+
|
|
186
|
+
Arguments:
|
|
187
|
+
metricName (MetricName): The name of the metric
|
|
188
|
+
measurable (AbstractMeasurable): The measurable that will be
|
|
189
|
+
measured by this metric
|
|
190
|
+
config (MetricConfig, optional): The configuration to use when
|
|
191
|
+
measuring this measurable
|
|
192
|
+
"""
|
|
193
|
+
# NOTE there was a lock here, but i don't think it's needed
|
|
194
|
+
metric = KafkaMetric(metric_name, measurable, config or self.config)
|
|
195
|
+
self.register_metric(metric)
|
|
196
|
+
|
|
197
|
+
def remove_metric(self, metric_name):
|
|
198
|
+
"""
|
|
199
|
+
Remove a metric if it exists and return it. Return None otherwise.
|
|
200
|
+
If a metric is removed, `metric_removal` will be invoked
|
|
201
|
+
for each reporter.
|
|
202
|
+
|
|
203
|
+
Arguments:
|
|
204
|
+
metric_name (MetricName): The name of the metric
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
KafkaMetric: the removed `KafkaMetric` or None if no such
|
|
208
|
+
metric exists
|
|
209
|
+
"""
|
|
210
|
+
with self._lock:
|
|
211
|
+
metric = self._metrics.pop(metric_name, None)
|
|
212
|
+
if metric:
|
|
213
|
+
for reporter in self._reporters:
|
|
214
|
+
reporter.metric_removal(metric)
|
|
215
|
+
return metric
|
|
216
|
+
|
|
217
|
+
def add_reporter(self, reporter):
|
|
218
|
+
"""Add a MetricReporter"""
|
|
219
|
+
with self._lock:
|
|
220
|
+
reporter.init(list(self.metrics.values()))
|
|
221
|
+
self._reporters.append(reporter)
|
|
222
|
+
|
|
223
|
+
def register_metric(self, metric):
|
|
224
|
+
with self._lock:
|
|
225
|
+
if metric.metric_name in self.metrics:
|
|
226
|
+
raise ValueError('A metric named "%s" already exists, cannot'
|
|
227
|
+
' register another one.' % (metric.metric_name,))
|
|
228
|
+
self.metrics[metric.metric_name] = metric
|
|
229
|
+
for reporter in self._reporters:
|
|
230
|
+
reporter.metric_change(metric)
|
|
231
|
+
|
|
232
|
+
class ExpireSensorTask:
|
|
233
|
+
"""
|
|
234
|
+
This iterates over every Sensor and triggers a remove_sensor
|
|
235
|
+
if it has expired. Package private for testing
|
|
236
|
+
"""
|
|
237
|
+
@staticmethod
|
|
238
|
+
def run(metrics):
|
|
239
|
+
items = list(metrics._sensors.items())
|
|
240
|
+
for name, sensor in items:
|
|
241
|
+
# remove_sensor also locks the sensor object. This is fine
|
|
242
|
+
# because synchronized is reentrant. There is however a minor
|
|
243
|
+
# race condition here. Assume we have a parent sensor P and
|
|
244
|
+
# child sensor C. Calling record on C would cause a record on
|
|
245
|
+
# P as well. So expiration time for P == expiration time for C.
|
|
246
|
+
# If the record on P happens via C just after P is removed,
|
|
247
|
+
# that will cause C to also get removed. Since the expiration
|
|
248
|
+
# time is typically high it is not expected to be a significant
|
|
249
|
+
# concern and thus not necessary to optimize
|
|
250
|
+
with sensor._lock:
|
|
251
|
+
if sensor.has_expired():
|
|
252
|
+
logger.debug('Removing expired sensor %s', name)
|
|
253
|
+
metrics.remove_sensor(name)
|
|
254
|
+
|
|
255
|
+
def close(self):
|
|
256
|
+
"""Close this metrics repository."""
|
|
257
|
+
for reporter in self._reporters:
|
|
258
|
+
reporter.close()
|
|
259
|
+
|
|
260
|
+
self._metrics.clear()
|
|
261
|
+
self._closed = True
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AbstractMetricsReporter(ABC):
|
|
5
|
+
"""
|
|
6
|
+
An abstract class to allow things to listen as new metrics
|
|
7
|
+
are created so they can be reported.
|
|
8
|
+
"""
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def init(self, metrics):
|
|
11
|
+
"""
|
|
12
|
+
This is called when the reporter is first registered
|
|
13
|
+
to initially register all existing metrics
|
|
14
|
+
|
|
15
|
+
Arguments:
|
|
16
|
+
metrics (list of KafkaMetric): All currently existing metrics
|
|
17
|
+
"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def metric_change(self, metric):
|
|
22
|
+
"""
|
|
23
|
+
This is called whenever a metric is updated or added
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
metric (KafkaMetric)
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def metric_removal(self, metric):
|
|
32
|
+
"""
|
|
33
|
+
This is called whenever a metric is removed
|
|
34
|
+
|
|
35
|
+
Arguments:
|
|
36
|
+
metric (KafkaMetric)
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def configure(self, configs):
|
|
42
|
+
"""
|
|
43
|
+
Configure this class with the given key-value pairs
|
|
44
|
+
|
|
45
|
+
Arguments:
|
|
46
|
+
configs (dict of {str, ?})
|
|
47
|
+
"""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def close(self):
|
|
52
|
+
"""Called when the metrics repository is closed."""
|
|
53
|
+
pass
|
kafka/metrics/quota.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
class Quota:
|
|
2
|
+
"""An upper or lower bound for metrics"""
|
|
3
|
+
__slots__ = ('_bound', '_upper')
|
|
4
|
+
|
|
5
|
+
def __init__(self, bound, is_upper):
|
|
6
|
+
self._bound = bound
|
|
7
|
+
self._upper = is_upper
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def upper_bound(upper_bound):
|
|
11
|
+
return Quota(upper_bound, True)
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def lower_bound(lower_bound):
|
|
15
|
+
return Quota(lower_bound, False)
|
|
16
|
+
|
|
17
|
+
def is_upper_bound(self):
|
|
18
|
+
return self._upper
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def bound(self):
|
|
22
|
+
return self._bound
|
|
23
|
+
|
|
24
|
+
def is_acceptable(self, value):
|
|
25
|
+
return ((self.is_upper_bound() and value <= self.bound) or
|
|
26
|
+
(not self.is_upper_bound() and value >= self.bound))
|
|
27
|
+
|
|
28
|
+
def __hash__(self):
|
|
29
|
+
prime = 31
|
|
30
|
+
result = prime + self.bound
|
|
31
|
+
return prime * result + self.is_upper_bound()
|
|
32
|
+
|
|
33
|
+
def __eq__(self, other):
|
|
34
|
+
if self is other:
|
|
35
|
+
return True
|
|
36
|
+
return (isinstance(self, type(other)) and
|
|
37
|
+
self.bound == other.bound and
|
|
38
|
+
self.is_upper_bound() == other.is_upper_bound())
|
|
39
|
+
|
|
40
|
+
def __ne__(self, other):
|
|
41
|
+
return not self.__eq__(other)
|
kafka/metrics/stat.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AbstractStat(ABC):
|
|
5
|
+
"""
|
|
6
|
+
An AbstractStat is a quantity such as average, max, etc that is computed
|
|
7
|
+
off the stream of updates to a sensor
|
|
8
|
+
"""
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def record(self, config, value, time_ms):
|
|
11
|
+
"""
|
|
12
|
+
Record the given value
|
|
13
|
+
|
|
14
|
+
Arguments:
|
|
15
|
+
config (MetricConfig): The configuration to use for this metric
|
|
16
|
+
value (float): The value to record
|
|
17
|
+
timeMs (int): The POSIX time in milliseconds this value occurred
|
|
18
|
+
"""
|
|
19
|
+
pass
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from kafka.metrics.stats.avg import Avg
|
|
2
|
+
from kafka.metrics.stats.count import Count
|
|
3
|
+
from kafka.metrics.stats.histogram import Histogram
|
|
4
|
+
from kafka.metrics.stats.max_stat import Max
|
|
5
|
+
from kafka.metrics.stats.min_stat import Min
|
|
6
|
+
from kafka.metrics.stats.percentile import Percentile
|
|
7
|
+
from kafka.metrics.stats.percentiles import Percentiles
|
|
8
|
+
from kafka.metrics.stats.rate import Rate
|
|
9
|
+
from kafka.metrics.stats.sensor import Sensor
|
|
10
|
+
from kafka.metrics.stats.total import Total
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'Avg', 'Count', 'Histogram', 'Max', 'Min', 'Percentile', 'Percentiles',
|
|
14
|
+
'Rate', 'Sensor', 'Total'
|
|
15
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from kafka.metrics.stats.sampled_stat import AbstractSampledStat
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Avg(AbstractSampledStat):
|
|
5
|
+
"""
|
|
6
|
+
An AbstractSampledStat that maintains a simple average over its samples.
|
|
7
|
+
"""
|
|
8
|
+
__slots__ = ('_initial_value', '_samples', '_current')
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__(0.0)
|
|
12
|
+
|
|
13
|
+
def update(self, sample, config, value, now):
|
|
14
|
+
sample.value += value
|
|
15
|
+
|
|
16
|
+
def combine(self, samples, config, now):
|
|
17
|
+
total_sum = 0
|
|
18
|
+
total_count = 0
|
|
19
|
+
for sample in samples:
|
|
20
|
+
total_sum += sample.value
|
|
21
|
+
total_count += sample.event_count
|
|
22
|
+
if not total_count:
|
|
23
|
+
return 0
|
|
24
|
+
return float(total_sum) / total_count
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from kafka.metrics.stats.sampled_stat import AbstractSampledStat
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Count(AbstractSampledStat):
|
|
5
|
+
"""
|
|
6
|
+
An AbstractSampledStat that maintains a simple count of what it has seen.
|
|
7
|
+
"""
|
|
8
|
+
__slots__ = ('_initial_value', '_samples', '_current')
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__(0.0)
|
|
12
|
+
|
|
13
|
+
def update(self, sample, config, value, now):
|
|
14
|
+
sample.value += 1.0
|
|
15
|
+
|
|
16
|
+
def combine(self, samples, config, now):
|
|
17
|
+
return float(sum(sample.value for sample in samples))
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Histogram:
|
|
5
|
+
__slots__ = ('_hist', '_count', '_bin_scheme')
|
|
6
|
+
|
|
7
|
+
def __init__(self, bin_scheme):
|
|
8
|
+
self._hist = [0.0] * bin_scheme.bins
|
|
9
|
+
self._count = 0.0
|
|
10
|
+
self._bin_scheme = bin_scheme
|
|
11
|
+
|
|
12
|
+
def record(self, value):
|
|
13
|
+
self._hist[self._bin_scheme.to_bin(value)] += 1.0
|
|
14
|
+
self._count += 1.0
|
|
15
|
+
|
|
16
|
+
def value(self, quantile):
|
|
17
|
+
if self._count == 0.0:
|
|
18
|
+
return float('NaN')
|
|
19
|
+
_sum = 0.0
|
|
20
|
+
quant = float(quantile)
|
|
21
|
+
for i, value in enumerate(self._hist[:-1]):
|
|
22
|
+
_sum += value
|
|
23
|
+
if _sum / self._count > quant:
|
|
24
|
+
return self._bin_scheme.from_bin(i)
|
|
25
|
+
return float('inf')
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def counts(self):
|
|
29
|
+
return self._hist
|
|
30
|
+
|
|
31
|
+
def clear(self):
|
|
32
|
+
for i in range(len(self._hist)):
|
|
33
|
+
self._hist[i] = 0.0
|
|
34
|
+
self._count = 0
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
values = ['%.10f:%.0f' % (self._bin_scheme.from_bin(i), value) for
|
|
38
|
+
i, value in enumerate(self._hist[:-1])]
|
|
39
|
+
values.append('%s:%s' % (float('inf'), self._hist[-1]))
|
|
40
|
+
return '{%s}' % ','.join(values)
|
|
41
|
+
|
|
42
|
+
class ConstantBinScheme:
|
|
43
|
+
__slots__ = ('_min', '_max', '_bins', '_bucket_width')
|
|
44
|
+
|
|
45
|
+
def __init__(self, bins, min_val, max_val):
|
|
46
|
+
if bins < 2:
|
|
47
|
+
raise ValueError('Must have at least 2 bins.')
|
|
48
|
+
self._min = float(min_val)
|
|
49
|
+
self._max = float(max_val)
|
|
50
|
+
self._bins = int(bins)
|
|
51
|
+
self._bucket_width = (max_val - min_val) / (bins - 2)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def bins(self):
|
|
55
|
+
return self._bins
|
|
56
|
+
|
|
57
|
+
def from_bin(self, b):
|
|
58
|
+
if b == 0:
|
|
59
|
+
return float('-inf')
|
|
60
|
+
elif b == self._bins - 1:
|
|
61
|
+
return float('inf')
|
|
62
|
+
else:
|
|
63
|
+
return self._min + (b - 1) * self._bucket_width
|
|
64
|
+
|
|
65
|
+
def to_bin(self, x):
|
|
66
|
+
if x < self._min:
|
|
67
|
+
return 0
|
|
68
|
+
elif x > self._max:
|
|
69
|
+
return self._bins - 1
|
|
70
|
+
else:
|
|
71
|
+
return int(((x - self._min) / self._bucket_width) + 1)
|
|
72
|
+
|
|
73
|
+
class LinearBinScheme:
|
|
74
|
+
__slots__ = ('_bins', '_max', '_scale')
|
|
75
|
+
|
|
76
|
+
def __init__(self, num_bins, max_val):
|
|
77
|
+
self._bins = num_bins
|
|
78
|
+
self._max = max_val
|
|
79
|
+
self._scale = max_val / (num_bins * (num_bins - 1) / 2)
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def bins(self):
|
|
83
|
+
return self._bins
|
|
84
|
+
|
|
85
|
+
def from_bin(self, b):
|
|
86
|
+
if b == self._bins - 1:
|
|
87
|
+
return float('inf')
|
|
88
|
+
else:
|
|
89
|
+
unscaled = (b * (b + 1.0)) / 2.0
|
|
90
|
+
return unscaled * self._scale
|
|
91
|
+
|
|
92
|
+
def to_bin(self, x):
|
|
93
|
+
if x < 0.0:
|
|
94
|
+
raise ValueError('Values less than 0.0 not accepted.')
|
|
95
|
+
elif x > self._max:
|
|
96
|
+
return self._bins - 1
|
|
97
|
+
else:
|
|
98
|
+
scaled = x / self._scale
|
|
99
|
+
return int(-0.5 + math.sqrt(2.0 * scaled + 0.25))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from kafka.metrics.stats.sampled_stat import AbstractSampledStat
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Max(AbstractSampledStat):
|
|
5
|
+
"""An AbstractSampledStat that gives the max over its samples."""
|
|
6
|
+
__slots__ = ('_initial_value', '_samples', '_current')
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__(float('-inf'))
|
|
10
|
+
|
|
11
|
+
def update(self, sample, config, value, now):
|
|
12
|
+
sample.value = max(sample.value, value)
|
|
13
|
+
|
|
14
|
+
def combine(self, samples, config, now):
|
|
15
|
+
if not samples:
|
|
16
|
+
return float('-inf')
|
|
17
|
+
return float(max(sample.value for sample in samples))
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from kafka.metrics.stats.sampled_stat import AbstractSampledStat
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Min(AbstractSampledStat):
|
|
7
|
+
"""An AbstractSampledStat that gives the min over its samples."""
|
|
8
|
+
__slots__ = ('_initial_value', '_samples', '_current')
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
super().__init__(float(sys.maxsize))
|
|
12
|
+
|
|
13
|
+
def update(self, sample, config, value, now):
|
|
14
|
+
sample.value = min(sample.value, value)
|
|
15
|
+
|
|
16
|
+
def combine(self, samples, config, now):
|
|
17
|
+
if not samples:
|
|
18
|
+
return float(sys.maxsize)
|
|
19
|
+
return float(min(sample.value for sample in samples))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Percentile:
|
|
2
|
+
__slots__ = ('_metric_name', '_percentile')
|
|
3
|
+
|
|
4
|
+
def __init__(self, metric_name, percentile):
|
|
5
|
+
self._metric_name = metric_name
|
|
6
|
+
self._percentile = float(percentile)
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def name(self):
|
|
10
|
+
return self._metric_name
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def percentile(self):
|
|
14
|
+
return self._percentile
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from kafka.metrics import AnonMeasurable, NamedMeasurable
|
|
2
|
+
from kafka.metrics.compound_stat import AbstractCompoundStat
|
|
3
|
+
from kafka.metrics.stats import Histogram
|
|
4
|
+
from kafka.metrics.stats.sampled_stat import AbstractSampledStat
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BucketSizing:
|
|
8
|
+
CONSTANT = 0
|
|
9
|
+
LINEAR = 1
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Percentiles(AbstractSampledStat, AbstractCompoundStat):
|
|
13
|
+
"""A compound stat that reports one or more percentiles"""
|
|
14
|
+
__slots__ = ('_initial_value', '_samples', '_current',
|
|
15
|
+
'_percentiles', '_buckets', '_bin_scheme')
|
|
16
|
+
|
|
17
|
+
def __init__(self, size_in_bytes, bucketing, max_val, min_val=0.0,
|
|
18
|
+
percentiles=None):
|
|
19
|
+
super().__init__(0.0)
|
|
20
|
+
self._percentiles = percentiles or []
|
|
21
|
+
self._buckets = int(size_in_bytes / 4)
|
|
22
|
+
if bucketing == BucketSizing.CONSTANT:
|
|
23
|
+
self._bin_scheme = Histogram.ConstantBinScheme(self._buckets,
|
|
24
|
+
min_val, max_val)
|
|
25
|
+
elif bucketing == BucketSizing.LINEAR:
|
|
26
|
+
if min_val != 0.0:
|
|
27
|
+
raise ValueError('Linear bucket sizing requires min_val'
|
|
28
|
+
' to be 0.0.')
|
|
29
|
+
self._bin_scheme = Histogram.LinearBinScheme(self._buckets, max_val)
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError('Unknown bucket type: %s' % (bucketing,))
|
|
32
|
+
|
|
33
|
+
def stats(self):
|
|
34
|
+
measurables = []
|
|
35
|
+
|
|
36
|
+
def make_measure_fn(pct):
|
|
37
|
+
return lambda config, now: self.value(config, now,
|
|
38
|
+
pct / 100.0)
|
|
39
|
+
|
|
40
|
+
for percentile in self._percentiles:
|
|
41
|
+
measure_fn = make_measure_fn(percentile.percentile)
|
|
42
|
+
stat = NamedMeasurable(percentile.name, AnonMeasurable(measure_fn))
|
|
43
|
+
measurables.append(stat)
|
|
44
|
+
return measurables
|
|
45
|
+
|
|
46
|
+
def value(self, config, now, quantile):
|
|
47
|
+
self.purge_obsolete_samples(config, now)
|
|
48
|
+
count = sum(sample.event_count for sample in self._samples)
|
|
49
|
+
if count == 0.0:
|
|
50
|
+
return float('NaN')
|
|
51
|
+
sum_val = 0.0
|
|
52
|
+
quant = float(quantile)
|
|
53
|
+
for b in range(self._buckets):
|
|
54
|
+
for sample in self._samples:
|
|
55
|
+
assert type(sample) is self.HistogramSample
|
|
56
|
+
hist = sample.histogram.counts
|
|
57
|
+
sum_val += hist[b]
|
|
58
|
+
if sum_val / count > quant:
|
|
59
|
+
return self._bin_scheme.from_bin(b)
|
|
60
|
+
return float('inf')
|
|
61
|
+
|
|
62
|
+
def combine(self, samples, config, now):
|
|
63
|
+
return self.value(config, now, 0.5)
|
|
64
|
+
|
|
65
|
+
def new_sample(self, time_ms):
|
|
66
|
+
return Percentiles.HistogramSample(self._bin_scheme, time_ms)
|
|
67
|
+
|
|
68
|
+
def update(self, sample, config, value, time_ms):
|
|
69
|
+
assert type(sample) is self.HistogramSample
|
|
70
|
+
sample.histogram.record(value)
|
|
71
|
+
|
|
72
|
+
class HistogramSample(AbstractSampledStat.Sample):
|
|
73
|
+
def __init__(self, scheme, now):
|
|
74
|
+
super().__init__(0.0, now)
|
|
75
|
+
self.histogram = Histogram(scheme)
|