vieyahn2017 / iBlog

44 stars 0 forks source link

6.6 Python内存监控工具memory_profiler和guppy的用法 #148

Closed vieyahn2017 closed 4 years ago

vieyahn2017 commented 6 years ago

python内存监控工具memory_profiler和guppy的用法 https://blog.csdn.net/meccaendless/article/details/79701726

vieyahn2017 commented 4 years ago

python2.7在内存管理上相比python3还是有些坑的,其释放后的内存仍然保留在python的内存池中,不被系统所用。python循环引用的变量不会被回收,这会导致程序越运行,占用的内存越大。我在跑py-faster-rcnn的demo时,基本上跑2000张图像,16g内存就要爆了。于是尝试用python的内存监控工具来调试程序,找到不能膨胀的变量,然后del之,再手动回收内存gc.collec()

下面是我用的两个内存监视工具,一个是按每行代码查看内存占用的工具memory_profiler,一个是查看占用内存前十位变量的工具guppy。

vieyahn2017 commented 4 years ago

1. memory_profiler

首先是安装:

pip install -U memory_profiler

然后用profile修饰想要查看的函数名:如:

@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == '__main__':
    my_func()

输出结果:

Line #    Mem usage  Increment   Line Contents
==============================================
     3                           @profile
     4      5.97 MB    0.00 MB   def my_func():
     5     13.61 MB    7.64 MB       a = [1] * (10 ** 6)
     6    166.20 MB  152.59 MB       b = [2] * (2 * 10 ** 7)
     7     13.61 MB -152.59 MB       del b
     8     13.61 MB    0.00 MB       return a

memory_profiler功能强大,更多功能可以看官网这里

vieyahn2017 commented 4 years ago

2. guppy

首先安装:

pip install guppy

然后import下

from guppy import hpy
hxx = hpy()
heap = hxx.heap()
byrcs = hxx.heap().byrcs; 

在主程序下增加:

print(heap) 输出示例:

Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  10124  22 81944416  95  81944416  95 list
     1  16056  34  1325464   2  83269880  96 str
     2   9147  20   745616   1  84015496  97 tuple
     3    102   0   366480   0  84381976  98 dict of module
     4    287   1   313448   0  84695424  98 dict of type
     5   2426   5   310528   0  85005952  98 types.CodeType
     6   2364   5   283680   0  85289632  99 function
     7    287   1   256960   0  85546592  99 type
     8    169   0   192088   0  85738680  99 dict (no owner)
     9    123   0   142728   0  85881408  99 dict of class

可以看到第一个list占了95%的内存,若print(heap)在主程序的循环中,可以查看每次循环后的变量内存占用情况。

输入以下命令,查看这个占内存最大的list中的数据类型:

byrcs[0].byid

最后测试后发现,test.py下get_im_blob等函数占用内存不断增大,每检测一副图像,该函数增加6-10MB内存开销。但奇怪的是用guppy查看前十个变量,并没有发现哪个变量有明显的内存增大迹象。于是猜测可能是每张图像推理后,推理的结果bbox,label,img等数据保存在了内存中,这样方便所有图像推理结束后,plt.show().于是修改程序,每张图像推理后,plt.show()一下。用memory_profiler发现内存不再继续增大,interesting!其实把plt.show()改成plt.close()也可以防止内存不断增大。具体原因肯定是python 的内存回收机制规则导致的。

vieyahn2017 commented 4 years ago

python程序内存泄漏调试记录

原创ybdesire 最后发布于2017-06-13 22:41:20 阅读数 8709 收藏 https://blog.csdn.net/ybdesire/java/article/details/73128353

问题描述

调试python程序时,用下面这段代码,可以获得进程占用系统内存值。程序跑一段时间后,就能画出进程对内存的占用情况。

def memory_usage_psutil():
    # return the memory usage in MB
    import psutil,os
    process = psutil.Process(os.getpid())
    mem = process.memory_info()[0] / float(2 ** 20)
    return mem

发现进程的内存占用一直再上涨,而这从逻辑上来说是不正常的,所以想到程序可能发生了Memory Leak。

python程序的Mem Leak

python程序不可能像C/C++一样出现malloc了的内存没有free这样的Memory Leak。但也会遇到“逻辑上没free”的情况,如下代码所示。

def foo(a=[]):
    a.append(time.time())
    return a

参数a这样可迭代的对象,稍不注意,它就能增长的很快。说白了,python的Memory Leak,就是“进程占用的内存莫名其妙一直再升高”。进程占用内存一直升高,与逻辑预期不一致,就可能发生了Memory Leak。

以下面程序为例说明Memory Leak调试的过程:

def memory_usage_psutil():
    # return the memory usage in MB
    import psutil,os
    process = psutil.Process(os.getpid())
    mem = process.memory_info()[0] / float(2 ** 20)
    return mem

def get_current_obj(a=[]):
    a.append([0]*1000)
    return a

def main():    
    obj = []
    for i in range(10000):
        obj = get_current_obj(obj)
        if(i%100==0):
            print(memory_usage_psutil())

if __name__=='__main__':
    main()

调试过程

用pmap -x [pid]查看进程占用的堆内存大小 首先想到,会不会是上面用的memory_usage_psutil函数统计错误呢。

先运行程序,再用pmap查看,发现进程内存占用确实很高。多次执行该命令,也可以发现内存一直升高。

强制执行GC(gc.collect()) 在需要执行GC的地方加上gc.collect()

def main():    
    obj = []
    for i in range(10000):
        obj = get_current_obj(obj)
        import gc;gc.collect()
        if(i%100==0):
            print(memory_usage_psutil())

可以看到,强制GC后,程序执行变慢,但内存依然不断升高。

使用memory_profiler查看

安装memory_profiler pip install -U memory_profiler

用@profile修饰需要查看内存的函数

@profile
def main():    
    obj = []
    for i in range(10000):
        obj = get_current_obj(obj)
        if(i%100==0):
            print(memory_usage_psutil())

用如下命令运行程序 python -m memory_profiler main.py

可以看到程序执行完成后,输出结果如下

Line #    Mem usage    Increment   Line Contents
================================================
    12   28.570 MiB    0.000 MiB   @profile
    13                             def main():
    14   28.570 MiB    0.000 MiB       obj = []
    15  106.203 MiB   77.633 MiB       for i in range(10000):
    16  106.203 MiB    0.000 MiB           obj = get_current_obj(obj)
    17  106.203 MiB    0.000 MiB           if(i%100==0):
    18  105.445 MiB   -0.758 MiB               print(memory_usage_psutil())

这样就能看到导致内存上涨最快的那几行代码。

用guppy查看python对象占用的堆内存大小

将main修改如下,即可查看python对堆内存的占用量。

def main():    
    obj = []
    for i in range(10000):
        obj = get_current_obj(obj)
        if(i%100==0):
            print(memory_usage_psutil())
            from guppy import hpy;hxx = hpy();heap = hxx.heap()
            print(heap)

下面就是输出结果,python程序中各个对象对内存的占用从大到小排列。

 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  10124  22 81944416  95  81944416  95 list
     1  16056  34  1325464   2  83269880  96 str
     2   9147  20   745616   1  84015496  97 tuple
     3    102   0   366480   0  84381976  98 dict of module
     4    287   1   313448   0  84695424  98 dict of type
     5   2426   5   310528   0  85005952  98 types.CodeType
     6   2364   5   283680   0  85289632  99 function
     7    287   1   256960   0  85546592  99 type
     8    169   0   192088   0  85738680  99 dict (no owner)
     9    123   0   142728   0  85881408  99 dict of class

可以从结果中看到,95%的进程内存,都被一个list占用。

还可以通过下面这种方式,查看这个占内存最大的list中的数据类型。

from guppy import hpy;hxx = hpy();byrcs = hxx.heap().byrcs; byrcs[0].byid

vieyahn2017 commented 4 years ago

一次python内存溢出的排查

原创99zhenzhen 最后发布于2017-06-10 21:46:55 阅读数 15603 收藏 https://blog.csdn.net/kelindame/article/details/73008487

最近在我的项目中,出现了内存持续增长的情况。这也是我第一次碰到这种情况,以前在写c++都没试过,难得啊,所以记录,积累一下经验。

一、项目背景

生产者和消费者,操作kafka,使用的是pykafka库。消费者从kafka获取消息后,发送给业务处理服务,使用的是requests库。

二、工具

在此次过程中,使用过的工具或者库。 1)、memory_profiler库。 可以使用这个库监控内存的消耗,以及基于行的内存分析 官方文档:https://pypi.python.org/pypi/memory_profiler

2)、objgraph库。 可以显示占据python程序内存的前n个对象 可以统计一段时间内,对象增加减少的数量 可以以图片形式,展现对象的引用关系 官网文档:http://mg.pov.lt/objgraph/index.html

3)graphviz工具 将objgraph库生成的dot文件生成图片

三、初次分析

使用python开发程序,内存泄露的几个基本原因: 1、存在循环引用,gc不能释放; 2、存在全局对象,该对象不断的变大,占据内存; 3、使用了c或者c++扩展,扩展内存溢出了; 初次分析的时候,先进行代码review,并没有发现存在循环引用的变量和全局对象,而且我也没有调用c或者c++扩展。所以以上三点排除。 使用memory_profiler库,将输出记录到文本中进行分析。

发现并没有内存增加的情况。 后面加上gc.collect()也没有效果。

四、二次分析

第一分析无果,然后当时没想到什么方法。只能暴力,注释一部分代码进行排查。发现注释掉将消息发送给业务服务的代码,内存不会持续增长。 怀疑是不是requests库的问题。然后进行google:requests memory leak,发现还真有这个问题。地址:https://github.com/requests/requests/issues/1685 他们的讨论非常精彩。不过上面的讨论是在pypy下,requests出现内存泄露。而我用的Cpython。而且后面的pypy版本也已经修复了这个bug。不过当时不信邪, 还是认为requests库有问题,所以改换成python3的urllib去发送信息,发现结果还是一样。然后网上一查,特么requests底层是封装url库的。已哭晕在厕所。

五、三次分析

第二次分析还是没有结果。只能看是不是有循环引用,导致内存泄露。使用objgraph查看引用和对象生成关系。 1、使用objgraph.show_most_common_types(limit=10),查看占据内存前10的对象变化关系; 一开始:

运行一段时间后:

发现没太大变化的对象。 2、使用objgraph.show_growth(),观察对象增长情况。

grep "^Message" 1.txt

发现Message对象不断的增加。 3、查看Message到底是何方妖怪。 1)使用pdb断点进行查看:

发现Message是pykafka的Message实列。 2)进行引用查看。

由于没有安装xdot,使用graphviz工具查看。

发现没有出现循环引用。 3、继续单点调试。

发现这个Message不断的变大。这时候怀疑是不是pykafka这个库导致的。然后一查。厉害了,我的哥 ,pykafka的balancedconsumer类有个参数queued_max_messages。 这个参数的意思balancedconsumer会为每个分区缓存消息,默认一个分区是2000条。如果一条消息是1M,只有一个分区的话,就缓存了2000M,对于不够内存的机器那就gg了。 至此,问题找到了。解决方法是将这个参数改小点就好了。

vieyahn2017 commented 4 years ago

定位python内存泄漏问题

原创Q博士 最后发布于2019-07-10 22:57:02 阅读数 1558 收藏

https://blog.csdn.net/qhshiniba/java/article/details/95392266

记一次 Python 内存泄漏的排查

背景 上周使用我的python web框架开发的第二个项目上线了,但是没运行几天机器内存就报警了,8G内存使用了7G,怀疑有内存泄漏,这个项目提供的功能就是一堆机器学习模型,对历史数据进行训练,挑选出最优的5个模型,用作未来数据的预测,所以整个项目有着数据量大,运行时间长的特点,就是把策略的离线工作搬到了线上。

定位内存泄漏

第一步:确定是否有内存泄漏

上pympler检查是否有内存泄漏,程序入口处初始化该工具

from pympler import tracker,summary,muppy
memory_tracker = tracker.SummaryTracker()

接口返回处打印内存差异,观察内存是否有泄漏

memory_tracker.print_diff() # 本次内存和上次内存块的差异

我们用的sanic,所以直接在main.py文件添加如下代码:

from pympler import tracker,summary,muppy
memory_tracker = tracker.SummaryTracker()

@app.middleware('request')
async def set_request_id(request):
    log_id = request.headers.get('log-id')
    threading.currentThread().logid = log_id
    gc.collect()
    memory_tracker.print_diff()

然后我们访问接口,多触发几次,不用看前两次,等输出稳定后,如果有内存泄漏是如下输出:

上图显示每次都有4类泄漏对象,一共泄漏约60K的内存

如果没有内存泄漏,没有数据输出

第二步:确定内心泄漏的代码块

我们确定程序有内存泄漏后,就想办法定位到代码块,就是我们自己写的代码,通过一步一步debug,注释,return,continue等方式定位到造成泄漏的代码块,下面的代码块就是遍历所有模型,然后挨个执行训练方法,因为有20多个模型,我不能挨个注释每次对象来定位,卡在这里了。

第三步:确定泄漏点 上tracemalloc定位泄漏点,python3.7.3自带,在main.py中添加如下代码:


tracemalloc.start(25)
snapshot = tracemalloc.take_snapshot()
@app.middleware('response')
async def print_on_response(request, response):
    global snapshot
    gc.collect()
    snapshot1 = tracemalloc.take_snapshot()
    top_stats = snapshot1.compare_to(snapshot, 'lineno')
    print("[ Top 10 differences ]")
    for stat in top_stats[:10]:
         if stat.size_diff < 0:
            continue
         print(stat)
    snapshot = tracemalloc.take_snapshot()

继续访问接口,多访问几次,输出如下,直接定位到具体泄漏的代码位置

图中所有的泄漏点都定位到pandas库,但是我用这些文件搜索内存泄漏,都没有搜到相关内存泄漏的问题,所以得寻找谁调用这些地方,以/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py:859为例,我们要找到我们写的代码哪里调用触发泄漏点

第四步:打印调用链

看了tracemalloc文档也没找到打印调用链的方法,后来灵机一动直接在这个文件加了下面代码:

raise Exception("doctorq")

然后在接口里catch异常,添加logging.exception(e),然后触发接口,打印堆栈信息:

ERROR:root:doctorq
Traceback (most recent call last):
  File "/home/doctorq/python-dev/scscore/src/forecasting/forecast.py", line 83, in update_method
    n_fraction=n_fraction)
  File "/home/doctorq/python-dev/scscore/src/forecasting/trainer.py", line 113, in training
    n_fraction=n_fraction)
  File "/home/doctorq/python-dev/scscore/src/forecasting/trainer.py", line 205, in train_machine_learning_model
    is_train=True).dropna()
  File "/home/doctorq/python-dev/scscore/src/feature_engineering/features.py", line 34, in get_feature
    history_same_periods=history_same_periods, zero_replace=zero_replace)
  File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 65, in get_feature
    store_and_sku=store_and_sku)
  File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 85, in get_rolling_feature
    rolling_result = self.get_rolling_result(window, rolling_obj, rolling_types)
  File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 169, in get_rolling_result
    rolling_result = self.rolling__(rolling_obj, rolling_type)
  File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 190, in rolling__
    return rolling_obj.min()
  File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 1723, in min
    return super(Rolling, self).min(*args, **kwargs)
  File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 1069, in min
    return self._apply('roll_min', 'min', **kwargs)
  File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 879, in _apply
    result = np.apply_along_axis(calc, self.axis, values)
  File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/numpy/lib/shape_base.py", line 380, in apply_along_axis
    res = asanyarray(func1d(inarr_view[ind0], *args, **kwargs))
  File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 875, in calc
    closed=self.closed)
  File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 858, in func
    raise Exception("doctorq")
Exception: doctorq

定位到我们代码触发点如下:

调用的就是pandas的Rolling的一系列方法,然后搜索该方法是否有泄漏问题

第一个链接链接就是说这些方法(rolling.min/max)有泄漏,pandas rolling max leak memory,具体因为啥泄漏的,也没时间细究,反正issue里说回退到0.23.4是没问题的,那么就回退试试:

pipenv install pandas==0.23.4

然后我们再用pympler定位有没有内存泄漏,pandas内存泄漏的问题是修复,剩下来就省memoryview的小泄漏了,明天继续

总结

定位的过程略耗时,不过经过这么一折腾,也算是有经验了,各种工具一阵堆,泄漏问题确定-定位代码块-定位泄漏点-搜索已知泄漏点-解决掉。

vieyahn2017 commented 4 years ago

另外,python本身自带了内存分析模块

Python性能分析器Profile

https://blog.csdn.net/kongxx/java/article/details/52216850

在使用Python进行开发的过程中,有时候我们需要对Python程序的执行性能进行分析,此时我们就需要用到Python的性能分析工具,这里我就简单说说Python中的profile和pstats库。

下面是一个测试程序

import os

def a():
    sum = 0
    for i in range(1, 10001):
        sum += i
    return sum

def b():
    sum = 0
    for i in range(1, 100):
        sum += a()
    return sum

print b()

此时我们可以使用下面的命令来查看分析结果

python -m cProfile test.py

运行结果如下:

$ python -m cProfile test.py
4950495000
         202 function calls in 0.040 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.040    0.040 test.py:1(<module>)
       99    0.035    0.000    0.040    0.000 test.py:3(a)
        1    0.000    0.000    0.040    0.040 test.py:9(b)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      100    0.005    0.000    0.005    0.000 {range}

其中:

也可以使用下面的命令把结果保存在一个文件里

python -m cProfile -o result test.py

然后使用pstats来格式化显示结果

python -c "import pstats; pstats.Stats('result').sort_stats(-1).print_stats()"

Thu Aug 11 13:20:20 2016    result

         202 function calls in 0.037 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.037    0.037 test.py:1(<module>)
       99    0.032    0.000    0.036    0.000 test.py:3(a)
        1    0.000    0.000    0.037    0.037 test.py:9(b)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
      100    0.004    0.000    0.004    0.000 {range}

另外,我们也可以在代码里直接嵌入代码来使用cProfile和pstats模块,这样在程序退出时就会自动生成分析结果并打印,如下:

import os

def a():
    sum = 0
    for i in range(1, 10001):
        sum += i
    return sum

def b():
    sum = 0
    for i in range(1, 100):
        sum += a()
    return sum

print b()

import cProfile
#cProfile.run("b()")
cProfile.run("b()", "result")

import pstats
pstats.Stats('result').sort_stats(-1).print_stats()

此时运行上面的程序就只要使用下面的命令

python test.py

vieyahn2017 commented 4 years ago

python 性能分析器 profile

的可视化工具 https://blog.csdn.net/u012409883/article/details/53039568

snakeviz

首先安装easy_install 点击打开链接  安装snakeviz命令:easy_install snakeviz

然后就开始实战吧

# -*- coding:utf-8 -*-
def fun1():
  for i in range(0, 1000):
    pass

def fun2():
  for i in range(0, 10000):
    pass
  fun1()

import cProfile
cProfile.run('fun2()', "result")

在命令行运行:snakeviz result 会生成一个html的分析报告

vieyahn2017 commented 4 years ago

分析报告(html) 网页源码如下


<!doctype html>
<html>
<head>
<meta charset="utf-8" .>
<title>C:\Users\ywx536122\Downloads\result</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/snakeviz.css" rel="stylesheet">
<!-- DataTables CSS -->
<link href="/static/vendor/jquery.dataTables.min.css" rel="stylesheet">
</head>
<body>
<h1 id="snakeviz-text">
<a href="https://jiffyclub.github.io/snakeviz/">SnakeViz</a>
</h1>
<!-- reset button -->
<span id="resetbuttons">
<div class="button-div">
<button id="resetbutton-root" disabled="True">Reset Root</button>
</div>
<div class="button-div">
<button id="resetbutton-zoom" disabled="True">Reset Zoom</button>
</div>
</span>
<!-- style select -->
<label id='sv-style-label'>Style:
<select name="sv-style" id="sv-style-select">
<option value="icicle" selected>Icicle</option>
<option value="sunburst">Sunburst</option>
</select>
</label>
<!-- depth select -->
<label id='sv-depth-label'>Depth:
<select name="sv-depth" id="sv-depth-select">

<option value="3" >3</option>

<option value="5" >5</option>

<option value="10" selected>10</option>

<option value="15" >15</option>

<option value="20" >20</option>

</select>
</label>
<!-- cutoff select -->
<label id='sv-cutoff-label'>Cutoff:
<select name="sv-cutoff" id="sv-cutoff-select">
<option value="0.001" selected>1 &frasl; 1000</option>
<option value="0.01">1 &frasl; 100</option>
<option value="0">None</option>
</select>
</label>
<!-- information div -->
<div id="sv-info-div"></div>
<!-- call stack -->
<div id="sv-call-stack">
<div id="working-spinner" class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
<div style="display: inline-block;">
<button id="sv-call-stack-btn">Call Stack</button>
</div>
<div id="sv-call-stack-list"></div>
</div>
<!-- Error message -->
<div id="sv-error-div" class="sv-error-msg">
<p>
An error occurred processing your profile.
You can try a lower depth, a larger cutoff,
or try profiling a smaller portion of your code.
If you continue to have problems you can
<a href="https://github.com/jiffyclub/snakeviz/issues">
contact us on GitHub</a>.
</p>
<div id="sv-error-close-div" class="sv-error-close">Close</div>
</div>
<!-- vis -->
<div style="text-align: center;">
<div id="chart"></div>
</div>
<br>
<!-- stats table -->
<div id="table_div">
<table cellpadding="0" cellspacing="0" border="0" class="display" id="pstats-table">
</table>
</div>
<!-- footer -->
<footer class="sv-footer">
<a class="footer-link" href="https://jiffyclub.github.io/snakeviz/">SnakeViz Docs</a>
</footer>
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<!-- Vendor JS -->
<script src="/static/vendor/jquery-3.2.1.min.js"></script>
<script src="/static/vendor/d3.v3.min.js"></script>
<script src="/static/vendor/jquery.dataTables.min.js"></script>
<script src="/static/vendor/lodash.min.js"></script>
<script src="/static/vendor/immutable.min.js"></script>
<!-- SnakeViz JS -->
<script>
// Make the stats table
var table_data = [[['1', 1], '1.117e-06', '1.117e-06', '1.117e-06', '1.117e-06', '~:0(&lt;method &#39;disable&#39; of &#39;_lsprof.Profiler&#39; objects&gt;)', "~:0(<method 'disable' of '_lsprof.Profiler' objects>)"], [['1', 1], '0.0001905', '0.0001905', '0.0003439', '0.0003439', '2.py:6(fun2)', '2.py:6(fun2)'], [['1', 1], '2.179e-05', '2.179e-05', '2.822e-05', '2.822e-05', '2.py:2(fun1)', '2.py:2(fun1)'], [['2', 2], '0.0001316', '6.579e-05', '0.0001316', '6.579e-05', '~:0(&lt;range&gt;)', '~:0(<range>)'], [['1', 1], '3.911e-06', '3.911e-06', '0.0003478', '0.0003478', '&lt;string&gt;:1(&lt;module&gt;)', '<string>:1(<module>)']];
$(document).ready(function() {
var table = $('#pstats-table').dataTable({
'data': table_data,
'columns': [
{'title': 'ncalls', 'type': 'num', 'searchable': 'false',
'data': {
'_': function (row) {return row[0][0];},
'sort': function (row) {return row[0][1]}
}},
{'title': 'tottime', 'type': 'num', 'searchable': 'false'},
{'title': 'percall', 'type': 'num', 'searchable': 'false'},
{'title': 'cumtime', 'type': 'num', 'searchable': 'false'},
{'title': 'percall', 'type': 'num', 'searchable': 'false'},
{'title': 'filename:lineno(function)'}
],
'order': [1, 'desc'],
'paging': false
}).api();
$('#pstats-table tbody').on('click', 'tr', function() {
var name = table.row(this).data()[6];
sv_root_func_name = name;
sv_draw_vis(name);
sv_call_stack = [name];
sv_update_call_stack_list();
$('#resetbutton-zoom').prop('disabled', true);
$('#resetbutton-root').prop('disabled', false);
}).on('mouseenter', 'tr', function () {
$(this).children('td').addClass('data-table-hover');
}).on('mouseleave', 'tr', function () {
$(this).children('td').removeClass('data-table-hover');
});
});
</script>
<!-- Web worker code for generating D3 JSON in a separate thread -->
<script id="hierarchy-worker" type="javascript/worker">
// This will all go into a web worker that will be used to generate
// the visualization JSON while leaving the rest of the app responsive.
//
// We put this here instead of in a separate JS file so that the worker
// can be stopped and restarted without loading the code from the server,
// and so that the stats data can be embedded in the worker.
var stats = {'2.py:6(fun2)': {'display_name': '2.py:6(fun2)', 'callers': {'<string>:1(<module>)': [1, 1, 0.00019052700832088994, 0.00034389845636805796]}, 'stats': [1, 1, 0.00019052700832088994, 0.00034389845636805796], 'children': {'~:0(<range>)': [1, 1, 0.00012515557144832654, 0.00012515557144832654], '2.py:2(fun1)': [1, 1, 2.179047895752114e-05, 2.8215876598841475e-05]}}, '~:0(<range>)': {'display_name': '~:0(<range>)', 'callers': {'2.py:6(fun2)': [1, 1, 0.00012515557144832654, 0.00012515557144832654], '2.py:2(fun1)': [1, 1, 6.425397641320336e-06, 6.425397641320336e-06]}, 'stats': [2, 2, 0.00013158096908964686, 0.00013158096908964686], 'children': {}}, '<string>:1(<module>)': {'display_name': '<string>:1(<module>)', 'callers': {}, 'stats': [1, 1, 3.9111116077602044e-06, 0.00034780956797581816], 'children': {'2.py:6(fun2)': [1, 1, 0.00019052700832088994, 0.00034389845636805796]}}, '2.py:2(fun1)': {'display_name': '2.py:2(fun1)', 'callers': {'2.py:6(fun2)': [1, 1, 2.179047895752114e-05, 2.8215876598841475e-05]}, 'stats': [1, 1, 2.179047895752114e-05, 2.8215876598841475e-05], 'children': {'~:0(<range>)': [1, 1, 6.425397641320336e-06, 6.425397641320336e-06]}}};
function sv_build_hierarchy(
node_name, depth, max_depth, cutoff, node_time, parent_name, call_stack) {
// track visited functions so we can avoid infinitely displaying
// instances of recursion
if (_.isUndefined(call_stack)) {
var call_stack = Immutable.Set([node_name])
} else {
var call_stack = call_stack.add(node_name);
};
var data = {
name: node_name,
display_name: stats[node_name]['display_name'],
time: node_time,
cumulative: stats[node_name]['stats'][3],
parent_name: parent_name
};
if (depth < max_depth && !_.isEmpty(stats[node_name]['children'])) {
child_names = {};
for (var child_name in stats[node_name]['children']) {
// Cut off children that have already been visited (recursion)
if (!call_stack.contains(child_name)) {
child_names[child_name] = stats[node_name]['children'][child_name];
}
}
// Normalize the child times.
// Unfortunately, stats[child_name]['callers'][node_name][3] doesn't
// give us the time that child spend under node in this instance, but
// in all instances across the call tree. Yikes!
// This may lead to unexpected behavior, e.g., the child times add up
// to more than the node time. A normalization is necessary.
var child_times = {};
var total_children_time = 0.0;
for (var child_name in child_names) {
child_times[child_name] = stats[child_name]['callers'][node_name][3];
total_children_time += child_times[child_name];
}
if (total_children_time > node_time) {
for (var child_name in child_names) {
child_times[child_name] *= (node_time / total_children_time);
}
}
data['children'] = [];
// recurse
for (var child_name in child_names) {
if (child_times[child_name]/node_time > cutoff) {
data['children'].push(
sv_build_hierarchy(
child_name, depth+1, max_depth, cutoff,
child_times[child_name], node_name, call_stack
));
}
}
// D3, the plotting framework, only accounts for times in the leaf
// nodes. Hence, if the node_time exceeds the total time in its
// children, we need to add another child node to account for the the
// time spent in node itself.
if (total_children_time < node_time) {
data['children'].push({
name: node_name,
display_name: data['display_name'],
parent_name: data['parent_name'],
cumulative: stats[node_name]['stats'][3],
time: node_time - total_children_time
});
}
}
return data;
}
self.onmessage = function (event) {
// Try loading JS from CDN in case snakeviz server is off
try {
importScripts(
"https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js");
}
// If the user is offline try loading from the SnakeViz server
catch (e) {
try {
importScripts(
event.data['url'] + "/static/vendor/immutable.min.js",
event.data['url'] + "/static/vendor/lodash.min.js");
}
catch (e) {
throw 'Could not load JS libraries in worker.';
}
}
var depth = 0;
var max_depth = event.data['depth'];
var cutoff = event.data['cutoff'];
var node_name = event.data['name'];
var parent_name = event.data['parent_name'];
var node_time = stats[node_name]['stats'][3];
self.postMessage(JSON.stringify(
sv_build_hierarchy(
node_name, depth, max_depth, cutoff, node_time, parent_name
)));
};
</script>
<!-- Load SnakeViz JS Files -->
<script src='/static/snakeviz.js'></script>
<script src='/static/drawsvg.js'></script>
<!-- Do initial setup stuff -->
<script>
// Initialize data
$(document).ready(_.defer(function () {
var profile_data = {'2.py:6(fun2)': {'display_name': '2.py:6(fun2)', 'callers': {'<string>:1(<module>)': [1, 1, 0.00019052700832088994, 0.00034389845636805796]}, 'stats': [1, 1, 0.00019052700832088994, 0.00034389845636805796], 'children': {'~:0(<range>)': [1, 1, 0.00012515557144832654, 0.00012515557144832654], '2.py:2(fun1)': [1, 1, 2.179047895752114e-05, 2.8215876598841475e-05]}}, '~:0(<range>)': {'display_name': '~:0(<range>)', 'callers': {'2.py:6(fun2)': [1, 1, 0.00012515557144832654, 0.00012515557144832654], '2.py:2(fun1)': [1, 1, 6.425397641320336e-06, 6.425397641320336e-06]}, 'stats': [2, 2, 0.00013158096908964686, 0.00013158096908964686], 'children': {}}, '<string>:1(<module>)': {'display_name': '<string>:1(<module>)', 'callers': {}, 'stats': [1, 1, 3.9111116077602044e-06, 0.00034780956797581816], 'children': {'2.py:6(fun2)': [1, 1, 0.00019052700832088994, 0.00034389845636805796]}}, '2.py:2(fun1)': {'display_name': '2.py:2(fun1)', 'callers': {'2.py:6(fun2)': [1, 1, 2.179047895752114e-05, 2.8215876598841475e-05]}, 'stats': [1, 1, 2.179047895752114e-05, 2.8215876598841475e-05], 'children': {'~:0(<range>)': [1, 1, 6.425397641320336e-06, 6.425397641320336e-06]}}};
sv_json_cache = {};
sv_worker = sv_make_worker();
sv_root_func_name = sv_find_root(profile_data);
sv_root_func_name__cached = sv_root_func_name;
sv_call_stack = [sv_root_func_name];
sv_total_time = profile_data[sv_root_func_name]['stats'][3];
}));
// Initialize the call stack button
$(document).ready(_.defer(function () {
sv_update_call_stack_list();
sv_call_stack_btn_for_show();
}));
// Draw the visualization
$(document).ready(_.defer(function () {
sv_draw_vis(sv_root_func_name);
}));
</script>
</body>
</html>