I'm trying to find a way to measure bytes transferred in or out of a web application built on php+apache. One problem is that all I/O is done by a native PHP extension, which is passed a handle to one of the built-in streams: php://input or php://output.
I have examined the following alternatives:
1.) ftell on stream wrapper After encountering this question, my first intuition was to try using ftell on the stream wrapper handle after the I/O operation; roughly:
$hOutput = fopen('php://output', 'wb');
extensionDoOutput($hOutput);
$iBytesTransferred = ftell($hOutput);
This seems to work for the input wrapper, but not the output (which always returns zero from ftell).
2.) Attach stream filter A non-modifying stream filter would seem like a reasonable way to count bytes passing through. However, the documentation seems a bit lacking and I haven't found a way to get at lengths without doing the iterate+copy pattern as in the example:
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
Unfortunately this seems to impose a significant reduction in throughput (>50%) as the data is copied in and out of the extension.
3.) Implement stream wrapper A custom stream wrapper could be used to wrap the other stream and accumulate bytes read/written:
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);
Again, this imposes a reduction in throughput (~20% on output, greater on input)
4.) Output buffering with callback A callback can be provided with ob_start to be called as chunks of output are flushed.
$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();
Again, this works but imposes a certain throughput performance penalty (~25%) due to buffering.
Option #1 was forgone because it does not appear to work for output. Of the remaining three, all work functionally but affect throughput negatively due to buffer/copy mechanisms.
Is there something instrinsic to PHP (or the apache server extensions) that I can use to do this gracefully, or will I need to bite the bullet on performance? I welcome any ideas on how this might be accomplished. (note: if possible I am interested in a PHP application-level solution... not an apache module)