【问题标题】:PHP HTTP Raw I/O Tracking on ApacheApache 上的 PHP HTTP 原始 I/O 跟踪
【发布时间】:2011-12-23 02:53:58
【问题描述】:

我正在尝试找到一种方法来测量传入或传出基于 php+apache 的 Web 应用程序的字节数。一个问题是所有 I/O 都由原生 PHP 扩展完成,该扩展将句柄传递给 built-in streams 之一:php://input 或 php://output。

我已经研究了以下替代方案:

1.) 流包装器上的 ftell 遇到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 模块)

【问题讨论】:

  • 您在问题中写的 “原生 PHP 扩展” 的名称是什么?
  • 我建议你坚持使用ftell 作为输入,如果它有效的话——你不可能找到更好的东西,当然任何对输入和输出都有效的东西。我很想知道a)在写入php://output 流时输出缓冲是否有效(可能应该),以及b)缓冲整个事物会降低多少性能,然后执行$bytesOut = ob_get_length(); ob_end_flush(); - 我排序怀疑这会严重损害性能,但可能值得一试。
  • @hakre:这是一个自定义扩展。不公开,但也不是修改的候选者(这就是我在这个级别询问的原因)。我检查了界面,没有办法从扩展中获取此信息。
  • @DaveRandom:感谢您的想法。我同意输入。 a.) 它确实 b.) 这个应用程序缓冲整个输出并不总是可行的

标签: php apache


【解决方案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 是否会使其更快,请尝试一下。

【讨论】:

    【解决方案2】:

    虽然很奇怪,但使用 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)

    EDIT 实际上,它工作正常吗,还是应该是18?我从不使用ftell(),所以我不能100%确定...

    另一个编辑

    看看这是否适合你:

    $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);
    

    【讨论】:

    • 有趣的观察。我将尝试将该句柄传递给扩展。
    • 再想一想,我不确定 STDOUT 在非 CLI 上下文中是否可用。
    • 不,你是对的,它不存在(刚刚检查过)——嗯,没关系。我会继续玩它,看看我能想出什么。
    • @Adam 我现在不必完全讨论它,但看看我最近编辑的代码是否有帮助(可能没有)
    • 这看起来大致相当于我的问题中的方法#4。它有效,但速度有点慢。
    猜你喜欢
    • 2014-06-05
    • 2020-01-04
    • 2011-06-20
    • 2016-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-24
    • 2021-12-26
    相关资源
    最近更新 更多