app.py 12.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#!/user/bin/env python

# Copyright (c) 2017 VisualDL Authors. All Rights Reserve.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# =======================================================================

import json
import os
import time
import sys
P
Peter Pan 已提交
22
import signal
23 24 25 26 27
import multiprocessing
import threading
import re
import webbrowser
import requests
28
from visualdl.reader.reader import LogReader
29
from argparse import ArgumentParser
走神的阿圆's avatar
走神的阿圆 已提交
30
from visualdl.utils import update_util
31

P
Peter Pan 已提交
32
from flask import (Flask, Response, redirect, request, send_file, send_from_directory)
P
Peter Pan 已提交
33
from flask_babel import Babel
34 35

import visualdl.server
P
Peter Pan 已提交
36
from visualdl.server import (lib, template)
37 38 39 40 41 42 43 44 45 46 47 48
from visualdl.server.log import logger
from visualdl.python.cache import MemCache

error_retry_times = 3
error_sleep_time = 2  # seconds

SERVER_DIR = os.path.join(visualdl.ROOT, 'server')

support_language = ["en", "zh"]
default_language = support_language[0]

server_path = os.path.abspath(os.path.dirname(sys.argv[0]))
P
Peter Pan 已提交
49 50
static_file_path = os.path.join(SERVER_DIR, "./static")
template_file_path = os.path.join(SERVER_DIR, "./dist")
51 52 53 54
mock_data_path = os.path.join(SERVER_DIR, "./mock_data/")


class ParseArgs(object):
55 56 57 58 59 60
    def __init__(self,
                 logdir,
                 host="0.0.0.0",
                 port=8040,
                 model_pb="",
                 cache_timeout=20,
P
Peter Pan 已提交
61 62
                 language=None,
                 public_path=None):
63 64 65 66 67 68
        self.logdir = logdir
        self.host = host
        self.port = port
        self.model_pb = model_pb
        self.cache_timeout = cache_timeout
        self.language = language
P
Peter Pan 已提交
69
        self.public_path = public_path
70 71 72 73 74 75


def try_call(function, *args, **kwargs):
    res = lib.retry(error_retry_times, function, error_sleep_time, *args,
                    **kwargs)
    if not res:
P
Peter Pan 已提交
76
        logger.error("Internal server error. Retry later.")
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
    return res


def parse_args():
    """
    :return:
    """
    parser = ArgumentParser(
        description="VisualDL, a tool to visualize deep learning.")
    parser.add_argument(
        "-p",
        "--port",
        type=int,
        default=8040,
        action="store",
        dest="port",
        help="api service port")
    parser.add_argument(
        "-t",
        "--host",
        type=str,
        default="0.0.0.0",
        action="store",
        help="api service ip")
    parser.add_argument(
        "-m",
        "--model_pb",
        type=str,
        action="store",
        help="model proto in ONNX format or in Paddle framework format")
    parser.add_argument(
        "--logdir",
        required=True,
        action="store",
        dest="logdir",
112
        nargs="+",
113 114 115 116 117 118 119
        help="log file directory")
    parser.add_argument(
        "--cache_timeout",
        action="store",
        dest="cache_timeout",
        type=float,
        default=20,
120
        help="memory cache timeout duration in seconds, default 20", )
121 122 123 124 125 126
    parser.add_argument(
        "-L",
        "--language",
        type=str,
        action="store",
        help="set the default language")
P
Peter Pan 已提交
127 128 129 130 131 132 133 134
    parser.add_argument(
        "-P",
        "--public-path",
        type=str,
        action="store",
        default="/app",
        help="set public path"
    )
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

    args = parser.parse_args()
    if not args.logdir:
        parser.print_help()
        sys.exit(-1)
    return args


# status, msg, data
def gen_result(status, msg, data):
    """
    :param status:
    :param msg:
    :return:
    """
    result = dict()
    result['status'] = status
    result['msg'] = msg
    result['data'] = data
    return result


走神的阿圆's avatar
走神的阿圆 已提交
157
def create_app(args):
158 159 160 161
    app = Flask(__name__, static_url_path="")
    # set static expires in a short time to reduce browser's memory usage.
    app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 30

P
Peter Pan 已提交
162 163
    app.config['BABEL_DEFAULT_LOCALE'] = default_language
    babel = Babel(app)
164 165 166 167 168
    log_reader = LogReader(args.logdir)

    # use a memory cache to reduce disk reading frequency.
    CACHE = MemCache(timeout=args.cache_timeout)
    cache_get = lib.cache_get(CACHE)
走神的阿圆's avatar
走神的阿圆 已提交
169
    update_util.PbUpdater().start()
170

P
Peter Pan 已提交
171 172 173
    public_path = args.public_path.rstrip('/')
    api_path = public_path + '/api'

P
Peter Pan 已提交
174 175
    @babel.localeselector
    def get_locale():
P
Peter Pan 已提交
176 177 178 179
        lang = args.language
        if not lang or lang not in support_language:
            lang = request.accept_languages.best_match(support_language)
        return lang
P
Peter Pan 已提交
180

181
    @app.route("/")
P
Peter Pan 已提交
182 183 184 185
    def base():
        return redirect(public_path, code=302)

    @app.route(public_path + "/")
186
    def index():
P
Peter Pan 已提交
187 188 189 190
        lang = get_locale()
        if lang == default_language:
            return redirect(public_path + '/index', code=302)
        return redirect(public_path + '/' + lang + '/index', code=302)
191

P
Peter Pan 已提交
192
    @app.route(public_path + '/<path:filename>')
193 194
    def serve_static(filename):
        return send_from_directory(
195 196
            os.path.join(server_path, static_file_path), filename
            if re.search(r'\..+$', filename) else filename + '.html')
197

P
Peter Pan 已提交
198
    @app.route(api_path + "/components")
199 200 201 202 203
    def components():
        data = cache_get('/data/components', lib.get_components, log_reader)
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
204
    @app.route(api_path + '/runs')
205
    def runs():
206
        data = cache_get('/data/runs', lib.get_runs, log_reader)
207 208 209
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
210
    @app.route(api_path + '/tags')
211 212 213 214 215
    def tags():
        data = cache_get('/data/tags', lib.get_tags, log_reader)
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
216
    @app.route(api_path + '/logs')
217 218
    def logs():
        data = cache_get('/data/logs', lib.get_logs, log_reader)
219 220 221
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
222
    @app.route(api_path + "/scalars/tags")
223 224 225 226 227 228
    def scalar_tags():
        data = cache_get("/data/plugin/scalars/tags", try_call,
                         lib.get_scalar_tags, log_reader)
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
229
    @app.route(api_path + "/images/tags")
230
    def image_tags():
231 232
        data = cache_get("/data/plugin/images/tags", try_call,
                         lib.get_image_tags, log_reader)
233 234 235
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
236
    @app.route(api_path + "/audio/tags")
237
    def audio_tags():
238 239
        data = cache_get("/data/plugin/audio/tags", try_call,
                         lib.get_audio_tags, log_reader)
240 241 242
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
243
    @app.route(api_path + "/embeddings/tags")
244 245 246 247 248 249
    def embeddings_tags():
        data = cache_get("/data/plugin/embeddings/tags", try_call,
                         lib.get_embeddings_tags, log_reader)
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
250
    @app.route(api_path + '/scalars/list')
251 252 253 254 255 256 257 258
    def scalars():
        run = request.args.get('run')
        tag = request.args.get('tag')
        key = os.path.join('/data/plugin/scalars/scalars', run, tag)
        data = cache_get(key, try_call, lib.get_scalar, log_reader, run, tag)
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
259
    @app.route(api_path + '/images/list')
260 261 262 263 264
    def images():
        mode = request.args.get('run')
        tag = request.args.get('tag')
        key = os.path.join('/data/plugin/images/images', mode, tag)

265 266
        data = cache_get(key, try_call, lib.get_image_tag_steps, log_reader,
                         mode, tag)
267 268 269 270
        result = gen_result(0, "", data)

        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
271
    @app.route(api_path + '/images/image')
272 273 274 275 276 277 278
    def individual_image():
        mode = request.args.get('run')
        tag = request.args.get('tag')  # include a index
        step_index = int(request.args.get('index'))  # index of step

        key = os.path.join('/data/plugin/images/individualImage', mode, tag,
                           str(step_index))
279 280 281
        data = cache_get(key, try_call, lib.get_individual_image, log_reader,
                         mode, tag, step_index)
        return Response(data, mimetype="image/png")
282

P
Peter Pan 已提交
283
    @app.route(api_path + '/embeddings/embedding')
284 285
    def embeddings():
        run = request.args.get('run')
走神的阿圆's avatar
走神的阿圆 已提交
286
        tag = request.args.get('tag', 'default')
287 288
        dimension = request.args.get('dimension')
        reduction = request.args.get('reduction')
289 290 291
        key = os.path.join('/data/plugin/embeddings/embeddings', run,
                           dimension, reduction)
        data = cache_get(key, try_call, lib.get_embeddings, log_reader, run,
292
                         tag, reduction, int(dimension))
293 294 295
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
296
    @app.route(api_path + '/audio/list')
297
    def audio():
298
        run = request.args.get('run')
299
        tag = request.args.get('tag')
300
        key = os.path.join('/data/plugin/audio/audio', run, tag)
301

302 303
        data = cache_get(key, try_call, lib.get_audio_tag_steps, log_reader,
                         run, tag)
304 305 306 307
        result = gen_result(0, "", data)

        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
308
    @app.route(api_path + '/audio/audio')
309
    def individual_audio():
310
        run = request.args.get('run')
311 312 313
        tag = request.args.get('tag')  # include a index
        step_index = int(request.args.get('index'))  # index of step

314
        key = os.path.join('/data/plugin/audio/individualAudio', run, tag,
315
                           str(step_index))
316 317
        data = cache_get(key, try_call, lib.get_individual_audio, log_reader,
                         run, tag, step_index)
318 319 320 321 322 323 324 325 326
        response = send_file(
            data, as_attachment=True, attachment_filename='audio.wav')
        return response

    return app


def _open_browser(app, index_url):
    while True:
P
Peter Pan 已提交
327
        # noinspection PyBroadException
328 329 330
        try:
            requests.get(index_url)
            break
331
        except Exception:
332 333 334 335
            time.sleep(0.5)
    webbrowser.open(index_url)


336 337 338 339 340 341
def _run(logdir,
         host="127.0.0.1",
         port=8080,
         model_pb="",
         cache_timeout=20,
         language=None,
P
Peter Pan 已提交
342
         public_path="/app",
343 344 345 346 347 348 349
         open_browser=False):
    args = ParseArgs(
        logdir=logdir,
        host=host,
        port=port,
        model_pb=model_pb,
        cache_timeout=cache_timeout,
P
Peter Pan 已提交
350 351
        language=language,
        public_path=public_path)
352
    logger.info(" port=" + str(args.port))
走神的阿圆's avatar
走神的阿圆 已提交
353
    app = create_app(args)
P
Peter Pan 已提交
354
    index_url = "http://" + host + ":" + str(port) + args.public_path
355
    if open_browser:
356 357 358
        threading.Thread(
            target=_open_browser, kwargs={"app": app,
                                          "index_url": index_url}).start()
359 360 361
    app.run(debug=False, host=args.host, port=args.port, threaded=True)


362 363
def run(logdir,
        host="127.0.0.1",
364
        port=8040,
365 366 367
        model_pb="",
        cache_timeout=20,
        language=None,
P
Peter Pan 已提交
368
        public_path="/app",
369
        open_browser=False):
370 371 372 373 374 375 376
    kwarg = {
        "logdir": logdir,
        "host": host,
        "port": port,
        "model_pb": model_pb,
        "cache_timeout": cache_timeout,
        "language": language,
P
Peter Pan 已提交
377
        "public_path": public_path,
378 379 380 381 382 383 384 385
        "open_browser": open_browser
    }

    p = multiprocessing.Process(target=_run, kwargs=kwarg)
    p.start()
    return p.pid


P
Peter Pan 已提交
386 387 388 389 390 391 392 393 394 395 396 397
def render_template(args):
    template.render(
        template_file_path,
        static_file_path,
        PUBLIC_PATH=args.public_path.strip('/'))


def clean_template(signalnum, frame):
    template.clean(static_file_path)
    sys.exit(0)


398 399
def main():
    args = parse_args()
P
Peter Pan 已提交
400 401 402
    render_template(args)
    for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
        signal.signal(sig, clean_template)
403
    logger.info(" port=" + str(args.port))
走神的阿圆's avatar
走神的阿圆 已提交
404
    app = create_app(args)
405
    app.run(debug=False, host=args.host, port=args.port, threaded=False)
406 407 408


if __name__ == "__main__":
P
Peter Pan 已提交
409
    main()