-
Notifications
You must be signed in to change notification settings - Fork 4
/
util.py
106 lines (83 loc) · 3.29 KB
/
util.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import collections
import decimal
import logging
import os
import alertlib
def probability(past_errors,
past_time,
errors_this_period,
time_this_period):
"""Given the number of errors we've had in the past, how long of a period
those errors occured over, the number of errors and the length of this
period, return the probability that we saw this number of errors due to
an abnormally elevated rate of reports,
as well as the mean (for displaying to users).
"""
mean = (past_errors * 1.0 / past_time) * time_this_period
return (mean, poisson_cdf(errors_this_period - 1, mean))
def poisson_cdf(actual, mean):
"""Return the probability that we see actual or anything smaller
in a random measurement of a
variable with a poisson distribution with mean mean.
Expects mean to be a Decimal or a float
(we use Decimal so that long periods and high numbers of reports work--
a mean of 746 or higher would cause a zero to propagate and make us report
a probability of 0 even if the actual probability was almost 1.)
"""
if actual < 0:
return decimal.Decimal(0)
if isinstance(mean, float):
mean = decimal.Decimal(mean)
cum_prob = decimal.Decimal(0)
p = (-mean).exp()
cum_prob += p
for i in range(actual):
# We calculate the probability of each lesser value individually, and
# sum as we go
p *= mean
p /= i + 1
cum_prob += p
return float(cum_prob)
def relative_path(f):
"""Given f, which is assumed to be in the same directory as this util file,
return the relative path to it."""
return os.path.join(os.path.dirname(__file__), f)
def thousand_commas(n):
"""Given n, a number, put in thousand separators.
EX: thousand_commas(100000) = 100,000
thousand_commas(1000000.0123) = 1,000,000.0123
"""
return '{:,}'.format(n)
def merge_int_dicts(d1, d2):
"""Given two dictionaries with integer values, merge them.
EX: merge_int_dicts({'a': 1}, {'a': 2, 'b': 5}) = {'a': 3, 'b': 5}
"""
merged_dict = collections.defaultdict(int)
for d in (d1, d2):
for k in d:
merged_dict[k] += d[k]
return merged_dict
def send_to_slack(message, channel):
alertlib.Alert(message, severity=logging.ERROR) \
.send_to_slack(channel, sender='beep-boop',
icon_emoji=None, # need to override default value
icon_url='https://slack.global.ssl.fastly.net/9fa2/img/services/hubot_128.png')
def send_to_pagerduty(message, service):
alertlib.Alert(message).send_to_pagerduty(service)
def retry(fn, description, should_retry_fn, retry_count=3):
"""A simple retry function.
should_retry_fn is a function taking an exception (raised by fn)
and returning True if we should retry or False else. Of course we
ignore should_retry_fn after retry_count retries.
"""
for i in range(1, retry_count):
try:
return fn()
except Exception as why:
if should_retry_fn(why):
logging.debug('FAILED: %s (attempt %s, retrying)'
% (description, i))
else:
raise
# Try one last time, which will just raise if it fails.
return fn()