server.py 17.3 KB
Newer Older
marius's avatar
marius committed
1
2
import tornado.ioloop
import grond
marius's avatar
wip    
marius committed
3
import os
Marius Isken's avatar
Marius Isken committed
4
import glob
marius's avatar
marius committed
5
import os.path as op
Marius Isken's avatar
Marius Isken committed
6
import logging
7
import numpy as num
Marius Isken's avatar
wip    
Marius Isken committed
8
import socket
marius's avatar
marius committed
9

10
11
from scipy import ndimage

marius's avatar
marius committed
12
from collections import OrderedDict
Marius Isken's avatar
wip    
Marius Isken committed
13
from datetime import datetime
marius's avatar
marius committed
14

Marius Isken's avatar
Marius Isken committed
15
from pyrocko.guts import Object, Bool, String, Int, List, Float
16

Marius Isken's avatar
Marius Isken committed
17
from tornado.web import RequestHandler, StaticFileHandler
marius's avatar
marius committed
18
19
20
from tornado import gen

from bokeh.embed import autoload_server
Marius Isken's avatar
Marius Isken committed
21
22
23
from bokeh.application import Application
from bokeh.server.server import Server as BokehServer
from bokeh.application.handlers import Handler as BokehHandler
marius's avatar
marius committed
24

Marius Isken's avatar
Marius Isken committed
25
26
from bokeh.models import ColumnDataSource
from bokeh import layouts
marius's avatar
marius committed
27
28
from bokeh.plotting import figure

Marius Isken's avatar
wip    
Marius Isken committed
29
30
31
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('grond.baraddur')

marius's avatar
marius committed
32

Marius Isken's avatar
Marius Isken committed
33
34
35
36
37
38
39
40
41
def makeColorGradient(misfits, fr=1., fg=.5, fb=1.,
                      pr=0, pg=2.5, pb=4):
    misfits /= misfits.max()
    r = num.sin(fr * misfits + pr) * 127 + 128
    g = num.sin(fg * misfits + pg) * 127 + 128
    b = num.sin(fb * misfits + pb) * 127 + 128
    return ['#%02x%02x%02x' % (r[i], g[i], b[i]) for i in xrange(misfits.size)]


Marius Isken's avatar
wip    
Marius Isken committed
42
class BaraddurModel(object):
Marius Isken's avatar
Marius Isken committed
43
    def __init__(self, rundir):
marius's avatar
wip    
marius committed
44
        logger.debug('Loading problem from %s' % rundir)
Marius Isken's avatar
Marius Isken committed
45
        self.rundir = op.abspath(rundir)
marius's avatar
wip    
marius committed
46
47
48
        self.problem = grond.core.load_problem_info(self.rundir)
        self.parameters = self.problem.parameters
        self.nparameters = self.problem.nparameters
Marius Isken's avatar
Marius Isken committed
49
        self.targets = self.problem.targets
marius's avatar
wip    
marius committed
50
51
        self.ntargets = self.problem.ntargets

Marius Isken's avatar
Marius Isken committed
52
53
54
55
56
    def _jp(self, file):
        return op.join(self.rundir, file)

    @property
    def start_time(self):
Marius Isken's avatar
Marius Isken committed
57
58
        return datetime.fromtimestamp(
                os.stat(self._jp('problem.yaml')).st_mtime)
Marius Isken's avatar
wip    
Marius Isken committed
59
60
61
62

    @property
    def duration(self):
        return datetime.fromtimestamp(
Marius Isken's avatar
wip    
Marius Isken committed
63
            os.stat(self._jp('models')).st_mtime) - self.start_time
Marius Isken's avatar
Marius Isken committed
64
65
66
67
68
69
70

    @property
    def name(self):
        return op.split(self.rundir)[-1]

    @property
    def best_misfit(self):
Marius Isken's avatar
Marius Isken committed
71
72
        _, data = self.get_misfits(keys='misfit_mean')
        return data['misfit_mean'].min()
Marius Isken's avatar
Marius Isken committed
73
74
75
76
77

    @property
    def niterations(self):
        return op.getsize(self._jp('models')) / (self.nparameters * 8)

Marius Isken's avatar
wip    
Marius Isken committed
78
79
80
81
    @property
    def iter_per_second(self):
        return self.niterations / self.duration.seconds

Marius Isken's avatar
Marius Isken committed
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
    @staticmethod
    def validate_rundir(rundir):

        def jp(fn):
            return op.join(rundir, fn)

        for fn in ['models', 'misfits', 'problem.yaml']:
            if op.lexists(jp(fn)):
                if op.getsize(jp(fn)) == 0:
                    return False
            else:
                return False
        return True

    def get_models(self, skip_nmodels=0, keys=None):
        fn = self._jp('models')

marius's avatar
wip    
marius committed
99
100
101
102
103
104
        with open(fn, 'r') as f:
            nmodels = os.fstat(f.fileno()).st_size / (self.nparameters * 8)
            nmodels -= skip_nmodels
            f.seek(skip_nmodels * self.nparameters * 8)
            data = num.fromfile(
                f, dtype='<f8', count=nmodels * self.nparameters)\
Marius Isken's avatar
Marius Isken committed
105
                .astype(num.float32)
marius's avatar
wip    
marius committed
106

Marius Isken's avatar
wip    
Marius Isken committed
107
        nmodels = data.size/self.nparameters
marius's avatar
wip    
marius committed
108
109
        models = data.reshape((nmodels, self.nparameters))

Marius Isken's avatar
wip    
Marius Isken committed
110
        models_dict = {}
marius's avatar
wip    
marius committed
111
        for ip, par in enumerate(self.parameters):
Marius Isken's avatar
wip    
Marius Isken committed
112
113
            models_dict[par.name] = models[:, ip]
        models_dict['niter'] = num.arange(nmodels, dtype=num.int)\
Marius Isken's avatar
Marius Isken committed
114
115
116
            + skip_nmodels + 1

        if keys is not None:
Marius Isken's avatar
wip    
Marius Isken committed
117
            for k in models_dict.keys():
Marius Isken's avatar
Marius Isken committed
118
                if k not in keys:
Marius Isken's avatar
wip    
Marius Isken committed
119
                    models_dict.pop(k)
Marius Isken's avatar
Marius Isken committed
120

Marius Isken's avatar
wip    
Marius Isken committed
121
        return nmodels, models_dict
marius's avatar
wip    
marius committed
122

Marius Isken's avatar
Marius Isken committed
123
124
    def get_misfits(self, skip_nmodels=0, keys=None):
        fn = self._jp('misfits')
marius's avatar
wip    
marius committed
125
126

        with open(fn, 'r') as f:
Marius Isken's avatar
wip    
Marius Isken committed
127
            nmodels = os.fstat(f.fileno()).st_size / (self.ntargets * 2 * 8)
marius's avatar
wip    
marius committed
128
129
130
131
            nmodels -= skip_nmodels
            f.seek(skip_nmodels * self.ntargets * 2 * 8)
            data = num.fromfile(
                f, dtype='<f8', count=nmodels*self.ntargets*2)\
Marius Isken's avatar
Marius Isken committed
132
                .astype(num.float32)
marius's avatar
wip    
marius committed
133

Marius Isken's avatar
wip    
Marius Isken committed
134
135
        nmodels = data.size/(self.ntargets * 2)

marius's avatar
wip    
marius committed
136
137
138
139
140
141
142
        data = data.reshape((nmodels, self.ntargets*2))
        combi = num.empty_like(data)
        combi[:, 0::2] = data[:, :self.ntargets]
        combi[:, 1::2] = data[:, self.ntargets:]
        misfits = combi.reshape((nmodels, self.ntargets, 2))

        mf_dict = {}
Marius Isken's avatar
Marius Isken committed
143
144
145
        for it, target in enumerate(self.targets):
            mf_dict[target.id] = misfits[:, it, 0]
        mf_dict['misfit_mean'] = num.mean(misfits, axis=1)[:, 0]
marius's avatar
wip    
marius committed
146

Marius Isken's avatar
Marius Isken committed
147
148
149
150
151
152
153
154
155
156
157
        mf_dict['niter'] = num.arange(nmodels, dtype=num.int)\
            + skip_nmodels + 1

        if keys is not None:
            for k in mf_dict.keys():
                if k not in keys:
                    mf_dict.pop(k)

        return nmodels, mf_dict


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
class SmoothColumnDataSource(ColumnDataSource):
    '''Gauss smoothed ColumnDataSource '''
    def __init__(self, *args, **kwargs):
        self._gaussian_kw = {
            'sigma': 3,
        }
        for kw in ['sigma', 'order', 'output', 'mode', 'cval', 'truncate']:
            if kw in kwargs.keys():
                self._gaussian_kw[kw] = kwargs.pop(kw)
        ColumnDataSource.__init__(self, *args, **kwargs)

    def stream(self, data, **kwargs):
        sigma = self._gaussian_kw['sigma']
        if data.keys() == self.data.keys():
            raise AttributeError('streaming data must represent'
                                 ' existing column')
        for key, arr in data.iteritems():
            if arr.ndim > 1 or not isinstance(arr, num.ndarray):
                raise AttributeError('data is not numpy.ndarray of 1d')

            nvalues = arr.size
            if nvalues < sigma:
                self.data[key][-(sigma-nvalues):]
                d = num.empty(sigma)
                d[:(sigma-nvalues)] = self.data[key][-(sigma-nvalues):]
                d[(sigma-nvalues):] = arr[:]
                arr = ndimage.gaussian_filter1d(d, **self._gaussian_kw)
                arr = arr[-nvalues:]
            else:
                arr = ndimage.gaussian_filter1d(arr, **self._gaussian_kw)
            data[key] = arr

        ColumnDataSource.stream(self, data, **kwargs)


class BaraddurRequestHandler(RequestHandler):
    def initialize(self, config):
        self.config = config
        self.model = config.model


class BaraddurBokehHandler(BokehHandler):
    def __init__(self, config, *args, **kwargs):
        BokehHandler.__init__(self, *args, **kwargs)
        self.config = config
        self.model = config.model
        self.nmodels = 0

        self.source = ColumnDataSource()

    def update_source(self, func):
        new_nmodels, new_data = func(
            skip_nmodels=self.nmodels,
            keys=self.source.data.keys())
        self.source.stream(new_data)
        self.nmodels += new_nmodels

    @gen.coroutine
    def update_models(self):
        return self.update_source(self.model.get_models)

    @gen.coroutine
    def update_misfits(self):
        return self.update_source(self.model.get_misfits)


Marius Isken's avatar
Marius Isken committed
224
225
226
class Rundirs(BaraddurRequestHandler):

    def get(self):
Marius Isken's avatar
wip    
Marius Isken committed
227
        models = [BaraddurModel(rundir=rd)
Marius Isken's avatar
Marius Isken committed
228
229
230
231
232
233
234
235
236
237
238
239
240
241
                  for rd in self.config.available_rundirs]
        self.render('rundirs.html',
                    pages=pages,
                    project_dir=self.config.project_dir,
                    models=models,
                    active_rundir=self.config.rundir)

    def post(self):
        rundir = self.get_argument('rundir', None)
        if rundir is not None:
            self.config.rundir = rundir
        self.get()


Marius Isken's avatar
wip    
Marius Isken committed
242
class Summary(BaraddurRequestHandler):
Marius Isken's avatar
Marius Isken committed
243
244
245
246
247

    @gen.coroutine
    def get(self):
        self.render('summary.html',
                    pages=pages,
Marius Isken's avatar
Marius Isken committed
248
                    problem=self.model.problem)
marius's avatar
wip    
marius committed
249
250


Marius Isken's avatar
wip    
Marius Isken committed
251
class Misfit(BaraddurRequestHandler):
252
253

    class MisfitsPlot(BaraddurBokehHandler):
Marius Isken's avatar
Marius Isken committed
254
255

        def modify_document(self, doc):
Marius Isken's avatar
Marius Isken committed
256
            self.source.add(num.ndarray(0, dtype=num.float32),
Marius Isken's avatar
Marius Isken committed
257
                            'misfit_mean')
Marius Isken's avatar
Marius Isken committed
258
259
            self.source.add(num.ndarray(0, dtype=num.float32),
                            'niter')
Marius Isken's avatar
Marius Isken committed
260
261
262
            self.update_misfits()

            plot = figure(webgl=True,
Marius Isken's avatar
wip    
Marius Isken committed
263
264
                          responsive=True,
                          plot_height=300,
Marius Isken's avatar
Marius Isken committed
265
266
                          x_axis_label='Iteration #',
                          y_axis_label='Misfit')
Marius Isken's avatar
Marius Isken committed
267
            plot.scatter('niter', 'misfit_mean',
Marius Isken's avatar
Marius Isken committed
268
269
270
                         source=self.source, alpha=.4)

            doc.add_root(plot)
Marius Isken's avatar
Marius Isken committed
271
272
            doc.add_periodic_callback(self.update_misfits,
                                      self.config.plot_update_small)
Marius Isken's avatar
Marius Isken committed
273

Marius Isken's avatar
Marius Isken committed
274
    bokeh_handlers = {'misfit': MisfitsPlot}
Marius Isken's avatar
Marius Isken committed
275
276
277

    @gen.coroutine
    def get(self):
Marius Isken's avatar
wip    
Marius Isken committed
278
        self.render('misfit.html',
Marius Isken's avatar
Marius Isken committed
279
                    pages=pages,
Marius Isken's avatar
Marius Isken committed
280
281
282
283
                    misfit_plot=autoload_server(
                        None,
                        app_path='/misfit',
                        url='plots'),
Marius Isken's avatar
Marius Isken committed
284
                    problem=self.model.problem)
Marius Isken's avatar
Marius Isken committed
285
286


287
class Parameters(BaraddurRequestHandler):
Marius Isken's avatar
Marius Isken committed
288

289
    class ParameterPlots(BaraddurBokehHandler):
Marius Isken's avatar
Marius Isken committed
290
291
292
293

        ncols = 4

        def modify_document(self, doc):
Marius Isken's avatar
Marius Isken committed
294
            problem = self.model.problem
295

Marius Isken's avatar
Marius Isken committed
296
297
298
299
            for param in ['niter'] + [p.name for p in problem.parameters]:
                self.source.add(
                    num.ndarray(0, dtype=num.float32),
                    param)
Marius Isken's avatar
wip    
Marius Isken committed
300
            self.model = BaraddurModel(self.config.rundir)
Marius Isken's avatar
Marius Isken committed
301
            self.update_models()
Marius Isken's avatar
Marius Isken committed
302
303
304
305

            plots = []
            for par in problem.parameters:
                fig = figure(webgl=True,
Marius Isken's avatar
wip    
Marius Isken committed
306
                             responsive=True,
Marius Isken's avatar
Marius Isken committed
307
308
                             x_axis_label='Iteration #',
                             y_axis_label='%s [%s]' % (par.label, par.unit))
Marius Isken's avatar
Marius Isken committed
309
                fig.scatter('niter', par.name,
310
                            source=self.source, alpha=.4)
Marius Isken's avatar
Marius Isken committed
311
                plots.append(fig)
Marius Isken's avatar
Marius Isken committed
312
313
            if (len(plots) % self.ncols) != 0:
                plots += [None] * (self.ncols - (len(plots) % self.ncols))
Marius Isken's avatar
Marius Isken committed
314
315
316
317
318
319
320

            grid = layouts.gridplot(
                plots,
                responsive=True,
                ncols=self.ncols)

            doc.add_root(grid)
Marius Isken's avatar
Marius Isken committed
321
            doc.add_periodic_callback(self.update_models,
Marius Isken's avatar
Marius Isken committed
322
                                      self.config.plot_update_small)
Marius Isken's avatar
Marius Isken committed
323

Marius Isken's avatar
Marius Isken committed
324
    bokeh_handlers = {'parameters': ParameterPlots}
marius's avatar
marius committed
325
326
327
328

    @gen.coroutine
    def get(self):

Marius Isken's avatar
Marius Isken committed
329
        self.render('parameters.html',
Marius Isken's avatar
Marius Isken committed
330
331
332
                    pages=pages,
                    parameter_plot=autoload_server(
                        None,
Marius Isken's avatar
Marius Isken committed
333
334
                        app_path='/parameters',
                        url='plots'),
Marius Isken's avatar
Marius Isken committed
335
                    problem=self.model.problem)
marius's avatar
marius committed
336
337


Marius Isken's avatar
wip    
Marius Isken committed
338
339
340
341
class Targets(BaraddurRequestHandler):

    class TargetContributionPlot(BaraddurBokehHandler):

342
343
344
345
        def __init__(self, *args, **kwargs):
            BaraddurBokehHandler.__init__(self, *args, **kwargs)
            self.source = SmoothColumnDataSource(sigma=20)

Marius Isken's avatar
wip    
Marius Isken committed
346
347
        def modify_document(self, doc):
            plot = figure(webgl=True,
Marius Isken's avatar
Marius Isken committed
348
349
                          responsive=True,
                          plot_height=300,
Marius Isken's avatar
wip    
Marius Isken committed
350
351
352
                          x_axis_label='Iteration #',
                          y_axis_label='Misfit')

Marius Isken's avatar
Marius Isken committed
353
354
355
356
357
358
359
360
361
362
363
            target_ids = [t.id for t in self.model.targets]
            for param in ['niter'] + target_ids:
                self.source.add(
                    num.ndarray(0, dtype=num.float32),
                    param)
            self.update_misfits()
            colors = ['blue', 'red']
            for it, target in enumerate(self.model.targets):
                plot.scatter('niter', target.id,
                             source=self.source,
                             color=colors[it])
Marius Isken's avatar
wip    
Marius Isken committed
364

Marius Isken's avatar
Marius Isken committed
365
366
367
368
            # plot.patches(
            #     ['niter'] * self.model.ntargets,
            #     target_ids,
            #     source=self.source)
Marius Isken's avatar
wip    
Marius Isken committed
369

Marius Isken's avatar
Marius Isken committed
370
371
372
            doc.add_root(plot)
            doc.add_periodic_callback(self.update_misfits,
                                      self.config.plot_update_small)
Marius Isken's avatar
wip    
Marius Isken committed
373

Marius Isken's avatar
Marius Isken committed
374
    bokeh_handlers = {'targets': TargetContributionPlot}
Marius Isken's avatar
Marius Isken committed
375

marius's avatar
marius committed
376
377
    @gen.coroutine
    def get(self):
Marius Isken's avatar
wip    
Marius Isken committed
378
        self.render('targets.html',
Marius Isken's avatar
Marius Isken committed
379
                    target_misfit_plot=autoload_server(
Marius Isken's avatar
wip    
Marius Isken committed
380
                        None,
Marius Isken's avatar
Marius Isken committed
381
                        app_path='/targets',
Marius Isken's avatar
Marius Isken committed
382
                        url='plots'),
Marius Isken's avatar
wip    
Marius Isken committed
383
                    pages=pages,
Marius Isken's avatar
Marius Isken committed
384
                    problem=self.model.problem)
marius's avatar
marius committed
385
386
387


pages = OrderedDict([
Marius Isken's avatar
Marius Isken committed
388
    ('Rundirs', Rundirs),
Marius Isken's avatar
Marius Isken committed
389
    ('Summary', Summary),
Marius Isken's avatar
wip    
Marius Isken committed
390
    ('Misfit', Misfit),
Marius Isken's avatar
Marius Isken committed
391
    ('Parameters', Parameters),
Marius Isken's avatar
Marius Isken committed
392
    ('Targets', Targets),
marius's avatar
marius committed
393
394
395
])


396
class BaraddurConfig(Object):
Marius Isken's avatar
Marius Isken committed
397
398
399
400
    rundir = String.T()
    project_dir = String.T(
        help='Grond project dir.',
        optional=True)
401
    debug = Bool.T(
402
        default=False,
403
        optional=True)
Marius Isken's avatar
wip    
Marius Isken committed
404
405
406
407
    hosts = List.T(
        String.T(),
        default=['*'],
        optional=True,
marius's avatar
wip    
marius committed
408
        help='List of allowed hosts, default is all \'*\'.')
marius's avatar
wip    
marius committed
409
410
    port = Int.T(
        default=8080,
Marius Isken's avatar
wip    
Marius Isken committed
411
412
        optional=True,
        help='Port to listen on.')
Marius Isken's avatar
Marius Isken committed
413
414
415
416
417
418
419
420
    plot_update_small = Float.T(
        default=1e3,
        optional=True,
        help='Update rate for small, performant plots. E.g. misfits')
    plot_update_big = Float.T(
        default=2.5e3,
        optional=True,
        help='Update rate for big, heavy plots. E.g. parameters.')
421

Marius Isken's avatar
Marius Isken committed
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
    def __init__(self, *args, **kwargs):
        Object.__init__(self, *args, **kwargs)
        self._rundir = None
        self._model = None

    @property
    def rundir(self):
        return self._rundir

    @rundir.getter
    def rundir(self):
        if self._rundir is None:
            if len(self.available_rundirs) > 0:
                self.rundir = self.available_rundirs[0]
        return self._rundir

    @rundir.setter
    def rundir(self, value):
        self._rundir = value
Marius Isken's avatar
wip    
Marius Isken committed
441
        self._model = BaraddurModel(rundir=self._rundir)
Marius Isken's avatar
Marius Isken committed
442
443
444
445

    @property
    def model(self):
        if self._model is None:
Marius Isken's avatar
wip    
Marius Isken committed
446
            self._model = BaraddurModel(rundir=self.rundir)
Marius Isken's avatar
Marius Isken committed
447
448
449
450
451
452
453
        return self._model

    @property
    def available_rundirs(self):
        rundirs = []
        for d in [d for d in glob.glob(op.join(self.project_dir, '*'))
                  if op.isdir(d)]:
Marius Isken's avatar
wip    
Marius Isken committed
454
            if BaraddurModel.validate_rundir(d):
Marius Isken's avatar
Marius Isken committed
455
456
457
                rundirs.append(d)
        return rundirs

458
459

class Baraddur(BokehServer):
Marius Isken's avatar
Marius Isken committed
460
461
462
463
464
465
466
467

    def __init__(self, config=None, project_dir=None, *args, **kwargs):
        if config is not None:
            self.config = config
        elif project_dir is not None:
            self.config = BaraddurConfig(project_dir=project_dir)
        else:
            raise AttributeError('No project dir set!')
468
        self.ioloop = tornado.ioloop.IOLoop.current()
Marius Isken's avatar
wip    
Marius Isken committed
469
        port_offset = 0
470

Marius Isken's avatar
wip    
Marius Isken committed
471
472
473
474
475
        while True:
            try:
                BokehServer.__init__(
                    self,
                    self.get_bokeh_apps(),
476
                    io_loop=self.ioloop,
Marius Isken's avatar
wip    
Marius Isken committed
477
478
479
480
481
482
483
                    extra_patterns=self.get_tornado_handlers(),
                    port=self.config.port + port_offset,
                    host=self.config.hosts)
                break
            except socket.error as se:
                if se.errno == 98 and port_offset < 50:  # Port in use
                    port_offset += 1
Marius Isken's avatar
Marius Isken committed
484
485
486
                    logger.warning('Port %d in use, bailing to %d'
                                   % (self.config.port + port_offset - 1,
                                      self.config.port + port_offset))
Marius Isken's avatar
wip    
Marius Isken committed
487
488
                else:
                    raise se
489
        tornado_app = self._tornado
490
491
        tornado_app.settings['template_path'] = op.join(
            op.dirname(op.abspath(__file__)), 'templates')
492
493
494
495
496
497
498
499
500
501

        if self.config.debug:
            tornado_app.settings.setdefault('autoreload', True)
            tornado_app.settings.setdefault('compiled_template_cache', False)
            tornado_app.settings.setdefault('static_hash_cache', False)
            tornado_app.settings.setdefault('serve_traceback', True)
            # Automatically reload modified modules
            from tornado import autoreload
            autoreload.start()

Marius Isken's avatar
wip    
Marius Isken committed
502
            logging.getLogger('').setLevel(logging.DEBUG)
503
504
505
506
507
508
509
510
511
512

    def get_bokeh_apps(self):
        bokeh_apps = {}
        for tornado_handler in pages.itervalues():
            handler_docs = getattr(tornado_handler, 'bokeh_handlers',
                                   None)
            if handler_docs is None:
                continue

            for url, bokeh_handler in handler_docs.iteritems():
Marius Isken's avatar
Marius Isken committed
513
                bokeh_apps['/plots/%s' % url] = Application(bokeh_handler(
514
515
516
517
                    self.config))
        return bokeh_apps

    def get_tornado_handlers(self):
Marius Isken's avatar
Marius Isken committed
518
519
        return [(r'/', pages.values()[0],
                 {'config': self.config})] +\
520
521
               [(r'/%s' % title, handler,
                 {'config': self.config})
Marius Isken's avatar
Marius Isken committed
522
                for title, handler in pages.iteritems()] +\
Marius Isken's avatar
Marius Isken committed
523
524
               [(r'/res/(.*)', StaticFileHandler,
                {'path': op.join(op.dirname(__file__), 'res')})]
Marius Isken's avatar
Marius Isken committed
525

526
    def start(self, signal=None):
Marius Isken's avatar
Marius Isken committed
527
528
        logger.info('Starting Baraddur server on http://%s:%d'
                    % ('localhost', self.port))
529
530
531
532
533
534
535

        if signal is not None:
            def shutdown():
                if not signal.empty():
                    self.stop()
            tornado.ioloop.PeriodicCallback(shutdown, 2000).start()

536
        BokehServer.start(self)
537
538
539
540
541
542
        self.ioloop.start()

    def stop(self, *args, **kwargs):
        logger.info('Stopping Baraddur server...')
        BokehServer.stop(self)
        self.ioloop.stop()
543
544


Marius Isken's avatar
Marius Isken committed
545
if __name__ == '__main__':
546
547
548
    baraddur = Baraddur(
        rundir='/home/marius/Development/testing/grond/rundir')
    baraddur.start()