C/CPP, PHP

[PHP模块开发]获取单次请求所耗的CPU时间

数天前与一个学校中的朋友闲聊,对方提到了使用Hostker的经历,涉及到了一项“按CPU时间”计费的功能。

个人来说,是挺欣赏这一项收费策略的,毕竟有多少个使用虚拟主机的用户,就有多少种不同资源需求量,按照PHP对CPU资源的使用情况来计费,不仅实现公平收费,还能逼那些让资源占用多的用户占得谨慎点,比用CloudLinux的那些逼格高得多哈!

 

既然如此,就自己来动手实现一个。

计算CPU时间,并不难实现,Unix Like有提供这一个系统调用,所以嘛,根本不需要你自己计算……

我所知道相关的系统调用有两个,以下是他们的函数原型:

times()需要一个tms结构体的指针,tms结构体的格式如下:

tms_cutime和tms_cstime不太清楚是计算子线程还是子进程,毕竟目前我还没接触到多线程开发,不过一般情况下PHP还是使用单进程单线程模式的,只需要用到tms_utime和tms_stime。

tms结构体中的值的单位并非为秒,内核计时是有一个频率的,因此除以该计时频率的值,才能得到以秒为单位的值,使用sysconf(_SC_CLK_TCK)可以获取到该值。

times函数的返回值是一个过去的时间,我看了下,好像除以一百才是秒,所以两次调用的返回值之差除以一百可以得到自然流逝的时间。不过man page里面说The return value may overflow the possible range of type clock_t.,意思是返回值可能溢出。

 

clock()不需要任何参数,直接返回用户态和核心态使用的时间总和,同样,返回值的单位不是秒,需要除以一个常量: CLOCKS_PER_SEC。

 

我们来看看C计算从零到十亿需要多少CPU时间:

执行后返回以下结果:

times()函数获取到用户态耗时3.22秒,核心态耗时0.02秒,总CPU时间3.24秒,自然时间流逝3.21秒。

clock()函数取到3.25秒,和times()有点差距,毕竟调用times()也要耗时。

至于自然时间流逝比总CPU耗时短的问题,有两种情况。

第一种是我们获取开始时间并非真正在程序启动时就获取,而CPU时间的计算是内核在程序启动一刻开始计时的,程序启动到我们获取起始时间之间是有时间差的。

第二种是多线程,如果在自然时间一秒内同时使用满两个CPU核心,那么自然时间流逝是一秒,CPU时间就是两秒,不过这里是单线程编程,应该不涉及这种情况。

 

知道了如何调用这些API,接下来就是要弄成PHP模块了。

PHP的模块开发也不难,PHP已经为你准备好了一切,我来教你如何生成基本模块结构。

假设我们的模块名定为cputime。

下载PHP的源码,进入源码根目录下的ext目录,执行:

ext_skel会自动生成目录cputime及其需要的文件,进入cputime,编辑cputime.c。

我觉得我只需要告诉你,宏PHP_RINIT_FUNCTION(cputime)与PHP_RSHUTDOWN_FUNCTION(cputime)中的代码,分别在PHP开始解析前和PHP解析完毕后执行。

 

cputime.c修改完毕后,修改config.m4,找到类似的代码:

改成:

这样就能通过phpize生成模块了。

 

一切完毕后,按照正常使用PHP模块的方式编译,加载。

 

我们来看看PHP计算零到十亿需要多少时间:

一百零七秒多,人民群众喜闻乐见。

 

如果你想说你不会改cputime.c,这里还有一个成品: https://coding.net/u/yzs/p/PHP-CPUTIME-PRINT/git,可以把每次页面的时间记录追加到/tmp/uid_用户id_php_cputime中。不过你最好别指望能直接搬到生产环境上用,多个进程往同一个地址同时写,数据恐怕就出问题了呢。