UNPKG

4.97 kBPlain TextView Raw
1# Steve Ivy <steveivy@gmail.com>
2# http://monkinetic.com
3
4from random import random
5from socket import socket, AF_INET, SOCK_DGRAM
6
7class StatsdClient(object):
8 SC_TIMING = "ms"
9 SC_COUNT = "c"
10 SC_GAUGE = "g"
11 SC_SET = "s"
12
13 def __init__(self, host='localhost', port=8125):
14 """
15 Sends statistics to the stats daemon over UDP
16
17 >>> from python_example import StatsdClient
18 """
19 self.addr = (host, port)
20
21 def timing(self, stats, value):
22 """
23 Log timing information
24
25 >>> client = StatsdClient()
26 >>> client.timing('example.timing', 500)
27 >>> client.timing(('example.timing23', 'example.timing29'), 500)
28 """
29 self.update_stats(stats, value, self.SC_TIMING)
30
31 def gauge(self, stats, value):
32 """
33 Log gauges
34
35 >>> client = StatsdClient()
36 >>> client.gauge('example.gauge', 47)
37 >>> client.gauge(('example.gauge41', 'example.gauge43'), 47)
38 """
39 self.update_stats(stats, value, self.SC_GAUGE)
40
41 def set(self, stats, value):
42 """
43 Log set
44
45 >>> client = StatsdClient()
46 >>> client.set('example.set', "set")
47 >>> client.set(('example.set61', 'example.set67'), "2701")
48 """
49 self.update_stats(stats, value, self.SC_SET)
50
51 def increment(self, stats, sample_rate=1):
52 """
53 Increments one or more stats counters
54
55 >>> client = StatsdClient()
56 >>> client.increment('example.increment')
57 >>> client.increment('example.increment', 0.5)
58 """
59 self.count(stats, 1, sample_rate)
60
61 def decrement(self, stats, sample_rate=1):
62 """
63 Decrements one or more stats counters
64
65 >>> client = StatsdClient()
66 >>> client.decrement('example.decrement')
67 """
68 self.count(stats, -1, sample_rate)
69
70 def count(self, stats, value, sample_rate=1):
71 """
72 Updates one or more stats counters by arbitrary value
73
74 >>> client = StatsdClient()
75 >>> client.count('example.counter', 17)
76 """
77 self.update_stats(stats, value, self.SC_COUNT, sample_rate)
78
79 def update_stats(self, stats, value, _type, sample_rate=1):
80 """
81 Pipeline function that formats data, samples it and passes to send()
82
83 >>> client = StatsdClient()
84 >>> client.update_stats('example.update_stats', 73, "c", 0.9)
85 """
86 stats = self.format(stats, value, _type)
87 self.send(self.sample(stats, sample_rate), self.addr)
88
89 @staticmethod
90 def format(keys, value, _type):
91 """
92 General format function.
93
94 >>> StatsdClient.format("example.format", 2, "T")
95 {'example.format': '2|T'}
96 >>> formatted = StatsdClient.format(("example.format31", "example.format37"), "2", "T")
97 >>> formatted['example.format31'] == '2|T'
98 True
99 >>> formatted['example.format37'] == '2|T'
100 True
101 >>> len(formatted)
102 2
103 """
104 data = {}
105 value = "{0}|{1}".format(value, _type)
106 # TODO: Allow any iterable except strings
107 if not isinstance(keys, (list, tuple)):
108 keys = [keys]
109 for key in keys:
110 data[key] = value
111 return data
112
113 @staticmethod
114 def sample(data, sample_rate):
115 """
116 Sample data dict
117 TODO(rbtz@): Convert to generator
118
119 >>> StatsdClient.sample({"example.sample2": "2"}, 1)
120 {'example.sample2': '2'}
121 >>> StatsdClient.sample({"example.sample3": "3"}, 0)
122 {}
123 >>> from random import seed
124 >>> seed(1)
125 >>> sampled = StatsdClient.sample({"example.sample5": "5", "example.sample7": "7"}, 0.99)
126 >>> len(sampled)
127 2
128 >>> sampled['example.sample5']
129 '5|@0.99'
130 >>> sampled['example.sample7']
131 '7|@0.99'
132 >>> StatsdClient.sample({"example.sample5": "5", "example.sample7": "7"}, 0.01)
133 {}
134 """
135 if sample_rate >= 1:
136 return data
137 elif sample_rate < 1:
138 if random() <= sample_rate:
139 sampled_data = {}
140 for stat, value in data.items():
141 sampled_data[stat] = "{0}|@{1}".format(value, sample_rate)
142 return sampled_data
143 return {}
144
145 @staticmethod
146 def send(_dict, addr):
147 """
148 Sends key/value pairs via UDP.
149
150 >>> StatsdClient.send({"example.send":"11|c"}, ("127.0.0.1", 8125))
151 """
152 # TODO(rbtz@): IPv6 support
153 # TODO(rbtz@): Creating socket on each send is a waste of recources
154 udp_sock = socket(AF_INET, SOCK_DGRAM)
155 # TODO(rbtz@): Add batch support
156 for item in _dict.items():
157 udp_sock.sendto(":".join(item).encode('utf-8'), addr)