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)
I would stick to the output buffer callback you can just return FALSE to pass through:
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;
}
}
Usage:
$metric = new OutputMetricBuffer;
# ... output ...
$length = $metric->getLength();
The reasons to use the output buffer callback is because it's more lightweight than a filter which needs to consume all buckets and copy them over. So it's more work.
I implemented the callback inside a class so it has it's own private length variable to count up with.
You can just create a global function as well and use a global variable, however another tweak might be to access it via $GLOBALS instead of the global keyword so PHP does not need to import the global variable into the local symbol table and back. But I'm not really sure if it makes a difference, just another point which could play a role.
Anyway I don't know as well if returning FALSE instead of $str will make it faster, just give it a try.
As bizarre as this is, using the STDOUT constant instead of the result of fopen('php://output') makes ftell() work correctly.
$stream = fopen('php://output','w');
fwrite($stream, "This is some data\n");
fwrite($stream, ftell($stream));
// Output:
// This is some data
// 0
However:
fwrite(STDOUT, "This is some data\n");
fwrite(STDOUT, ftell(STDOUT));
// Output:
// This is some data
// 17
Tested PHP/5.2.17 (win32)
EDIT actually, is that working correctly, or should it be 18? I never use ftell() so I'm not 100% sure either way...
ANOTHER EDIT
See whether this suits you:
$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);
Related
I use CURL in several individual PHP scripts to download / upload files, is there a way to set a GLOBAL (not per-curl-handle) UL / DL Rate Speed Limit?
Unfortunately, you can only set a speed limit for the single session at CURL, but that's not dynamic.
As a server OS Ubuntu is used, is there an alternative way to limit CURL processes differently?
Thanks
curl/libcurl doesn't have any feature to share a bandwidth limit across curl_easy handles, much less across different processes. i propose a curl daemon to enforce bandwidth limits instead. with the client looking something like
class curl_daemon_response{
public $stdout;
public $stderr;
}
function curl_daemon(array $curl_options):curl_daemon_response{
$from_big_uint64_t=function(string $i): int {
$arr = unpack ( 'Juint64_t', $i );
return $arr ['uint64_t'];
};
$to_big_uint64_t=function(int $i): string {
return pack ( 'J', $i );
};
$conn = stream_socket_client("unix:///var/run/curl_daemon", $errno, $errstr, 3);
if (!$conn) {
throw new \RuntimeError("failed to connect to /var/run/curl_daemon! $errstr ($errno)");
}
stream_set_blocking($conn,true);
$curl_options=serialize($curl_options);
fwrite($conn,$to_big_uint64_t(strlen($curl_options)).$curl_options);
$stdoutLen=$from_big_uint64_t(fread($conn,8));
$stdout=fread($conn,$stdoutLen);
$stderrLen=$from_big_uint64_t(fread($conn,8));
$stderr=fread($conn,$stderrLen);
$ret=new curl_daemon_response();
$ret->stdout=$stdout;
$ret->stderr=$stderr;
fclose($conn);
return $ret;
}
and the daemon looking something like
<?php
declare(strict_types=1);
const MAX_DOWNLOAD_SPEED=1000*1024; // 1000 kilobytes
const MINIMUM_DOWNLOAD_SPEED=100; // 100 bytes per second,
class Client{
public $id;
public $socket;
public $curl;
public $arguments;
public $stdout;
public $stderr;
}
$clients=[];
$mh=curl_multi_init();
$srv = stream_socket_server("unix:///var/run/curl_daemon", $errno, $errstr);
if (!$srv) {
throw new \RuntimeError("failed to create unix socket /var/run/curl_daemon! $errstr ($errno)");
}
stream_set_blocking($srv,false);
while(true){
getNewClients();
$cc=count($clients);
if(!$cc){
sleep(1); // nothing to do.
continue;
}
curl_multi_exec($mh, $running);
if($running!==$cc){
// at least 1 of the curls finished!
while(false!==($info=curl_multi_info_read($mh))){
$key=curl_getinfo($info['handle'],CURLINFO_PRIVATE);
curl_multi_remove_handle($mh,$clients[$key]->curl);
curl_close($clients[$key]->curl);
$stdout=file_get_contents(stream_get_meta_data($clients[$key]->stdout)['uri']); // https://bugs.php.net/bug.php?id=76268
fclose($clients[$key]->stdout);
$stderr=file_get_contents(stream_get_meta_data($clients[$key]->stderr)['uri']); // https://bugs.php.net/bug.php?id=76268
fclose($clients[$key]->stderr);
$sock=$clients[$key]->socket;
fwrite($sock,to_big_uint64_t(strlen($stdout)).$stdout.to_big_uint64_t(strlen($stderr)).$stderr);
fclose($sock);
echo "finished request #{$key}!\n";
unset($clients[$key],$key,$stdout,$stderr,$sock);
}
updateSpeed();
}
curl_multi_select($mh);
}
function updateSpeed(){
global $clients;
static $old_speed=-1;
if(empty($clients)){
return;
}
$clientsn=count($clients);
$per_handle_speed=MAX(MINIMUM_DOWNLOAD_SPEED,(MAX_DOWNLOAD_SPEED/$clientsn));
if($per_handle_speed===$old_speed){
return;
}
$old_speed=$per_handle_speed;
echo "new per handle speed: {$per_handle_speed} - clients: {$clientsn}\n";
foreach($clients as $client){
/** #var Client $client */
curl_setopt($client->curl,CURLOPT_MAX_RECV_SPEED_LARGE,$per_hande_speed);
}
}
function getNewClients(){
global $clients,$srv,$mh;
static $counter=-1;
$newClients=false;
while(false!==($new=stream_socket_accept($srv,0))){
++$counter;
$newClients=true;
echo "new client! request #{$counter}\n";
stream_set_blocking($new,true);
$tmp=new Client();
$tmp->id=$counter;
$tmp->socket=$new;
$tmp->curl=curl_init();
$tmp->stdout=tmpfile();
$tmp->stderr=tmpfile();
$size=from_big_uint64_t(fread($new,8));
$arguments=fread($new,$size);
$arguments=unserialize($arguments);
assert(is_array($arguments));
$tmp->arguments=$arguments;
curl_setopt_array($tmp->curl,$arguments);
curl_setopt_array($tmp->curl,array(
CURLOPT_FILE=>$tmp->stdout,
CURLOPT_STDERR=>$tmp->stderr,
CURLOPT_VERBOSE=>1,
CURLOPT_PRIVATE=>$counter
));
curl_multi_add_handle($mh,$tmp->curl);
}
if($newClients){
updateSpeed();
}
}
function from_big_uint64_t(string $i): int {
$arr = unpack ( 'Juint64_t', $i );
return $arr ['uint64_t'];
}
function to_big_uint64_t(int $i): string {
return pack ( 'J', $i );
}
note: this is completely untested code, because my development environment died literally a couple of hours ago, and i wrote all this in notepad++. (my dev env won't boot at all, it's a VM, not sure wtf happened, but haven't fixed it yet )
also, the code is not at all optimized for large file transfers, if you need to support big file transfers this way (sizes you don't want packed up in ram, like gigabytes+), then modify the daemon to return filepaths instead of writing all the data over an unix socket.
I am totally befuddled as to why the filter function is entered twice: once over the contents of the memory stream, and a second time over nothing (thus while loop never entered). It causes a bug when you actually want to use the stream construct to prepend and append a quoting character.
Program:
<?php
class Trivial extends php_user_filter {
function filter($in, $out, &$consumed, $closing) {
print("entering filter:\n");
while ($bucket = stream_bucket_make_writeable($in)) {
print("in the loop\n");
$consumed += $bucket->datalen;
stream_bucket_append($out, $bucket);
}
print("returning from filter\n\n");
return PSFS_PASS_ON;
}
}
$stream = #fopen("php://temp", 'r+');
fwrite($stream, "Hello", 5);
rewind($stream);
stream_filter_register('trivial', 'Trivial');
stream_filter_append($stream, 'trivial', STREAM_FILTER_READ);
$result = fread($stream, 8192);
print("stream: '" . $result . "'\n");
?>
Output:
entering filter:
in the loop
returning from filter
entering filter:
returning from filter
stream: 'Hello'
Edit Checked on PHP7.02 and on my PHP 5.6 on my Mac laptop and Ubuntu 16.04 Linux; this is not a bug or version quirk.
Edit 2 I think I'm already pretty close to the answer (by very unsatisfying inference though): $closing is false the first time and true the second time.
I'd like to perform an operation on the php://input stream but also return it.
For example, what I'd like to achieve is that:
php://input ==> OPERATION ==> php://input
Is that possible to make something like that ?
$input = fopen("php://input", "r");
$output = fopen("php://input", "w");
while (($buffer.= fgets($input, 1024)) !== false) {
// Do something with that buffer
// ??
fwrite($output, $buffer);
}
fclose($output);
fclose($input);
If you are fine with one of the operations supported as a filter in php, you can use the php://filter fopen wrapper.
Let's say you want to base64 decode the data for example:
$data = file_get_contents('php://filter/read=convert.base64-decode/resource=php://input');
Or:
$input = fopen('php://filter/read=convert.base64-decode/resource=php://input');
// now you can pass $input to somewhere and every read operation will
// return base64 decoded data ...
However, the set of operations supported as a filter in PHP is quite limited. If it does not fit your needs I would suggest to wrap the file pointer, in a class maybe. Here comes a very basic example, you might add buffering, caching or whatever...
class Input {
public static function read() {
return $this->process(file_get_contents('php://stdin'));
}
public function process($data) {
return do_whatever_with($data);
}
}
Then in the application code use:
$input = Input::read();
I'm new to OOP terminology, I am trying to create a class that make a hit counter.
I try the code below but it create just a counter.txt page with inside value 1. I dont know why its not incrementing.
class LOGFILE {
public function READ($FileName) {
$handle = fopen($FileName, 'r');
$fread = file_get_contents($FileName);
return $fread;
fclose($handle);
}
public function WRITE($FileName, $FileData) {
$handle = fopen($FileName, 'w');
$FileData = $fread +1;
fwrite($handle, $FileData);
fclose($handle);
}
}
$logfile = new LOGFILE();
$logfile -> WRITE("counter.txt",$FileData);
echo $logfile -> READ("counter.txt");
The reason is that $fread is local variable for both READ and WRITE methods. You need to make it private global variable for your class:
class LOGFILE {
private $fread;
public function READ($FileName) {
$this->fread = file_get_contents($FileName);
return $this->fread;
}
public function WRITE($FileName) {
$this->READ($FileName);
$handle = fopen($FileName, 'w');
$FileData = $this->fread +1;
fwrite($handle, $FileData);
fclose($handle);
}
}
$logfile = new LOGFILE();
$logfile -> WRITE("counter.txt");
echo $logfile -> READ("counter.txt");
Note: I have removed fopen and fclose because file_get_contents does not need it. In write you can use file_put_contents. Removed not used variable $FileData too. It's always a good practice to create variables methods and classes when they are needed.
Also take a look at best practices how to name your classes, variables, methods and so on. Here's best guide, IMO.
Let's start going over the corrected code and see what was missing:
<?php
class LOGFILE {
public function READ($FileName) {
$handle = fopen($FileName, 'r');
$fread = fgets($handle, 8192);
fclose($handle);
return $fread;
}
public function WRITE($FileName, $FileData) {
$counter = $this->READ($FileName);
$handle = fopen($FileName, 'w');
fwrite($handle, $FileData + $counter);
fclose($handle);
}
}
$logfile = new LOGFILE();
$FileData = 1;
$logfile -> WRITE("counter.txt",$FileData);
echo $logfile -> READ("counter.txt")."\n";
$logfile -> WRITE("counter.txt",$FileData);
echo $logfile -> READ("counter.txt")."\n";
?>
use of fgets instead of file_get_contents in READ (you can choose to use file_get_contents but I rather stay consistent with the other function that uses fopen)
use of READ inside function WRITE (the principal of code-reuse)
open of file with write permissions in WRITE: 'w'
init $FileData = 1;
no need to hold a private member: $fread
most important: do not write statements after return (like you did in READ) - statements that are written after return will not be executed!
This solution was tested successfully.
OOP must be used where it's needed. You need a simple thing so, no need of OOP.
<?php
function addValue($file='counter.txt', $amount=1) {
if( false == is_file($file) ) {
return false;
}
$initial = file_get_contents($file);
return #file_put_contents($initial+$amount);
}
addValue();
?>
Test your OOP knowledge on something complex, like a shopping cart or some other concept.
EDIT // so, if you need a simple example that looks complex, here you go :)
<?php
class log {
public $file = '';
private $amount = 0;
public function __construct( $file ) {
$this->file = $file;
$this->amount = 1;
}
public function makeAdd() {
$initial = file_get_contents($this->file);
return #file_put_contents($this->file, $initial + $this->amount);
}
function __call($f, $args) {
switch( $f ) {
case 'add':
if(isset($args[0]) && !empty($args[0])) {
$this->amount = (int)$args[0];
}
if( $this->amount == 0 ) {
throw new Exception('Not a valid amount.');
}
return $this->makeAdd();
break;
}
}
}
try {
// create log
$L = new log('count.txt');
// this will add 2
var_dump($L->add(2));
// this will also add 2
var_dump($L->add());
// until you rewrite the amount
var_dump($L->add(1));
// final result -> 5
} catch(Exception $e) {
die($e->getMessage());
}
?>
Good luck!
Use UpperCamelCase for class names. LogFile, not LOGFILE. When you have a variable and the most interesting thing about it is that it's expected to hold a reference to something that is_a LogFile you should name it logFile.
Use lowerCamelCase for functions. read and write, not READ and WRITE
No spaces around the arrow operator
Code after a return statement in a method can never be reached, so delete it.
read() does not use the handle returned by fopen, so don't call fopen
the temp variable $freed doesn't help us understand the code, so we can lose it
read is a slightly unconventional name. If we rename the function to getCount it will be more obvious what it does.
You said you wanted to make a hit counter. So rename the class from LogFile to HitCounter, and the variable to hitCounter
the $FileData parameter to write doesn't get used because the variable is re-assigned inside the function. We can lose it.
The write method is supposed to add one to the number in the file. Write doesn't really express that. Rename it to increment.
Use a blank line between functions. The procedural code at the end should generally be in a separate file, but here we can just add a couple of extra lines. Delete the blanks between the last three lines of code.
Don't repeat yourself - we shouldn't have to mention 'counter.txt' more than once. OOP is all about combining data structures and behaviour into classes, so make a class private variable to hold the filename, and pass it via a constructor
$fread doesn't exist in the scope of increment, so we can't use it. This won't work. Replace it with a call to to getCount()
Swap the first two lines of increment, so we're not doing two concurent accesses to the same file, although we might be running inside a server that's running our script twice and still doing two concurrent accesses.
Rename the variable $FileData to $count, since that's what it is.
Replace the fopen,fwrite,fclose sequence with file_put_contents, since that does the same thing and is more succinct.
We need tag, since our php code continues to the end of the file.
That leaves us with:
<?php
class HitCounter {
private $fileName;
public function __construct($fileName){
$this->fileName = $fileName;
}
public function getCount() {
return file_get_contents($this->fileName);
}
public function increment() {
$count = $this->getCount() + 1;
file_put_contents($this->fileName, $count);
}
}
$hitCounter = new HitCounter("counter.txt");
$hitCounter->increment();
echo $hitCounter->getCount();
You can create a static counter and increment it each time (instead of create file)
<?php
class CountClass {
public static $counter = 0;
function __construct() {
self::$counter++;
}
}
new CountClass();
new CountClass();
echo CountClass::$counter;
?>
Currently I want to read some data (metadata, scene names, mesh count, vertices count ...) from a .blend file with the unpack() function of PHP refering to the Blender SDNA documentation:
http://www.atmind.nl/blender/blender-sdna-256.html
Is there some easy solution to read all these information with some existing classes or libraries or do I have to read block by block from the file and write my own functions / clas / library (so I can create something like an object)?
After consultation with php manual I can tell you that php just doesn't provide way to read binary files, but I think there's quite nice way to do this (inspirited by c and fread)
class BinaryReader {
const FLOAT_SIZE = 4;
protected $fp = null; // file pointer
...
public function readFloat() {
$data = fread( $fp, self::FLOAT_SIZE);
$array = unpack( 'f', $data);
return $array[0];
}
// Reading unsigned short int
public function readUint16( $endian = null){
if( $endian === null){
$endian = $this->getDefaultEndian();
}
// Assuming _fread handles EOF and similar things
$data = $this->_fread( 2);
$array = unapack( ($endian == BIG_ENDIAN ? 'n' : 'v'), $data);
return $array[0];
}
// ... All other binary type functions
// You may also write it more general:
public function readByReference( &$variable){
switch( get_type( $variable)){
case 'double':
return $this->readDouble();
...
}
}
}
If you have any improvements or tips, just post them in the comment I'll be glad to extend the answer.