diff --git a/livebridge/config.py b/livebridge/config.py index 3a1ad76..19a77b3 100644 --- a/livebridge/config.py +++ b/livebridge/config.py @@ -52,3 +52,6 @@ "password": os.environ.get("LB_WEB_PWD") } } + +PROFILER_INTERVAL = int(os.environ.get("LB_PROFILER_INTERVAL", 0)) + diff --git a/livebridge/profiler.py b/livebridge/profiler.py new file mode 100644 index 0000000..02ced62 --- /dev/null +++ b/livebridge/profiler.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2017 dpa-infocom GmbH +# +# 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 asyncio +import tracemalloc +import os +import linecache +import pprint +import logging +from collections import OrderedDict + +tracemalloc.start() + + +async def run_profiler(interval): + while 1: + await show_traces() + await asyncio.sleep(interval) + +snapshot_prev = None +async def show_traces(): + if tracemalloc.is_tracing(): + snapshot = tracemalloc.take_snapshot() + snapshot = snapshot.filter_traces(( + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, ""), + tracemalloc.Filter(False, linecache.__file__), + tracemalloc.Filter(False, tracemalloc.__file__), + tracemalloc.Filter(False, ""), + )) + await display_top(snapshot) + await display_diff(snapshot) + +async def display_top(snapshot, key_type='lineno', limit=15): + top_stats = snapshot.statistics(key_type) + + logging.debug("\n\n") + logging.debug("Top %s lines" % limit) + for index, stat in enumerate(top_stats[:limit], 1): + frame = stat.traceback[0] + # replace "/path/to/module/file.py" with "module/file.py" + filename = os.sep.join(frame.filename.split(os.sep)[-2:]) + logging.debug("#%s: %s:%s: %.1f KiB %dx" + % (index, filename, frame.lineno, stat.size / 1024, stat.count)) + line = linecache.getline(frame.filename, frame.lineno).strip() + if line: + logging.debug(' %s' % line) + + other = top_stats[limit:] + if other: + size = sum(stat.size for stat in other) + logging.debug("%s other: %.1f KiB" % (len(other), size / 1024)) + total = sum(stat.size for stat in top_stats) + logging.debug("Total allocated size: %.1f KiB\n\n" % (total / 1024)) + +async def display_diff(snapshot, key_type='lineno', limit=15): + global snapshot_prev + if snapshot_prev: + logging.debug("Top %s differences" % limit) + top_diffs= snapshot.compare_to(snapshot_prev, 'lineno') + for x, stat in enumerate([x for x in top_diffs[:limit]], 1): + frame = stat.traceback[0] + # replace "/path/to/module/file.py" with "module/file.py" + filename = os.sep.join(frame.filename.split(os.sep)[-2:]) + logging.debug("#{}: {}:{} {:.1f} KiB {}x (+{})".format( + x, filename, frame.lineno, stat.size / 1024, stat.count, stat.count_diff)) + line = linecache.getline(frame.filename, frame.lineno).strip() + if line: + logging.debug(' %s' % line) + logging.debug("\n\n") + snapshot_prev = snapshot + diff --git a/livebridge/run.py b/livebridge/run.py index 5e658e1..e439186 100644 --- a/livebridge/run.py +++ b/livebridge/run.py @@ -26,7 +26,6 @@ from livebridge.web import WebApi from livebridge.loader import load_extensions - def read_args(**kwargs): """Read controlfile parameter.""" if kwargs.get("control"): @@ -67,6 +66,11 @@ def main(**kwargs): controller = Controller(config=config, control_file=args.control) asyncio.ensure_future(controller.run()) + if config.PROFILER_INTERVAL > 0: + # do some profiling + from livebridge.profiler import run_profiler + asyncio.ensure_future(run_profiler(config.PROFILER_INTERVAL)) + # start http api if config.WEB.get("host") and config.WEB.get("port"): server = WebApi(config=config.WEB, controller=controller, loop=loop)