1
# Copyright (C) 2011-2012 Canonical Services Ltd
3
# Permission is hereby granted, free of charge, to any person obtaining
4
# a copy of this software and associated documentation files (the
5
# "Software"), to deal in the Software without restriction, including
6
# without limitation the rights to use, copy, modify, merge, publish,
7
# distribute, sublicense, and/or sell copies of the Software, and to
8
# permit persons to whom the Software is furnished to do so, subject to
9
# the following conditions:
11
# The above copyright notice and this permission notice shall be
12
# included in all copies or substantial portions of the Software.
14
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
from twisted.internet.defer import inlineCallbacks, returnValue
25
from twisted.internet.protocol import DatagramProtocol
26
from twisted.python import log
29
class StatsDClientProtocol(DatagramProtocol):
30
"""A Twisted-based implementation of the StatsD client protocol.
32
Data is sent via UDP to a StatsD server for aggregation.
35
def __init__(self, client):
38
def startProtocol(self):
39
"""Connect to destination host."""
40
self.client.connect(self.transport)
42
def stopProtocol(self):
43
"""Connection was lost."""
44
self.client.disconnect()
47
class TwistedStatsDClient(object):
49
def __init__(self, host, port,
50
connect_callback=None,
51
disconnect_callback=None,
52
resolver_errback=None):
54
Build a connection that reports to the endpoint (on C{host} and
57
@param host: The StatsD server host.
58
@param port: The StatsD server port.
59
@param resolver_errback: The errback to invoke should
60
issues occur resolving the supplied C{host}.
61
@param connect_callback: The callback to invoke on connection.
62
@param disconnect_callback: The callback to invoke on disconnection.
64
from twisted.internet import reactor
66
self.reactor = reactor
70
self.host = yield reactor.resolve(host)
71
returnValue(self.host)
73
self.original_host = host
75
self.resolver = resolve(host)
76
if resolver_errback is None:
77
self.resolver.addErrback(log.err)
79
self.resolver.addErrback(resolver_errback)
82
self.connect_callback = connect_callback
83
self.disconnect_callback = disconnect_callback
88
return "%s:%d" % (self.original_host, self.port)
91
def connect(self, transport=None):
92
"""Connect to the StatsD server."""
93
host = yield self.resolver
95
self.transport = transport
96
if self.transport is not None:
97
if self.connect_callback is not None:
98
self.connect_callback()
100
def disconnect(self):
101
"""Disconnect from the StatsD server."""
102
if self.disconnect_callback is not None:
103
self.disconnect_callback()
104
self.transport = None
106
def write(self, data, callback=None):
107
"""Send the metric to the StatsD server.
109
@param data: The data to be sent.
110
@param callback: The callback to which the result should be sent.
111
B{Note}: The C{callback} will be called in the C{reactor}
112
thread, and not in the thread of the original caller.
114
self.reactor.callFromThread(self._write, data, callback)
116
def _write(self, data, callback):
117
"""Send the metric to the StatsD server.
119
@param data: The data to be sent.
120
@param callback: The callback to which the result should be sent.
121
@raise twisted.internet.error.MessageLengthError: If the size of data
124
if self.host is not None and self.transport is not None:
126
bytes_sent = self.transport.write(data, (self.host, self.port))
127
if callback is not None:
129
except (OverflowError, TypeError, socket.error, socket.gaierror):
130
if callback is not None: