app.py 12.7 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
    def serve_static(filename):
P
Peter Pan 已提交
194
        print(static_file_path, filename)
195
        return send_from_directory(
196 197
            os.path.join(server_path, static_file_path), filename
            if re.search(r'\..+$', filename) else filename + '.html')
198

P
Peter Pan 已提交
199
    @app.route(api_path + "/components")
200 201 202 203 204
    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 已提交
205
    @app.route(api_path + '/runs')
206
    def runs():
207
        data = cache_get('/data/runs', lib.get_runs, log_reader)
208 209 210
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
211
    @app.route(api_path + '/tags')
212 213 214 215 216
    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 已提交
217
    @app.route(api_path + '/logs')
218 219
    def logs():
        data = cache_get('/data/logs', lib.get_logs, log_reader)
220 221 222
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

P
Peter Pan 已提交
223
    @app.route(api_path + "/scalars/tags")
224 225 226 227 228 229
    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 已提交
230
    @app.route(api_path + "/images/tags")
231
    def image_tags():
232 233
        data = cache_get("/data/plugin/images/tags", try_call,
                         lib.get_image_tags, log_reader)
234 235 236
        result = gen_result(0, "", data)
        return Response(json.dumps(result), mimetype='application/json')

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

P
Peter Pan 已提交
244
    @app.route(api_path + "/embeddings/tags")
245 246 247 248 249 250
    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 已提交
251
    @app.route(api_path + '/scalars/list')
252 253 254 255 256 257 258 259
    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 已提交
260
    @app.route(api_path + '/images/list')
261 262 263 264 265
    def images():
        mode = request.args.get('run')
        tag = request.args.get('tag')
        key = os.path.join('/data/plugin/images/images', mode, tag)

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

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

P
Peter Pan 已提交
272
    @app.route(api_path + '/images/image')
273 274 275 276 277 278 279
    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))
280 281 282
        data = cache_get(key, try_call, lib.get_individual_image, log_reader,
                         mode, tag, step_index)
        return Response(data, mimetype="image/png")
283

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

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

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

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

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

315
        key = os.path.join('/data/plugin/audio/individualAudio', run, tag,
316
                           str(step_index))
317 318
        data = cache_get(key, try_call, lib.get_individual_audio, log_reader,
                         run, tag, step_index)
319 320 321 322 323 324 325 326 327
        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 已提交
328
        # noinspection PyBroadException
329 330 331
        try:
            requests.get(index_url)
            break
332
        except Exception:
333 334 335 336
            time.sleep(0.5)
    webbrowser.open(index_url)


P
Peter Pan 已提交
337 338 339 340 341 342 343 344 345 346 347 348
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)


349 350 351 352 353 354
def _run(logdir,
         host="127.0.0.1",
         port=8080,
         model_pb="",
         cache_timeout=20,
         language=None,
P
Peter Pan 已提交
355
         public_path="/app",
356 357 358 359 360 361 362
         open_browser=False):
    args = ParseArgs(
        logdir=logdir,
        host=host,
        port=port,
        model_pb=model_pb,
        cache_timeout=cache_timeout,
P
Peter Pan 已提交
363 364
        language=language,
        public_path=public_path)
P
Peter Pan 已提交
365 366 367
    render_template(args)
    for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
        signal.signal(sig, clean_template)
368
    logger.info(" port=" + str(args.port))
走神的阿圆's avatar
走神的阿圆 已提交
369
    app = create_app(args)
P
Peter Pan 已提交
370
    index_url = "http://" + host + ":" + str(port) + args.public_path
371
    if open_browser:
372 373 374
        threading.Thread(
            target=_open_browser, kwargs={"app": app,
                                          "index_url": index_url}).start()
375
    app.run(debug=False, host=args.host, port=args.port, threaded=False)
376 377


378 379
def run(logdir,
        host="127.0.0.1",
380
        port=8040,
381 382 383
        model_pb="",
        cache_timeout=20,
        language=None,
P
Peter Pan 已提交
384
        public_path="/app",
385
        open_browser=False):
386 387 388 389 390 391 392
    kwarg = {
        "logdir": logdir,
        "host": host,
        "port": port,
        "model_pb": model_pb,
        "cache_timeout": cache_timeout,
        "language": language,
P
Peter Pan 已提交
393
        "public_path": public_path,
394 395 396 397 398 399 400 401 402 403
        "open_browser": open_browser
    }

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


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


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