-
Notifications
You must be signed in to change notification settings - Fork 33
/
log.py
248 lines (183 loc) · 6.46 KB
/
log.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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
import json
import logging
import math
import numbers
import os
import platform
import resource
import sys
from collections import MutableMapping
from contextlib import contextmanager
from os.path import join
from IPython.core.display import display, HTML
from pyhocon import ConfigFactory
from pyhocon import ConfigMissingException
from pyhocon import ConfigTree
from pyhocon import HOCONConverter
from gtd.utils import NestedDict, Config
def in_ipython():
try:
__IPYTHON__
return True
except NameError:
return False
def print_with_fonts(tokens, sizes, colors, background=None):
def style(text, size=12, color='black'):
return u'<span style="font-size: {}px; color: {};">{}</span>'.format(size, color, text)
styled = [style(token, size, color) for token, size, color in zip(tokens, sizes, colors)]
text = u' '.join(styled)
if background:
text = u'<span style="background-color: {};">{}</span>'.format(background, text)
display(HTML(text))
def gb_used():
used = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
if platform.system() != 'Darwin':
# on Linux, used is in terms of kilobytes
power = 2
else:
# on Mac, used is in terms of bytes
power = 3
return float(used) / math.pow(1024, power)
class Metadata(MutableMapping):
"""A wrapper around ConfigTree.
Supports a name_scope contextmanager.
"""
def __init__(self, config_tree=None):
if config_tree is None:
config_tree = ConfigTree()
self._config_tree = config_tree
self._namestack = []
@contextmanager
def name_scope(self, name):
self._namestack.append(name)
yield
self._namestack.pop()
def _full_key(self, key):
return '.'.join(self._namestack + [key])
def __getitem__(self, key):
try:
val = self._config_tree.get(self._full_key(key))
except ConfigMissingException:
raise KeyError(key)
if isinstance(val, ConfigTree):
return Metadata(val)
return val
def __setitem__(self, key, value):
"""Put a value (key is a dot-separated name)."""
self._config_tree.put(self._full_key(key), value)
def __delitem__(self, key):
raise NotImplementedError()
def __iter__(self):
return iter(self._config_tree)
def __len__(self):
return len(self._config_tree)
def __repr__(self):
return self.to_str()
def to_str(self, fmt='hocon'):
return HOCONConverter.convert(self._config_tree, fmt)
def to_file(self, path, fmt='hocon'):
with open(path, 'w') as f:
f.write(self.to_str(fmt))
@classmethod
def from_file(cls, path, fmt='hocon'):
if fmt == 'hocon':
config_tree = ConfigFactory.parse_file(path)
elif fmt == 'json':
with open(path, 'r') as f:
d = json.load(f)
config_tree = ConfigFactory.from_dict(d)
else:
raise ValueError('Invalid format: {}'.format(fmt))
return cls(config_tree)
class SyncedMetadata(Metadata):
"""A Metadata object which writes to file after every change."""
def __init__(self, path, fmt='hocon'):
if os.path.exists(path):
m = Metadata.from_file(path, fmt)
else:
m = Metadata()
super(SyncedMetadata, self).__init__(m._config_tree)
self._path = path
self._fmt = fmt
def __setitem__(self, key, value):
super(SyncedMetadata, self).__setitem__(key, value)
self.to_file(self._path, fmt=self._fmt)
def print_list(l):
for item in l:
print item
def print_no_newline(s):
sys.stdout.write(s)
sys.stdout.flush()
def set_log_level(level):
"""Set the log-level of the root logger of the logging module.
Args:
level: can be an integer such as 30 (logging.WARN), or a string such as 'WARN'
"""
if isinstance(level, str):
level = logging._levelNames[level]
logger = logging.getLogger() # gets root logger
logger.setLevel(level)
def jupyter_no_margins():
"""Cause Jupyter notebook to take up 100% of window width."""
display(HTML("<style>.container { width:100% !important; }</style>"))
class TraceSession(object):
def __init__(self, tracer):
self.tracer = tracer
self._values = {}
@property
def values(self):
return self._values
def save(self, save_path):
with open(save_path, 'w') as f:
json.dump(self.values, f, indent=4, sort_keys=True)
def __enter__(self):
if self.tracer._current_session:
raise RuntimeError('Already in the middle of a TraceSession')
# register as the current session
self.tracer._current_session = self
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# un-register
self.tracer._current_session = None
class Tracer(object):
"""Log values computed during program execution.
Values are logged to the currently active TraceSession object.
"""
def __init__(self):
self._current_session = None
def session(self):
return TraceSession(self)
def log(self, logging_callback):
"""If we are in a TraceSession, execute the logging_callback.
The logging_callback should take a `values` dict as its only argument, and modify `values` in some way.
Args:
logging_callback (Callable[dict]): a function which takes a `values` dict as its only argument.
"""
if self._current_session is None:
return
logging_callback(self._current_session.values)
def log_put(self, name, value):
"""Log a value.
Args:
name (str): name of the variable
value (object)
"""
def callback(values):
if name in values:
raise RuntimeError('{} already logged'.format(name))
values[name] = value
return self.log(callback)
def log_append(self, name, value):
"""Append a value.
Args:
name (str): name of the variable
value (object): value to append
"""
def callback(values):
if name not in values:
values[name] = []
values[name].append(value)
return self.log(callback)
def indent(s, spaces=4):
whitespace = u' ' * spaces
return u'\n'.join(whitespace + line for line in s.split(u'\n'))