在GAE中使用hook

标签:Google App Engine, Python

实际上Google App Engine文章中早就有一篇《在 Google App Engine 中使用 hook》来介绍hook技术了,只是当时对我没用,加上写得很难懂,所以没去细读。
而最近在考虑做blog,想像Discuz!一样做个性能评估,将页面生成时间、数据库访问次数等输出出来。
要实现这个功能的话,一般是需要自己封装一下数据库调用函数,在每次调用时记录下时间和增加调用次数,不过这样改动太大,所以就想到了hook,于是再次读了一遍。

原文的例子有点难懂,所以这里我就先给个简单的例子,实际上hook是非常容易使用的:
from google.appengine.api import apiproxy_stub_map
from google.appengine.ext import db

class Test(db.Model):
  pass

def hook(service, call, request, response):
  print 'Hello, datastore!'

apiproxy_stub_map.apiproxy.GetPreCallHooks().Append('db_hook', hook, 'datastore_v3')

Test().put()
在这个例子里面,我定义了一个Test模型,它只是用来访问数据库的一个工具而已。
接着定义了一个hook函数,这个函数一旦被注册,就会在服务被调用时自动调用。这里我只是输出了一句话而已。
接着就是关键代码了,GetPreCallHooks()是表示在服务调用前来调用我的hook,Append()是将hook放在调用队列的最后面。
这个函数接收3个参数,分别为key、function和service。Key是标记一个hook用的字符串,如果调用队列中已有该key,则再次添加的时候会忽略掉;function就是需要调用的函数,它的参数我用不到,你可以去原文看看,和所调用的服务有关;service则是被hook的服务名了,数据库操作是'datastore_v3'。至于服务名的文档我没找到,不过可以在GAE SDK的源代码里搜索apiproxy_stub_map.MakeSyncCall,就会发现'user'、'xmpp'、'images'之类的服务名了。此外,虽然没找到memcache,但它的服务名就是'memcache'。
最后,我创建了一个Test实体,然后保存到数据库,这时hook函数就会被调用,输出'Hello, datastore!'。

看懂这个例子后,要实现我所要达到的目标就不难了,于是我就直接上代码吧:
from google.appengine.api import apiproxy_stub_map
from google.appengine.ext import db
from time import time

class Test(db.Model):
  pass

db_times = 0
db_time = 0
db_start_time = 0

def before_db(service, call, request, response):
  global db_times, db_start_time
  db_times += 1
  db_start_time = time()

def after_db(service, call, request, response):
  global db_time, db_start_time
  db_time += time() - db_start_time

apiproxy_stub_map.apiproxy.GetPreCallHooks().Append('before_db', before_db, 'datastore_v3')
apiproxy_stub_map.apiproxy.GetPostCallHooks().Push('after_db', after_db, 'datastore_v3')

print db_times, db_time
Test().put()
print db_times, db_time
Test().put()
print db_times, db_time
大部分代码是不需要解释了,只说说GetPostCallHooks().Push():它实际上和GetPreCallHooks().Append()相反,是在服务调用结束后才调用该hook;由于我只计算数据库时间,不想被其他hook干扰,所以就用Push()放到了队列的开头,保证它是第一个被调用的hook。

最终这个程序的运行结果如下:
0 0
1 0.0285320281982
2 0.058926820755
成功达到了我的目的,顺便还能了解到,保存一个空的实体需要约0.03秒的数据库时间,还算过得去。

至于如何跨模块地使用这些性能评估用的全局变量,可以参考《在GAE中实现请求级别的全局变量》
当然,最方便的方法是将hook函数放在一个模块里,将性能评估用的变量作为该模块的全局变量,然后在__main__里导入并注册这些hook即可。

1条评论 你不来一发么↓ 顺序排列 倒序排列

    向下滚动可载入更多评论,或者点这里禁止自动加载

    想说点什么呢?