Apache上的PHP HTTP原始I / O跟踪

时间:2011-12-22 17:15:07

标签: php apache

我试图找到一种方法来测量在php + apache上构建的Web应用程序传入或传出的字节数。一个问题是所有I / O都是由本机PHP扩展完成的,它将句柄传递给built-in streams:php:// input或php://输出。

我检查了以下替代方案:

1。)ftell on stream wrapper 在遇到this question之后,我的第一个直觉就是在I / O操作之后尝试在流包装器句柄上使用ftell;大致是:

$hOutput = fopen('php://output', 'wb');
extensionDoOutput($hOutput);
$iBytesTransferred = ftell($hOutput);

这似乎适用于输入包装器,但不适用于输出(它总是从ftell返回零)。

2.)附加流过滤器 非修改流过滤器似乎是计算通过字节数的合理方法。但是,文档似乎是a bit lacking,而且我还没有找到一种方法可以在不执行迭代+复制模式的情况下完成任务:

class test_filter extends php_user_filter {
  public static $iTotalBytes = 0;

  function filter(&$in, &$out, &$consumed, $closing) {
    while ($bucket = stream_bucket_make_writeable($in)) {
      $consumed += $bucket->datalen;
      stream_bucket_append($out, $bucket);
    }
    test_filter::$iTotalBytes += $consumed;
    return PSFS_PASS_ON;
  }
}

stream_filter_register("test", "test_filter")
    or die("Failed to register filter");

$f = fopen("php://output", "wb");

stream_filter_append($f, "test");

// do i/o

不幸的是,随着数据被复制到扩展中和从扩展中复制,这似乎会显着降低吞吐量(> 50%)。

3.。)实现流包装器 可以使用自定义流包装器来包装其他流并累积读/写的字节:

class wrapper {
    var $position;

    var $handle;

    function stream_open($path, $mode, $options, &$opened_path)
    {
        $this->position = 0;
...
        $this->handle = fopen($opened_path, $mode);
        return $this->handle != false;
    }

    function stream_read($count)
    {
        $ret = fread($this->handle, $count);
        $this->position += strlen($ret);
        return $ret;
    }

    function stream_write($data)
    {
        $written = fwrite($this->handle, $data);
        $this->position += $written;
        return $written;
    }

    function stream_tell()
    {
        return $this->position;
    }

    function stream_eof()
    {
        return feof($this->handle);
    }
...
}

stream_wrapper_register("test", "wrapper")
    or die("Failed to register protocol");

$hOutput = fopen('test://output', 'wb');
extensionDoOutput($hOutput);
$iBytesTransferred = ftell($hOutput);

同样,这会降低吞吐量(输出约20%,输入更多)

4.。)使用回调输出缓冲 可以使用ob_start提供回调,以便在刷新输出块时调用。

$totalBytes = 0;

function cb($strBuffer) {
   global $totalBytes;
   $totalBytes += strlen($strBuffer);
   return $strBuffer;
}

$f = fopen("php://output", "wb");

ob_start('cb', 16384);
// do output...
fclose($f);
ob_end_flush();

同样,这有效,但由于缓冲,会产生一定的吞吐量性能损失(~25%)。


选项#1已放弃,因为它似乎不适用于输出。在剩余的三个中,所有这些都在功能上有效,但由于缓冲/复制机制而导致吞吐量产生负面影响。

PHP(或apache服务器扩展)是否有一些内在的东西可以用来优雅地执行此操作,还是需要在性能上咬紧牙关?我欢迎任何关于如何实现这一点的想法。 (注意:如果可能的话,我对PHP应用程序级解决方案感兴趣...而不是apache模块)

2 个答案:

答案 0 :(得分:1)

尽管如此奇怪,使用STDOUT常量而不是fopen('php://output')的结果会使ftell()正常工作。

$stream = fopen('php://output','w');
fwrite($stream, "This is some data\n");
fwrite($stream, ftell($stream));
// Output:
// This is some data
// 0

然而:

fwrite(STDOUT, "This is some data\n");
fwrite(STDOUT, ftell(STDOUT));
// Output:
// This is some data
// 17

经过测试的PHP / 5.2.17(win32)

编辑实际上,这是正常的,还是应该是18?我从不使用ftell()所以我不是百分之百确定......

另一个编辑

看看这是否适合你:

$bytesOutput = 0;

function output_counter ($str) {
  $GLOBALS['bytesOutput'] += strlen($str);
  return $str;
}
ob_start('output_counter');

$stream = fopen('php://output','w');
fwrite($stream, "This is some data\n");

var_dump($bytesOutput);

答案 1 :(得分:1)

我会坚持输出缓冲区回调,您只需返回FALSE即可通过:

class OutputMetricBuffer
{
    private $length;
    public function __construct()
    {
        ob_start(array($this, 'callback'));
    }
    public function callback($str)
    {
        $this->length += strlen($str);
        return FALSE;
    }
    public function getLength()
    {
        ob_flush();
        return $this->length;
    }
}

用法:

$metric = new OutputMetricBuffer;

# ... output ...

$length = $metric->getLength();

使用输出缓冲区回调的原因是因为它比需要使用所有存储桶并将其复制的过滤器更轻量级。所以这是更多的工作。

我在一个类中实现了回调,因此它有自己的私有length变量来计算。

您也可以创建一个全局函数并使用全局变量,但是另一个调整可能是通过$GLOBALS而不是global关键字来访问它,因此PHP不需要导入全局变量进入局部符号表并返回。但我不确定它是否有所作为,只是另一个可以起作用的点。

无论如何,我不知道如果返回FALSE而不是$str会让它更快,只需试一试。