From 779e9652b65b735fd0c3186ab29345d3e5caadce Mon Sep 17 00:00:00 2001 From: Fengda HUANG Date: Fri, 23 Oct 2015 22:27:02 +0800 Subject: [PATCH] update analtyics 2 --- chapters/04-analytics-02.md | 418 +++++++++++++++++++++--------------- img/main-events.png | Bin 0 -> 18949 bytes img/smtwtfs.png | Bin 0 -> 8528 bytes 3 files changed, 239 insertions(+), 179 deletions(-) create mode 100644 img/main-events.png create mode 100644 img/smtwtfs.png diff --git a/chapters/04-analytics-02.md b/chapters/04-analytics-02.md index 3fb2d69..5b56c85 100644 --- a/chapters/04-analytics-02.md +++ b/chapters/04-analytics-02.md @@ -3,25 +3,34 @@ 让我们分析之前的程序,然后再想办法做出优化。网上看到一篇文章[http://www.huyng.com/posts/python-performance-analysis/](http://www.huyng.com/posts/python-performance-analysis/)讲的就是分析这部分内容的。 -#time python分析# +##time python分析 + 分析程序的运行时间 - $time python handle.py +```bash +$time python handle.py +``` 结果便是,但是对于我们的分析没有一点意义 - real 0m43.411s - user 0m39.226s - sys 0m0.618s +``` + real 0m43.411s + user 0m39.226s + sys 0m0.618s +``` + +##line_profiler python -#line_profiler python# 这是 ##Mac OS X 10.9 line_profiler Install## - sudo ARCHFLAGS="-Wno-error=unused-command-line-argument-hard-error-in-future" easy_install line_profiler +```bash +sudo ARCHFLAGS="-Wno-error=unused-command-line-argument-hard-error-in-future" easy_install line_profiler +``` 然后在我们的``parse_data.py``的``handle_json``前面加上``@profile`` -

+
+```python
 @profile
 def handle_json(jsonfile):
     f = open(jsonfile, "r")
@@ -37,107 +46,127 @@ def handle_json(jsonfile):
 
     f.close()
     return datacount, dataarray
-
+``` + Line_profiler带了一个分析脚本``kernprof.py``,so - kernprof.py -l -v handle.py +```bash +kernprof.py -l -v handle.py +``` 我们便会得到下面的结果 +``` +Wrote profile results to handle.py.lprof +Timer unit: 1e-06 s - Wrote profile results to handle.py.lprof - Timer unit: 1e-06 s +File: parse_data.py +Function: handle_json at line 15 +Total time: 127.332 s - File: parse_data.py - Function: handle_json at line 15 - Total time: 127.332 s - - Line # Hits Time Per Hit % Time Line Contents - ============================================================== - 15 @profile - 16 def handle_json(jsonfile): - 17 19 636 33.5 0.0 f = open(jsonfile, "r") - 18 19 21 1.1 0.0 dataarray = [] - 19 19 16 0.8 0.0 datacount = 0 - 20 - 21 212373 730344 3.4 0.6 for line in open(jsonfile): - 22 212354 2826826 13.3 2.2 line = f.readline() - 23 212354 13848171 65.2 10.9 lin = json.loads(line) - 24 212354 109427317 515.3 85.9 date = dateutil.parser.parse(lin["created_at"]) - 25 212354 238112 1.1 0.2 datacount += 1 - 26 212354 260227 1.2 0.2 dataarray.append(date.minute) - 27 - 28 19 349 18.4 0.0 f.close() - 29 19 20 1.1 0.0 return datacount, dataarray +Line # Hits Time Per Hit % Time Line Contents +============================================================== + 15 @profile + 16 def handle_json(jsonfile): + 17 19 636 33.5 0.0 f = open(jsonfile, "r") + 18 19 21 1.1 0.0 dataarray = [] + 19 19 16 0.8 0.0 datacount = 0 + 20 + 21 212373 730344 3.4 0.6 for line in open(jsonfile): + 22 212354 2826826 13.3 2.2 line = f.readline() + 23 212354 13848171 65.2 10.9 lin = json.loads(line) + 24 212354 109427317 515.3 85.9 date = dateutil.parser.parse(lin["created_at"]) + 25 212354 238112 1.1 0.2 datacount += 1 + 26 212354 260227 1.2 0.2 dataarray.append(date.minute) + 27 + 28 19 349 18.4 0.0 f.close() + 29 19 20 1.1 0.0 return datacount, dataarray +``` 于是我们就发现我们的瓶颈就是从读取``created_at``,即创建时间。。。以及解析json,反而不是我们关心的IO,果然``readline``很强大。 -#memory_profiler python# -##memory_profiler install## +##memory_profiler python - $ pip install -U memory_profiler - $ pip install psutil +###memory_profiler install + +```bash +$ pip install -U memory_profiler +$ pip install psutil +``` + +###memory_profiler python -##memory_profiler python## 如上,我们只需要在``handle_json``前面加上``@profile`` - python -m memory_profiler handle.py +```bash +python -m memory_profiler handle.py +``` 于是 +``` +Filename: parse_data.py + +Line # Mem usage Increment Line Contents +================================================ + 13 39.930 MiB 0.000 MiB @profile + 14 def handle_json(jsonfile): + 15 39.930 MiB 0.000 MiB f = open(jsonfile, "r") + 16 39.930 MiB 0.000 MiB dataarray = [] + 17 39.930 MiB 0.000 MiB datacount = 0 + 18 + 19 40.055 MiB 0.125 MiB for line in open(jsonfile): + 20 40.055 MiB 0.000 MiB line = f.readline() + 21 40.066 MiB 0.012 MiB lin = json.loads(line) + 22 40.055 MiB -0.012 MiB date = dateutil.parser.parse(lin["created_at"]) + 23 40.055 MiB 0.000 MiB datacount += 1 + 24 40.055 MiB 0.000 MiB dataarray.append(date.minute) + 25 + 26 f.close() + 27 return datacount, dataarray +``` - Filename: parse_data.py - - Line # Mem usage Increment Line Contents - ================================================ - 13 39.930 MiB 0.000 MiB @profile - 14 def handle_json(jsonfile): - 15 39.930 MiB 0.000 MiB f = open(jsonfile, "r") - 16 39.930 MiB 0.000 MiB dataarray = [] - 17 39.930 MiB 0.000 MiB datacount = 0 - 18 - 19 40.055 MiB 0.125 MiB for line in open(jsonfile): - 20 40.055 MiB 0.000 MiB line = f.readline() - 21 40.066 MiB 0.012 MiB lin = json.loads(line) - 22 40.055 MiB -0.012 MiB date = dateutil.parser.parse(lin["created_at"]) - 23 40.055 MiB 0.000 MiB datacount += 1 - 24 40.055 MiB 0.000 MiB dataarray.append(date.minute) - 25 - 26 f.close() - 27 return datacount, dataarray +##objgraph python +###objgraph install -#objgraph python# - -##objgraph install## - - pip install objgraph +```bash +pip install objgraph +``` 我们需要调用他 - import pdb; +```python +import pdb; +``` 以及在需要调度的地方加上 - pdb.set_trace() +```python +pdb.set_trace() +``` 接着会进入``command``模式 - (pdb) import objgraph - (pdb) objgraph.show_most_common_types() +```python +(pdb) import objgraph +(pdb) objgraph.show_most_common_types() +``` 然后我们可以找到。。 - function 8259 - dict 2137 - tuple 1949 - wrapper_descriptor 1625 - list 1586 - weakref 1145 - builtin_function_or_method 1117 - method_descriptor 948 - getset_descriptor 708 - type 705 +``` +function 8259 +dict 2137 +tuple 1949 +wrapper_descriptor 1625 +list 1586 +weakref 1145 +builtin_function_or_method 1117 +method_descriptor 948 +getset_descriptor 708 +type 705 +``` 也可以用他生成图形,貌似这里是用``dot``生成的,加上``python-xdot`` @@ -145,17 +174,20 @@ Line_profiler带了一个分析脚本``kernprof.py``,so 如果我们每次都要花同样的时间去做一件事,去扫那些数据的话,那么这是最好的打发时间的方法。 -##python SQLite3 查询数据## +##python SQLite3 查询数据 + 我们创建了一个名为``userdata.db``的数据库文件,然后创建了一个表,里面有owner,language,eventtype,name url - def init_db(): - conn = sqlite3.connect('userdata.db') - c = conn.cursor() - c.execute('''CREATE TABLE userinfo (owner text, language text, eventtype text, name text, url text)''') +```python +def init_db(): + conn = sqlite3.connect('userdata.db') + c = conn.cursor() + c.execute('''CREATE TABLE userinfo (owner text, language text, eventtype text, name text, url text)''') +``` 接着我们就可以查询数据,这里从结果讲起。 -

+```python
 def get_count(username):
     count = 0
     userinfo = []
@@ -165,11 +197,11 @@ def get_count(username):
         userinfo.append(zero)
 
     return count, userinfo
-
-
+``` 当我查询``gmszone``的时候,也就是我自己就会有如下的结果 -

+
+```bash
 (u'gmszone', u'ForkEvent', u'RESUME', u'TeX', u'https://github.com/gmszone/RESUME')
 (u'gmszone', u'WatchEvent', u'iot-dashboard', u'JavaScript', u'https://github.com/gmszone/iot-dashboard')
 (u'gmszone', u'PushEvent', u'wechat-wordpress', u'Ruby', u'https://github.com/gmszone/wechat-wordpress')
@@ -180,43 +212,53 @@ def get_count(username):
 (u'gmszone', u'PushEvent', u'iot-doc', u'TeX', u'https://github.com/gmszone/iot-doc')
 (u'gmszone', u'PushEvent', u'iot-doc', u'TeX', u'https://github.com/gmszone/iot-doc')
 109
-
+```` 一共有109个事件,有``Watch``,``Create``,``Push``,``Fork``还有其他的, 项目主要有``iot``,``RESUME``,``iot-dashboard``,``wechat-wordpress``, 接着就是语言了,``Tex``,``Javascript``,``Ruby``,接着就是项目的url了。 值得注意的是。 -

+
+```bash
 -rw-r--r--   1 fdhuang staff 905M Apr 12 14:59 userdata.db
-
+``` + 这个数据库文件有**905M**,不过查询结果相当让人满意,至少相对于原来的结果来说。 -##Python SQLite3## +##Python SQLite3 Python自带了对SQLite3的支持,然而我们还需要安装SQLite3 - brew install sqlite3 +```bash +brew install sqlite3 +``` 或者是 - - sudo port install sqlite3 + +```bash +sudo port install sqlite3 +``` 或者是Ubuntu的 - sudo apt-get install sqlite3 +```bash +sudo apt-get install sqlite3 +``` openSUSE自然就是 - sudo zypper install sqlite3 +```bash +sudo zypper install sqlite3 +``` 不过,用yast2也很不错,不是么。。 -##Pythont Github Sqlite3数据导入## +##Pythont Github Sqlite3数据导入 需要注意的是这里是需要python2.7,起源于对gzip的上下文管理器的支持问题 -

+```python
 def handle_gzip_file(filename):
     userinfo = []
     with gzip.GzipFile(filename) as f:
@@ -264,7 +306,7 @@ def build_db_with_gzip():
 
     conn.commit()
     c.close()
-
+``` ``executemany``可以插入多条数据,对于我们的数据来说,一小时的文件大概有五六千个会符合我们上面的安装,也就是有``actor``又有``type``才是我们需要记录的数据,我们只需要统计用户的那些事件,而非全部的事件。 @@ -276,7 +318,9 @@ def build_db_with_gzip(): 首先是正规匹配 - date_re = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]+)\.json.gz") +```python +date_re = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})-([0-9]+)\.json.gz") +``` 不过主要的还是在于``glob.glob`` @@ -290,7 +334,7 @@ def build_db_with_gzip(): 更好的方案? -###redis### +###redis 结合了前面两篇我们终于可以成功地读取出用户数据、处理,再接着可以找相近的用户。 @@ -298,30 +342,36 @@ def build_db_with_gzip(): 查询用户事件总数 - import redis - r = redis.StrictRedis(host='localhost', port=6379, db=0) - pipe = pipe = r.pipeline() - pipe.zscore('osrc:user',"gmszone") - pipe.execute() +```python +import redis +r = redis.StrictRedis(host='localhost', port=6379, db=0) +pipe = pipe = r.pipeline() +pipe.zscore('osrc:user',"gmszone") +pipe.execute() +``` 系统返回了``227.0``,试试别人。 - >>> pipe.zscore('osrc:user',"dfm") - - >>> pipe.execute() - [425.0] - >>> +```bash +>>> pipe.zscore('osrc:user',"dfm") + +>>> pipe.execute() +[425.0] +>>> +``` 看看主要是在哪一天提交的 - >>> pipe.hgetall('osrc:user:gmszone:day') - - >>> pipe.execute() - [{'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}] +```python +>>> pipe.hgetall('osrc:user:gmszone:day') + +>>> pipe.execute() +[{'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}] +``` 结果大致如下图所示: -![SMTWTFS][1] +![SMTWTFS](./img/smtwtfs.png) 看看主要的事件是? @@ -331,17 +381,17 @@ def build_db_with_gzip(): [[('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)]] >>> -![Main Event][2] +![Main Event](./img/main-events.png) 蓝色的就是push事件,黄色的是create等等。 到这里我们算是知道了OSRC的数据库部分是如何工作的。 -##Python redis 查询 +###Python redis 查询 主要代码如下所示 -

+```python
 def get_vector(user, pipe=None):
 
     r = redis.StrictRedis(host='localhost', port=6379, db=0)
@@ -364,19 +414,20 @@ def get_vector(user, pipe=None):
 
     if no_pipe:
         return pipe.execute()
-
+``` 结果在上一篇中显示出来了,也就是 - [227.0, {'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}, [('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)], 0, 0, 0, 11, [('CSS', 74.0), ('JavaScript', 60.0), ('Ruby', 12.0), ('TeX', 6.0), ('Python', 6.0), ('Java', 5.0), ('C++', 5.0), ('Assembly', 5.0), ('C', 3.0), ('Emacs Lisp', 2.0), ('Arduino', 2.0)]] +``` +[227.0, {'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}, [('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)], 0, 0, 0, 11, [('CSS', 74.0), ('JavaScript', 60.0), ('Ruby', 12.0), ('TeX', 6.0), ('Python', 6.0), ('Java', 5.0), ('C++', 5.0), ('Assembly', 5.0), ('C', 3.0), ('Emacs Lisp', 2.0), ('Arduino', 2.0)]] +``` 有意思的是在这里生成了和自己相近的人 - ['alesdokshanin', 'hjiawei', 'andrewreedy', 'christj6', '1995eaton'] +``` +['alesdokshanin', 'hjiawei', 'andrewreedy', 'christj6', '1995eaton'] +``` - [1]: https://www.phodal.com/static/media/uploads/screen_shot_2014-04-15_at_8.11.14_pm.png - [2]: https://www.phodal.com/static/media/uploads/screen_shot_2014-04-15_at_8.14.52_pm.png - osrc最有意思的一部分莫过于flann,当然说的也是系统后台的设计的一个很关键及有意思的部分。 ##Python Github @@ -386,20 +437,24 @@ osrc最有意思的一部分莫过于flann,当然说的也是系统后台的 换句话说,我们需要一些样本来当作我们的分析资料,这里东西用到的就是我们之前的。 - [227.0, {'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}, [('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)], 0, 0, 0, 11, [('CSS', 74.0), ('JavaScript', 60.0), ('Ruby', 12.0), ('TeX', 6.0), ('Python', 6.0), ('Java', 5.0), ('C++', 5.0), ('Assembly', 5.0), ('C', 3.0), ('Emacs Lisp', 2.0), ('Arduino', 2.0)]] +``` +[227.0, {'1': '51', '0': '41', '3': '17', '2': '34', '5': '28', '4': '22', '6': '34'}, [('PushEvent', 154.0), ('CreateEvent', 41.0), ('WatchEvent', 18.0), ('GollumEvent', 8.0), ('MemberEvent', 3.0), ('ForkEvent', 2.0), ('ReleaseEvent', 1.0)], 0, 0, 0, 11, [('CSS', 74.0), ('JavaScript', 60.0), ('Ruby', 12.0), ('TeX', 6.0), ('Python', 6.0), ('Java', 5.0), ('C++', 5.0), ('Assembly', 5.0), ('C', 3.0), ('Emacs Lisp', 2.0), ('Arduino', 2.0)]] +``` 在代码中是构建了一个points.h5的文件来分析每个用户的points,之后再记录到hdf5文件中。 - [ 0.00438596 0.18061674 0.2246696 0.14977974 0.07488987 0.0969163 - 0.12334802 0.14977974 0. 0.18061674 0. 0. 0. - 0.00881057 0. 0. 0.03524229 0. 0. - 0.01321586 0. 0. 0. 0.6784141 0. - 0.07929515 0.00440529 1. 1. 1. 0.08333333 - 0.26431718 0.02202643 0.05286344 0.02643172 0. 0.01321586 - 0.02202643 0. 0. 0. 0. 0. 0. - 0. 0. 0.00881057 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0. 0.00881057] +``` +[ 0.00438596 0.18061674 0.2246696 0.14977974 0.07488987 0.0969163 + 0.12334802 0.14977974 0. 0.18061674 0. 0. 0. + 0.00881057 0. 0. 0.03524229 0. 0. + 0.01321586 0. 0. 0. 0.6784141 0. + 0.07929515 0.00440529 1. 1. 1. 0.08333333 + 0.26431718 0.02202643 0.05286344 0.02643172 0. 0.01321586 + 0.02202643 0. 0. 0. 0. 0. 0. + 0. 0. 0.00881057 0. 0. 0. 0. + 0. 0. 0. 0. 0. 0. 0. + 0. 0. 0. 0. 0.00881057] +``` 这里分析到用户的大部分行为,再找到与其行为相近的用户,主要的行为有下面这些: @@ -410,62 +465,67 @@ osrc最有意思的一部分莫过于flann,当然说的也是系统后台的 osrc中用于解析的代码 +```python +def parse_vector(results): + points = np.zeros(nvector) + total = int(results[0]) - def parse_vector(results): - points = np.zeros(nvector) - total = int(results[0]) + points[0] = 1.0 / (total + 1) - points[0] = 1.0 / (total + 1) + # Week means. + for k, v in results[1].iteritems(): + points[1 + int(k)] = float(v) / total - # Week means. - for k, v in results[1].iteritems(): - points[1 + int(k)] = float(v) / total + # Event types. + n = 8 + for k, v in results[2]: + points[n + evttypes.index(k)] = float(v) / total - # Event types. - n = 8 - for k, v in results[2]: - points[n + evttypes.index(k)] = float(v) / total + # Number of contributions, connections and languages. + n += nevts + points[n] = 1.0 / (float(results[3]) + 1) + points[n + 1] = 1.0 / (float(results[4]) + 1) + points[n + 2] = 1.0 / (float(results[5]) + 1) + points[n + 3] = 1.0 / (float(results[6]) + 1) - # Number of contributions, connections and languages. - n += nevts - points[n] = 1.0 / (float(results[3]) + 1) - points[n + 1] = 1.0 / (float(results[4]) + 1) - points[n + 2] = 1.0 / (float(results[5]) + 1) - points[n + 3] = 1.0 / (float(results[6]) + 1) + # Top languages. + n += 4 + for k, v in results[7]: + if k in langs: + points[n + langs.index(k)] = float(v) / total + else: + # Unknown language. + points[-1] = float(v) / total - # Top languages. - n += 4 - for k, v in results[7]: - if k in langs: - points[n + langs.index(k)] = float(v) / total - else: - # Unknown language. - points[-1] = float(v) / total - - return points + return points +``` 这样也就返回我们需要的点数,然后我们可以用``get_points``来获取这些 - def get_points(usernames): - r = redis.StrictRedis(host='localhost', port=6379, db=0) - pipe = r.pipeline() +```python +def get_points(usernames): + r = redis.StrictRedis(host='localhost', port=6379, db=0) + pipe = r.pipeline() - results = get_vector(usernames) - points = np.zeros([len(usernames), nvector]) - points = parse_vector(results) - return points + results = get_vector(usernames) + points = np.zeros([len(usernames), nvector]) + points = parse_vector(results) + return points +``` 就会得到我们的相应的数据,接着找找和自己邻近的,看看结果。 - [ 0.01298701 0.19736842 0. 0.30263158 0.21052632 0.19736842 - 0. 0.09210526 0. 0.22368421 0.01315789 0. 0. - 0. 0. 0. 0.01315789 0. 0. - 0.01315789 0. 0. 0. 0.73684211 0. 0. - 0. 1. 1. 1. 0.2 0.42105263 - 0.09210526 0. 0. 0. 0. 0.23684211 - 0. 0. 0.03947368 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0. ] +``` +[ 0.01298701 0.19736842 0. 0.30263158 0.21052632 0.19736842 + 0. 0.09210526 0. 0.22368421 0.01315789 0. 0. + 0. 0. 0. 0.01315789 0. 0. + 0.01315789 0. 0. 0. 0.73684211 0. 0. + 0. 1. 1. 1. 0.2 0.42105263 + 0.09210526 0. 0. 0. 0. 0.23684211 + 0. 0. 0.03947368 0. 0. 0. 0. + 0. 0. 0. 0. 0. 0. 0. + 0. 0. 0. 0. 0. 0. 0. + 0. 0. 0. 0. ] +``` 真看不出来两者有什么相似的地方 。。。。 diff --git a/img/main-events.png b/img/main-events.png new file mode 100644 index 0000000000000000000000000000000000000000..37ba331a608468395f6f7de1e93bde1df4faf0db GIT binary patch literal 18949 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 literal 0 HcmV?d00001 diff --git a/img/smtwtfs.png b/img/smtwtfs.png new file mode 100644 index 0000000000000000000000000000000000000000..a8ffb1159dcaabfc18133b173eb2011bb96f40f9 GIT binary patch literal 8528 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