app.py 12.5 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 30
from argparse import ArgumentParser

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

import visualdl.server
P
Peter Pan 已提交
35
from visualdl.server import (lib, template)
36 37 38 39 40 41 42 43 44 45 46 47
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 已提交
48 49
static_file_path = os.path.join(SERVER_DIR, "./static")
template_file_path = os.path.join(SERVER_DIR, "./dist")
50 51 52 53
mock_data_path = os.path.join(SERVER_DIR, "./mock_data/")


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


def try_call(function, *args, **kwargs):
    res = lib.retry(error_retry_times, function, error_sleep_time, *args,
                    **kwargs)
    if not res:
P
Peter Pan 已提交
75
        logger.error("Internal server error. Retry later.")
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
    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",
111
        nargs="+",
112 113 114 115 116 117 118
        help="log file directory")
    parser.add_argument(
        "--cache_timeout",
        action="store",
        dest="cache_timeout",
        type=float,
        default=20,
119
        help="memory cache timeout duration in seconds, default 20", )
120 121 122 123 124 125
    parser.add_argument(
        "-L",
        "--language",
        type=str,
        action="store",
        help="set the default language")
P
Peter Pan 已提交
126 127 128 129 130 131 132 133
    parser.add_argument(
        "-P",
        "--public-path",
        type=str,
        action="store",
        default="/app",
        help="set public path"
    )
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

    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


def create_app(args):
    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 已提交
161 162
    app.config['BABEL_DEFAULT_LOCALE'] = default_language
    babel = Babel(app)
163 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)

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

P
Peter Pan 已提交
172 173
    @babel.localeselector
    def get_locale():
P
Peter Pan 已提交
174 175 176 177
        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 已提交
178

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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


P
Peter Pan 已提交
384 385 386 387 388 389 390 391 392 393 394 395
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)


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


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