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
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from kafka.metrics.measurable_stat import AbstractMeasurableStat
|
|
2
|
+
from kafka.metrics.stats.sampled_stat import AbstractSampledStat
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TimeUnit:
|
|
6
|
+
_names = {
|
|
7
|
+
'nanosecond': 0,
|
|
8
|
+
'microsecond': 1,
|
|
9
|
+
'millisecond': 2,
|
|
10
|
+
'second': 3,
|
|
11
|
+
'minute': 4,
|
|
12
|
+
'hour': 5,
|
|
13
|
+
'day': 6,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
NANOSECONDS = _names['nanosecond']
|
|
17
|
+
MICROSECONDS = _names['microsecond']
|
|
18
|
+
MILLISECONDS = _names['millisecond']
|
|
19
|
+
SECONDS = _names['second']
|
|
20
|
+
MINUTES = _names['minute']
|
|
21
|
+
HOURS = _names['hour']
|
|
22
|
+
DAYS = _names['day']
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def get_name(time_unit):
|
|
26
|
+
return TimeUnit._names[time_unit]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Rate(AbstractMeasurableStat):
|
|
30
|
+
"""
|
|
31
|
+
The rate of the given quantity. By default this is the total observed
|
|
32
|
+
over a set of samples from a sampled statistic divided by the elapsed
|
|
33
|
+
time over the sample windows. Alternative AbstractSampledStat
|
|
34
|
+
implementations can be provided, however, to record the rate of
|
|
35
|
+
occurrences (e.g. the count of values measured over the time interval)
|
|
36
|
+
or other such values.
|
|
37
|
+
"""
|
|
38
|
+
__slots__ = ('_stat', '_unit')
|
|
39
|
+
|
|
40
|
+
def __init__(self, time_unit=TimeUnit.SECONDS, sampled_stat=None):
|
|
41
|
+
self._stat = sampled_stat or SampledTotal()
|
|
42
|
+
self._unit = time_unit
|
|
43
|
+
|
|
44
|
+
def unit_name(self):
|
|
45
|
+
return TimeUnit.get_name(self._unit)
|
|
46
|
+
|
|
47
|
+
def record(self, config, value, time_ms):
|
|
48
|
+
self._stat.record(config, value, time_ms)
|
|
49
|
+
|
|
50
|
+
def measure(self, config, now):
|
|
51
|
+
value = self._stat.measure(config, now)
|
|
52
|
+
return float(value) / self.convert(self.window_size(config, now))
|
|
53
|
+
|
|
54
|
+
def window_size(self, config, now):
|
|
55
|
+
# purge old samples before we compute the window size
|
|
56
|
+
self._stat.purge_obsolete_samples(config, now)
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
Here we check the total amount of time elapsed since the oldest
|
|
60
|
+
non-obsolete window. This give the total window_size of the batch
|
|
61
|
+
which is the time used for Rate computation. However, there is
|
|
62
|
+
an issue if we do not have sufficient data for e.g. if only
|
|
63
|
+
1 second has elapsed in a 30 second window, the measured rate
|
|
64
|
+
will be very high. Hence we assume that the elapsed time is
|
|
65
|
+
always N-1 complete windows plus whatever fraction of the final
|
|
66
|
+
window is complete.
|
|
67
|
+
|
|
68
|
+
Note that we could simply count the amount of time elapsed in
|
|
69
|
+
the current window and add n-1 windows to get the total time,
|
|
70
|
+
but this approach does not account for sleeps. AbstractSampledStat
|
|
71
|
+
only creates samples whenever record is called, if no record is
|
|
72
|
+
called for a period of time that time is not accounted for in
|
|
73
|
+
window_size and produces incorrect results.
|
|
74
|
+
"""
|
|
75
|
+
total_elapsed_time_ms = now - self._stat.oldest(now).last_window_ms
|
|
76
|
+
# Check how many full windows of data we have currently retained
|
|
77
|
+
num_full_windows = int(total_elapsed_time_ms / config.time_window_ms)
|
|
78
|
+
min_full_windows = config.samples - 1
|
|
79
|
+
|
|
80
|
+
# If the available windows are less than the minimum required,
|
|
81
|
+
# add the difference to the totalElapsedTime
|
|
82
|
+
if num_full_windows < min_full_windows:
|
|
83
|
+
total_elapsed_time_ms += ((min_full_windows - num_full_windows) *
|
|
84
|
+
config.time_window_ms)
|
|
85
|
+
|
|
86
|
+
return total_elapsed_time_ms
|
|
87
|
+
|
|
88
|
+
def convert(self, time_ms):
|
|
89
|
+
if self._unit == TimeUnit.NANOSECONDS:
|
|
90
|
+
return time_ms * 1000.0 * 1000.0
|
|
91
|
+
elif self._unit == TimeUnit.MICROSECONDS:
|
|
92
|
+
return time_ms * 1000.0
|
|
93
|
+
elif self._unit == TimeUnit.MILLISECONDS:
|
|
94
|
+
return time_ms
|
|
95
|
+
elif self._unit == TimeUnit.SECONDS:
|
|
96
|
+
return time_ms / 1000.0
|
|
97
|
+
elif self._unit == TimeUnit.MINUTES:
|
|
98
|
+
return time_ms / (60.0 * 1000.0)
|
|
99
|
+
elif self._unit == TimeUnit.HOURS:
|
|
100
|
+
return time_ms / (60.0 * 60.0 * 1000.0)
|
|
101
|
+
elif self._unit == TimeUnit.DAYS:
|
|
102
|
+
return time_ms / (24.0 * 60.0 * 60.0 * 1000.0)
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError('Unknown unit: %s' % (self._unit,))
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SampledTotal(AbstractSampledStat):
|
|
108
|
+
__slots__ = ('_initial_value', '_samples', '_current')
|
|
109
|
+
def __init__(self, initial_value=None):
|
|
110
|
+
if initial_value is not None:
|
|
111
|
+
raise ValueError('initial_value cannot be set on SampledTotal')
|
|
112
|
+
super().__init__(0.0)
|
|
113
|
+
|
|
114
|
+
def update(self, sample, config, value, time_ms):
|
|
115
|
+
sample.value += value
|
|
116
|
+
|
|
117
|
+
def combine(self, samples, config, now):
|
|
118
|
+
return float(sum(sample.value for sample in samples))
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from kafka.metrics.measurable_stat import AbstractMeasurableStat
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AbstractSampledStat(AbstractMeasurableStat, ABC):
|
|
7
|
+
"""
|
|
8
|
+
An AbstractSampledStat records a single scalar value measured over
|
|
9
|
+
one or more samples. Each sample is recorded over a configurable
|
|
10
|
+
window. The window can be defined by number of events or elapsed
|
|
11
|
+
time (or both, if both are given the window is complete when
|
|
12
|
+
*either* the event count or elapsed time criterion is met).
|
|
13
|
+
|
|
14
|
+
All the samples are combined to produce the measurement. When a
|
|
15
|
+
window is complete the oldest sample is cleared and recycled to
|
|
16
|
+
begin recording the next sample.
|
|
17
|
+
|
|
18
|
+
Subclasses of this class define different statistics measured
|
|
19
|
+
using this basic pattern.
|
|
20
|
+
"""
|
|
21
|
+
__slots__ = ('_initial_value', '_samples', '_current')
|
|
22
|
+
|
|
23
|
+
def __init__(self, initial_value):
|
|
24
|
+
self._initial_value = initial_value
|
|
25
|
+
self._samples = []
|
|
26
|
+
self._current = 0
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def update(self, sample, config, value, time_ms):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def combine(self, samples, config, now):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
def record(self, config, value, time_ms):
|
|
37
|
+
sample = self.current(time_ms)
|
|
38
|
+
if sample.is_complete(time_ms, config):
|
|
39
|
+
sample = self._advance(config, time_ms)
|
|
40
|
+
self.update(sample, config, float(value), time_ms)
|
|
41
|
+
sample.event_count += 1
|
|
42
|
+
|
|
43
|
+
def new_sample(self, time_ms):
|
|
44
|
+
return self.Sample(self._initial_value, time_ms)
|
|
45
|
+
|
|
46
|
+
def measure(self, config, now):
|
|
47
|
+
self.purge_obsolete_samples(config, now)
|
|
48
|
+
return float(self.combine(self._samples, config, now))
|
|
49
|
+
|
|
50
|
+
def current(self, time_ms):
|
|
51
|
+
if not self._samples:
|
|
52
|
+
self._samples.append(self.new_sample(time_ms))
|
|
53
|
+
return self._samples[self._current]
|
|
54
|
+
|
|
55
|
+
def oldest(self, now):
|
|
56
|
+
if not self._samples:
|
|
57
|
+
self._samples.append(self.new_sample(now))
|
|
58
|
+
oldest = self._samples[0]
|
|
59
|
+
for sample in self._samples[1:]:
|
|
60
|
+
if sample.last_window_ms < oldest.last_window_ms:
|
|
61
|
+
oldest = sample
|
|
62
|
+
return oldest
|
|
63
|
+
|
|
64
|
+
def purge_obsolete_samples(self, config, now):
|
|
65
|
+
"""
|
|
66
|
+
Timeout any windows that have expired in the absence of any events
|
|
67
|
+
"""
|
|
68
|
+
expire_age = config.samples * config.time_window_ms
|
|
69
|
+
for sample in self._samples:
|
|
70
|
+
if now - sample.last_window_ms >= expire_age:
|
|
71
|
+
sample.reset(now)
|
|
72
|
+
|
|
73
|
+
def _advance(self, config, time_ms):
|
|
74
|
+
self._current = (self._current + 1) % config.samples
|
|
75
|
+
if self._current >= len(self._samples):
|
|
76
|
+
sample = self.new_sample(time_ms)
|
|
77
|
+
self._samples.append(sample)
|
|
78
|
+
return sample
|
|
79
|
+
else:
|
|
80
|
+
sample = self.current(time_ms)
|
|
81
|
+
sample.reset(time_ms)
|
|
82
|
+
return sample
|
|
83
|
+
|
|
84
|
+
class Sample:
|
|
85
|
+
|
|
86
|
+
def __init__(self, initial_value, now):
|
|
87
|
+
self.initial_value = initial_value
|
|
88
|
+
self.event_count = 0
|
|
89
|
+
self.last_window_ms = now
|
|
90
|
+
self.value = initial_value
|
|
91
|
+
|
|
92
|
+
def reset(self, now):
|
|
93
|
+
self.event_count = 0
|
|
94
|
+
self.last_window_ms = now
|
|
95
|
+
self.value = self.initial_value
|
|
96
|
+
|
|
97
|
+
def is_complete(self, time_ms, config):
|
|
98
|
+
return (time_ms - self.last_window_ms >= config.time_window_ms or
|
|
99
|
+
self.event_count >= config.event_window)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from kafka.errors import QuotaViolationError
|
|
5
|
+
from kafka.metrics import KafkaMetric
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Sensor:
|
|
9
|
+
"""
|
|
10
|
+
A sensor applies a continuous sequence of numerical values
|
|
11
|
+
to a set of associated metrics. For example a sensor on
|
|
12
|
+
message size would record a sequence of message sizes using
|
|
13
|
+
the `record(double)` api and would maintain a set
|
|
14
|
+
of metrics about request sizes such as the average or max.
|
|
15
|
+
"""
|
|
16
|
+
__slots__ = ('_lock', '_registry', '_name', '_parents', '_metrics',
|
|
17
|
+
'_stats', '_config', '_inactive_sensor_expiration_time_ms',
|
|
18
|
+
'_last_record_time')
|
|
19
|
+
|
|
20
|
+
def __init__(self, registry, name, parents, config,
|
|
21
|
+
inactive_sensor_expiration_time_seconds):
|
|
22
|
+
if not name:
|
|
23
|
+
raise ValueError('name must be non-empty')
|
|
24
|
+
self._lock = threading.RLock()
|
|
25
|
+
self._registry = registry
|
|
26
|
+
self._name = name
|
|
27
|
+
self._parents = parents or []
|
|
28
|
+
self._metrics = []
|
|
29
|
+
self._stats = []
|
|
30
|
+
self._config = config
|
|
31
|
+
self._inactive_sensor_expiration_time_ms = (
|
|
32
|
+
inactive_sensor_expiration_time_seconds * 1000)
|
|
33
|
+
self._last_record_time = time.monotonic() * 1000
|
|
34
|
+
self._check_forest(set())
|
|
35
|
+
|
|
36
|
+
def _check_forest(self, sensors):
|
|
37
|
+
"""Validate that this sensor doesn't end up referencing itself."""
|
|
38
|
+
if self in sensors:
|
|
39
|
+
raise ValueError('Circular dependency in sensors: %s is its own'
|
|
40
|
+
'parent.' % (self.name,))
|
|
41
|
+
sensors.add(self)
|
|
42
|
+
for parent in self._parents:
|
|
43
|
+
parent._check_forest(sensors)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self):
|
|
47
|
+
"""
|
|
48
|
+
The name this sensor is registered with.
|
|
49
|
+
This name will be unique among all registered sensors.
|
|
50
|
+
"""
|
|
51
|
+
return self._name
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def metrics(self):
|
|
55
|
+
return tuple(self._metrics)
|
|
56
|
+
|
|
57
|
+
def record(self, value=1.0, time_ms=None):
|
|
58
|
+
"""
|
|
59
|
+
Record a value at a known time.
|
|
60
|
+
Arguments:
|
|
61
|
+
value (double): The value we are recording
|
|
62
|
+
time_ms (int): A POSIX timestamp in milliseconds.
|
|
63
|
+
Default: The time when record() is evaluated (now)
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
QuotaViolationException: if recording this value moves a
|
|
67
|
+
metric beyond its configured maximum or minimum bound
|
|
68
|
+
"""
|
|
69
|
+
if time_ms is None:
|
|
70
|
+
time_ms = time.monotonic() * 1000
|
|
71
|
+
self._last_record_time = time_ms
|
|
72
|
+
with self._lock: # XXX high volume, might be performance issue
|
|
73
|
+
# increment all the stats
|
|
74
|
+
for stat in self._stats:
|
|
75
|
+
stat.record(self._config, value, time_ms)
|
|
76
|
+
self._check_quotas(time_ms)
|
|
77
|
+
for parent in self._parents:
|
|
78
|
+
parent.record(value, time_ms)
|
|
79
|
+
|
|
80
|
+
def _check_quotas(self, time_ms):
|
|
81
|
+
"""
|
|
82
|
+
Check if we have violated our quota for any metric that
|
|
83
|
+
has a configured quota
|
|
84
|
+
"""
|
|
85
|
+
for metric in self._metrics:
|
|
86
|
+
if metric.config and metric.config.quota:
|
|
87
|
+
value = metric.value(time_ms)
|
|
88
|
+
if not metric.config.quota.is_acceptable(value):
|
|
89
|
+
raise QuotaViolationError("'%s' violated quota. Actual: "
|
|
90
|
+
"%d, Threshold: %d" %
|
|
91
|
+
(metric.metric_name,
|
|
92
|
+
value,
|
|
93
|
+
metric.config.quota.bound))
|
|
94
|
+
|
|
95
|
+
def add_compound(self, compound_stat, config=None):
|
|
96
|
+
"""
|
|
97
|
+
Register a compound statistic with this sensor which
|
|
98
|
+
yields multiple measurable quantities (like a histogram)
|
|
99
|
+
|
|
100
|
+
Arguments:
|
|
101
|
+
stat (AbstractCompoundStat): The stat to register
|
|
102
|
+
config (MetricConfig): The configuration for this stat.
|
|
103
|
+
If None then the stat will use the default configuration
|
|
104
|
+
for this sensor.
|
|
105
|
+
"""
|
|
106
|
+
if not compound_stat:
|
|
107
|
+
raise ValueError('compound stat must be non-empty')
|
|
108
|
+
self._stats.append(compound_stat)
|
|
109
|
+
for named_measurable in compound_stat.stats():
|
|
110
|
+
metric = KafkaMetric(named_measurable.name, named_measurable.stat,
|
|
111
|
+
config or self._config)
|
|
112
|
+
self._registry.register_metric(metric)
|
|
113
|
+
self._metrics.append(metric)
|
|
114
|
+
|
|
115
|
+
def add(self, metric_name, stat, config=None):
|
|
116
|
+
"""
|
|
117
|
+
Register a metric with this sensor
|
|
118
|
+
|
|
119
|
+
Arguments:
|
|
120
|
+
metric_name (MetricName): The name of the metric
|
|
121
|
+
stat (AbstractMeasurableStat): The statistic to keep
|
|
122
|
+
config (MetricConfig): A special configuration for this metric.
|
|
123
|
+
If None use the sensor default configuration.
|
|
124
|
+
"""
|
|
125
|
+
with self._lock:
|
|
126
|
+
metric = KafkaMetric(metric_name, stat, config or self._config)
|
|
127
|
+
self._registry.register_metric(metric)
|
|
128
|
+
self._metrics.append(metric)
|
|
129
|
+
self._stats.append(stat)
|
|
130
|
+
|
|
131
|
+
def has_expired(self):
|
|
132
|
+
"""
|
|
133
|
+
Return True if the Sensor is eligible for removal due to inactivity.
|
|
134
|
+
"""
|
|
135
|
+
return ((time.monotonic() * 1000 - self._last_record_time) >
|
|
136
|
+
self._inactive_sensor_expiration_time_ms)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from kafka.metrics.measurable_stat import AbstractMeasurableStat
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Total(AbstractMeasurableStat):
|
|
5
|
+
"""An un-windowed cumulative total maintained over all time."""
|
|
6
|
+
__slots__ = ('_total',)
|
|
7
|
+
|
|
8
|
+
def __init__(self, value=0.0):
|
|
9
|
+
self._total = value
|
|
10
|
+
|
|
11
|
+
def record(self, config, value, now):
|
|
12
|
+
self._total += value
|
|
13
|
+
|
|
14
|
+
def measure(self, config, now):
|
|
15
|
+
return float(self._total)
|
kafka/net/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .connection import KafkaConnection
|
|
2
|
+
from .inet import create_connection, KafkaNetSocket
|
|
3
|
+
from .manager import KafkaConnectionManager
|
|
4
|
+
from .metrics import KafkaConnectionMetrics, KafkaManagerMetrics
|
|
5
|
+
from .selector import NetworkSelector
|
|
6
|
+
from .http_connect import HttpConnectProxy
|
|
7
|
+
from .socks5 import Socks5Proxy
|
|
8
|
+
from .transport import KafkaTCPTransport, KafkaSSLTransport
|
|
9
|
+
from .wakeup_notifier import WakeupNotifier
|
|
10
|
+
|
|
11
|
+
from .compat import KafkaNetClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'KafkaConnection', 'create_connection', 'KafkaNetSocket',
|
|
16
|
+
'KafkaConnectionManager', 'KafkaConnectionMetrics', 'KafkaManagerMetrics',
|
|
17
|
+
'NetworkSelector', 'HttpConnectProxy', 'Socks5Proxy', 'KafkaTCPTransport', 'KafkaSSLTransport',
|
|
18
|
+
'WakeupNotifier', 'KafkaNetClient',
|
|
19
|
+
]
|
kafka/net/compat.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import random
|
|
3
|
+
import threading
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import kafka.errors as Errors
|
|
7
|
+
from kafka.net.manager import KafkaConnectionManager
|
|
8
|
+
from kafka.net.selector import NetworkSelector
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class KafkaNetClient:
|
|
15
|
+
"""Drop-in replacement for KafkaClient backed by KafkaConnectionManager.
|
|
16
|
+
|
|
17
|
+
Provides the KafkaClient API surface that existing consumer/producer/admin
|
|
18
|
+
code depends on. Goal: shrink over time as components transition to using
|
|
19
|
+
KafkaConnectionManager directly (fire-and-forget via _request_buffer).
|
|
20
|
+
"""
|
|
21
|
+
def __init__(self, net=None, manager=None, **configs):
|
|
22
|
+
# _lock is still used by the legacy Coordinator (kafka/coordinator/base.py).
|
|
23
|
+
# Remove once Coordinator moves to the IO thread (Phase D).
|
|
24
|
+
self._lock = threading.RLock()
|
|
25
|
+
self._net = NetworkSelector(**configs) if net is None else net
|
|
26
|
+
self._manager = KafkaConnectionManager(self._net, **configs) if manager is None else manager
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def cluster(self):
|
|
30
|
+
return self._manager.cluster
|
|
31
|
+
|
|
32
|
+
# Connection state queries
|
|
33
|
+
|
|
34
|
+
def connected(self, node_id):
|
|
35
|
+
conn = self._manager._conns.get(node_id)
|
|
36
|
+
return conn is not None and conn.connected
|
|
37
|
+
|
|
38
|
+
def is_disconnected(self, node_id):
|
|
39
|
+
return not self.connected(node_id)
|
|
40
|
+
|
|
41
|
+
def is_ready(self, node_id):
|
|
42
|
+
conn = self._manager._conns.get(node_id)
|
|
43
|
+
return conn is not None and conn.connected and not conn.paused
|
|
44
|
+
|
|
45
|
+
def ready(self, node_id, **kwargs):
|
|
46
|
+
if self.is_ready(node_id):
|
|
47
|
+
return True
|
|
48
|
+
try:
|
|
49
|
+
self._manager.get_connection(node_id)
|
|
50
|
+
except Errors.NodeNotReadyError:
|
|
51
|
+
pass
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
def maybe_connect(self, node_id, **kwargs):
|
|
55
|
+
try:
|
|
56
|
+
self._manager.get_connection(node_id)
|
|
57
|
+
except Errors.NodeNotReadyError:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def await_ready(self, node_id, timeout_ms=30000):
|
|
61
|
+
if self.is_ready(node_id):
|
|
62
|
+
return True
|
|
63
|
+
self.maybe_connect(node_id)
|
|
64
|
+
conn = self._manager._conns.get(node_id)
|
|
65
|
+
if conn is not None and not conn.init_future.is_done:
|
|
66
|
+
self._net.poll(timeout_ms=timeout_ms, future=conn.init_future)
|
|
67
|
+
# Connection may be initialized but paused (e.g. max_in_flight reached).
|
|
68
|
+
# Poll briefly to drain in-flight responses and unpause.
|
|
69
|
+
if conn is not None and conn.connected and conn.paused:
|
|
70
|
+
self._net.poll(timeout_ms=min(timeout_ms, self._manager.config['request_timeout_ms']))
|
|
71
|
+
if not self.is_ready(node_id):
|
|
72
|
+
raise Errors.KafkaConnectionError('Node %s not ready after %s ms' % (node_id, timeout_ms))
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
# In-flight request tracking
|
|
76
|
+
|
|
77
|
+
def in_flight_request_count(self, node_id=None):
|
|
78
|
+
if node_id is not None:
|
|
79
|
+
conn = self._manager._conns.get(node_id)
|
|
80
|
+
return len(conn.in_flight_requests) if conn is not None else 0
|
|
81
|
+
return sum(len(c.in_flight_requests) for c in self._manager._conns.values())
|
|
82
|
+
|
|
83
|
+
def throttle_delay(self, node_id):
|
|
84
|
+
conn = self._manager._conns.get(node_id)
|
|
85
|
+
if conn is None:
|
|
86
|
+
return 0
|
|
87
|
+
remaining = conn._throttle_time - time.monotonic()
|
|
88
|
+
return max(0, remaining) * 1000
|
|
89
|
+
|
|
90
|
+
# Bootstrap / version
|
|
91
|
+
|
|
92
|
+
def bootstrap_connected(self):
|
|
93
|
+
bootstrap_future = self._manager._bootstrap_future
|
|
94
|
+
return bootstrap_future is not None and not bootstrap_future.is_done
|
|
95
|
+
|
|
96
|
+
def get_broker_version(self, timeout_ms=None):
|
|
97
|
+
if self._manager.broker_version is None:
|
|
98
|
+
self._manager.bootstrap(timeout_ms)
|
|
99
|
+
return self._manager.broker_version
|
|
100
|
+
|
|
101
|
+
def check_version(self, node_id=None, timeout_ms=10000):
|
|
102
|
+
if not self._manager.bootstrapped:
|
|
103
|
+
self._manager.bootstrap(timeout_ms)
|
|
104
|
+
if node_id is None:
|
|
105
|
+
return self._manager.broker_version
|
|
106
|
+
async def _check_version(broker_id, timeout_ms):
|
|
107
|
+
conn = await self._manager.get_connection(broker_id, timeout_ms=timeout_ms)
|
|
108
|
+
return conn.broker_version
|
|
109
|
+
return self._net.run(_check_version, node_id, timeout_ms)
|
|
110
|
+
|
|
111
|
+
# Request sending
|
|
112
|
+
|
|
113
|
+
def send(self, node_id, request, **kwargs):
|
|
114
|
+
return self._manager.send(request, node_id=node_id)
|
|
115
|
+
|
|
116
|
+
def send_and_receive(self, node_id, request, timeout_ms=30000):
|
|
117
|
+
self.await_ready(node_id, timeout_ms=timeout_ms)
|
|
118
|
+
f = self.send(node_id, request)
|
|
119
|
+
self._net.poll(timeout_ms=timeout_ms, future=f)
|
|
120
|
+
if f.succeeded():
|
|
121
|
+
return f.value
|
|
122
|
+
elif f.failed():
|
|
123
|
+
raise f.exception
|
|
124
|
+
raise Errors.KafkaTimeoutError('Request timed out')
|
|
125
|
+
|
|
126
|
+
# Delegation
|
|
127
|
+
|
|
128
|
+
def poll(self, timeout_ms=None, future=None):
|
|
129
|
+
# _lock serializes with HeartbeatThread, which also drives poll()
|
|
130
|
+
# while holding this lock. Without it, both threads would call
|
|
131
|
+
# _net.poll() concurrently and race on selector / task state.
|
|
132
|
+
# The lock goes away once HeartbeatThread does (Phase D).
|
|
133
|
+
with self._lock:
|
|
134
|
+
return self._net.poll(timeout_ms=timeout_ms, future=future)
|
|
135
|
+
|
|
136
|
+
def close(self, node_id=None):
|
|
137
|
+
self._manager.close(node_id=node_id)
|
|
138
|
+
if node_id is None:
|
|
139
|
+
self._net.close()
|
|
140
|
+
|
|
141
|
+
def least_loaded_node(self, bootstrap_fallback=False):
|
|
142
|
+
node_id = self._manager.least_loaded_node()
|
|
143
|
+
if node_id is None and bootstrap_fallback:
|
|
144
|
+
node_id = random.choice(self._manager.cluster.bootstrap_brokers()).node_id
|
|
145
|
+
return node_id
|
|
146
|
+
|
|
147
|
+
def least_loaded_node_refresh_ms(self, bootstrap_fallback=False):
|
|
148
|
+
brokers = self._manager.cluster.brokers()
|
|
149
|
+
if not brokers and bootstrap_fallback:
|
|
150
|
+
brokers = self._manager.cluster.bootstrap_brokers()
|
|
151
|
+
if not brokers:
|
|
152
|
+
return self._manager.config['reconnect_backoff_ms']
|
|
153
|
+
delays = [self._manager.connection_delay(broker.node_id) for broker in brokers]
|
|
154
|
+
return min(delays) * 1000
|
|
155
|
+
|
|
156
|
+
def connection_delay(self, node_id):
|
|
157
|
+
return self._manager.connection_delay(node_id)
|
|
158
|
+
|
|
159
|
+
def wakeup(self):
|
|
160
|
+
self._net.wakeup()
|
|
161
|
+
|
|
162
|
+
def api_version(self, operation, max_version=None):
|
|
163
|
+
if self._manager.broker_version_data is None:
|
|
164
|
+
raise Errors.IllegalStateError('broker_version_data is not available: have we bootstrapped yet?')
|
|
165
|
+
return self._manager.broker_version_data.api_version(operation, max_version=max_version)
|