1 |
|
2 |
|
3 |
|
4 | from random import random
|
5 | from socket import socket, AF_INET, SOCK_DGRAM
|
6 |
|
7 | class 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 |
|
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 |
|
153 |
|
154 | udp_sock = socket(AF_INET, SOCK_DGRAM)
|
155 |
|
156 | for item in _dict.items():
|
157 | udp_sock.sendto(":".join(item).encode('utf-8'), addr)
|