Merge pull request #646 from datastax/284
PYTHON-284 - request size attribute, request init listener
This commit is contained in:
@@ -1916,6 +1916,7 @@ class Session(object):
|
|||||||
_pools = None
|
_pools = None
|
||||||
_profile_manager = None
|
_profile_manager = None
|
||||||
_metrics = None
|
_metrics = None
|
||||||
|
_request_init_callbacks = None
|
||||||
|
|
||||||
def __init__(self, cluster, hosts, keyspace=None):
|
def __init__(self, cluster, hosts, keyspace=None):
|
||||||
self.cluster = cluster
|
self.cluster = cluster
|
||||||
@@ -1926,6 +1927,7 @@ class Session(object):
|
|||||||
self._pools = {}
|
self._pools = {}
|
||||||
self._profile_manager = cluster.profile_manager
|
self._profile_manager = cluster.profile_manager
|
||||||
self._metrics = cluster.metrics
|
self._metrics = cluster.metrics
|
||||||
|
self._request_init_callbacks = []
|
||||||
self._protocol_version = self.cluster.protocol_version
|
self._protocol_version = self.cluster.protocol_version
|
||||||
|
|
||||||
self.encoder = Encoder()
|
self.encoder = Encoder()
|
||||||
@@ -2018,6 +2020,7 @@ class Session(object):
|
|||||||
"""
|
"""
|
||||||
future = self._create_response_future(query, parameters, trace, custom_payload, timeout, execution_profile)
|
future = self._create_response_future(query, parameters, trace, custom_payload, timeout, execution_profile)
|
||||||
future._protocol_handler = self.client_protocol_handler
|
future._protocol_handler = self.client_protocol_handler
|
||||||
|
self._on_request(future)
|
||||||
future.send_request()
|
future.send_request()
|
||||||
return future
|
return future
|
||||||
|
|
||||||
@@ -2123,6 +2126,38 @@ class Session(object):
|
|||||||
setattr(clone, attr, value)
|
setattr(clone, attr, value)
|
||||||
return clone
|
return clone
|
||||||
|
|
||||||
|
def add_request_init_listener(self, fn, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Adds a callback with arguments to be called when any request is created.
|
||||||
|
|
||||||
|
It will be invoked as `fn(response_future, *args, **kwargs)` after each client request is created,
|
||||||
|
and before the request is sent\*. This can be used to create extensions by adding result callbacks to the
|
||||||
|
response future.
|
||||||
|
|
||||||
|
\* where `response_future` is the :class:`.ResponseFuture` for the request.
|
||||||
|
|
||||||
|
Note that the init callback is done on the client thread creating the request, so you may need to consider
|
||||||
|
synchronization if you have multiple threads. Any callbacks added to the response future will be executed
|
||||||
|
on the event loop thread, so the normal advice about minimizing cycles and avoiding blocking apply (see Note in
|
||||||
|
:meth:`.ResponseFuture.add_callbacks`.
|
||||||
|
|
||||||
|
See `this example <https://github.com/datastax/python-driver/blob/master/examples/request_init_listener.py>`_ in the
|
||||||
|
source tree for an example.
|
||||||
|
"""
|
||||||
|
self._request_init_callbacks.append((fn, args, kwargs))
|
||||||
|
|
||||||
|
def remove_request_init_listener(self, fn, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Removes a callback and arguments from the list.
|
||||||
|
|
||||||
|
See :meth:`.Session.add_request_init_listener`.
|
||||||
|
"""
|
||||||
|
self._request_init_callbacks.remove((fn, args, kwargs))
|
||||||
|
|
||||||
|
def _on_request(self, response_future):
|
||||||
|
for fn, args, kwargs in self._request_init_callbacks:
|
||||||
|
fn(response_future, *args, **kwargs)
|
||||||
|
|
||||||
def prepare(self, query, custom_payload=None):
|
def prepare(self, query, custom_payload=None):
|
||||||
"""
|
"""
|
||||||
Prepares a query string, returning a :class:`~cassandra.query.PreparedStatement`
|
Prepares a query string, returning a :class:`~cassandra.query.PreparedStatement`
|
||||||
@@ -3162,6 +3197,11 @@ class ResponseFuture(object):
|
|||||||
Always ``True`` for non-DDL requests.
|
Always ``True`` for non-DDL requests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
request_encoded_size = None
|
||||||
|
"""
|
||||||
|
Size of the request message sent
|
||||||
|
"""
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
row_factory = None
|
row_factory = None
|
||||||
message = None
|
message = None
|
||||||
@@ -3285,8 +3325,10 @@ class ResponseFuture(object):
|
|||||||
connection, request_id = pool.borrow_connection(timeout=2.0)
|
connection, request_id = pool.borrow_connection(timeout=2.0)
|
||||||
self._connection = connection
|
self._connection = connection
|
||||||
result_meta = self.prepared_statement.result_metadata if self.prepared_statement else []
|
result_meta = self.prepared_statement.result_metadata if self.prepared_statement else []
|
||||||
connection.send_msg(message, request_id, cb=cb, encoder=self._protocol_handler.encode_message, decoder=self._protocol_handler.decode_message,
|
self.request_encoded_size = connection.send_msg(message, request_id, cb=cb,
|
||||||
result_metadata=result_meta)
|
encoder=self._protocol_handler.encode_message,
|
||||||
|
decoder=self._protocol_handler.decode_message,
|
||||||
|
result_metadata=result_meta)
|
||||||
return request_id
|
return request_id
|
||||||
except NoConnectionsAvailable as exc:
|
except NoConnectionsAvailable as exc:
|
||||||
log.debug("All connections for host %s are at capacity, moving to the next host", host)
|
log.debug("All connections for host %s are at capacity, moving to the next host", host)
|
||||||
|
|||||||
@@ -458,8 +458,9 @@ class Connection(object):
|
|||||||
# queue the decoder function with the request
|
# queue the decoder function with the request
|
||||||
# this allows us to inject custom functions per request to encode, decode messages
|
# this allows us to inject custom functions per request to encode, decode messages
|
||||||
self._requests[request_id] = (cb, decoder, result_metadata)
|
self._requests[request_id] = (cb, decoder, result_metadata)
|
||||||
self.push(encoder(msg, request_id, self.protocol_version, compressor=self.compressor, allow_beta_protocol_version=self.allow_beta_protocol_version))
|
msg = encoder(msg, request_id, self.protocol_version, compressor=self.compressor, allow_beta_protocol_version=self.allow_beta_protocol_version)
|
||||||
return request_id
|
self.push(msg)
|
||||||
|
return len(msg)
|
||||||
|
|
||||||
def wait_for_response(self, msg, timeout=None):
|
def wait_for_response(self, msg, timeout=None):
|
||||||
return self.wait_for_responses(msg, timeout=timeout)[0]
|
return self.wait_for_responses(msg, timeout=timeout)[0]
|
||||||
|
|||||||
@@ -150,6 +150,10 @@
|
|||||||
|
|
||||||
.. automethod:: execution_profile_clone_update
|
.. automethod:: execution_profile_clone_update
|
||||||
|
|
||||||
|
.. automethod:: add_request_init_listener
|
||||||
|
|
||||||
|
.. automethod:: remove_request_init_listener
|
||||||
|
|
||||||
.. autoclass:: ResponseFuture ()
|
.. autoclass:: ResponseFuture ()
|
||||||
|
|
||||||
.. autoattribute:: query
|
.. autoattribute:: query
|
||||||
|
|||||||
8
examples/README.rst
Normal file
8
examples/README.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Driver Examples
|
||||||
|
===============
|
||||||
|
This directory will contain a set of scripts demonstrating driver APIs or integration techniques. It will not be exhaustive, but will contain examples where they are too involved, or
|
||||||
|
open-ended to include inline in the docstrings. In that case, they should be referenced from the docstrings
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
* `request_init_listener.py <request_init_listener.py>`_ A script demonstrating how to register a session request listener and use it to track alternative metrics about requests (size, for example).
|
||||||
107
examples/request_init_listener.py
Normal file
107
examples/request_init_listener.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# Copyright 2013-2016 DataStax, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
# This script shows an example "request init listener" which can be registered to track certain request metrics
|
||||||
|
# for a session. In this case we're just accumulating total request and error counts, as well as some statistics
|
||||||
|
# about the encoded request size. Note that the counts would be available using the internal 'metrics' tracking --
|
||||||
|
# this is just demonstrating a way to track a few custom attributes.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
from cassandra.cluster import Cluster
|
||||||
|
from greplin import scales
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
pp = pprint.PrettyPrinter(indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestAnalyzer(object):
|
||||||
|
"""
|
||||||
|
Class used to track request and error counts for a Session.
|
||||||
|
|
||||||
|
Also computes statistics on encoded request size.
|
||||||
|
"""
|
||||||
|
|
||||||
|
requests = scales.PmfStat('request size')
|
||||||
|
errors = scales.IntStat('errors')
|
||||||
|
|
||||||
|
def __init__(self, session):
|
||||||
|
scales.init(self, '/cassandra')
|
||||||
|
# each instance will be registered with a session, and receive a callback for each request generated
|
||||||
|
session.add_request_init_listener(self.on_request)
|
||||||
|
|
||||||
|
def on_request(self, rf):
|
||||||
|
# This callback is invoked each time a request is created, on the thread creating the request.
|
||||||
|
# We can use this to count events, or add callbacks
|
||||||
|
rf.add_callbacks(self.on_success, self.on_error, callback_args=(rf,), errback_args=(rf,))
|
||||||
|
|
||||||
|
def on_success(self, _, response_future):
|
||||||
|
# future callback on a successful request; just record the size
|
||||||
|
self.requests.addValue(response_future.request_encoded_size)
|
||||||
|
|
||||||
|
def on_error(self, _, response_future):
|
||||||
|
# future callback for failed; record size and increment errors
|
||||||
|
self.requests.addValue(response_future.request_encoded_size)
|
||||||
|
self.errors += 1
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# just extracting request count from the size stats (which are recorded on all requests)
|
||||||
|
request_sizes = dict(self.requests)
|
||||||
|
count = request_sizes.pop('count')
|
||||||
|
return "%d requests (%d errors)\nRequest size statistics:\n%s" % (count, self.errors, pp.pformat(request_sizes))
|
||||||
|
|
||||||
|
|
||||||
|
# connect a session
|
||||||
|
session = Cluster().connect()
|
||||||
|
|
||||||
|
# attach a listener to this session
|
||||||
|
ra = RequestAnalyzer(session)
|
||||||
|
|
||||||
|
session.execute("SELECT release_version FROM system.local")
|
||||||
|
session.execute("SELECT release_version FROM system.local")
|
||||||
|
|
||||||
|
print(ra)
|
||||||
|
# 2 requests (0 errors)
|
||||||
|
# Request size statistics:
|
||||||
|
# { '75percentile': 74,
|
||||||
|
# '95percentile': 74,
|
||||||
|
# '98percentile': 74,
|
||||||
|
# '999percentile': 74,
|
||||||
|
# '99percentile': 74,
|
||||||
|
# 'max': 74,
|
||||||
|
# 'mean': 74.0,
|
||||||
|
# 'median': 74.0,
|
||||||
|
# 'min': 74,
|
||||||
|
# 'stddev': 0.0}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# intentional error to show that count increase
|
||||||
|
session.execute("syntax err")
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(ra) # note: the counts are updated, but the stats are not because scales only updates every 20s
|
||||||
|
# 3 requests (1 errors)
|
||||||
|
# Request size statistics:
|
||||||
|
# { '75percentile': 74,
|
||||||
|
# '95percentile': 74,
|
||||||
|
# '98percentile': 74,
|
||||||
|
# '999percentile': 74,
|
||||||
|
# '99percentile': 74,
|
||||||
|
# 'max': 74,
|
||||||
|
# 'mean': 74.0,
|
||||||
|
# 'median': 74.0,
|
||||||
|
# 'min': 74,
|
||||||
|
# 'stddev': 0.0}
|
||||||
Reference in New Issue
Block a user