title: PHP fread ssh stream significantly slow
body: <p><strong>Scenario:</strong> I needed a function to get <code>STDOUT</code> of a command run through SSH asynchronously. This has various uses including (and most importantly) reading files through SSH.
  An important feature of this function is that it is asynchronous, hence I can display output as it is returned from the server (or give an estimate of the file download progress). This differs from the common approach of using <code>ssh_move()</code> to download files.</p>
  
  <pre><code>function ssh_exec($dsn, $cmd, $return=true, $size_est=null){
      $BUFFER_SIZE = $return ? (1024 * 1024) : 1;
      debug('ssh2_exec '.$cmd);
      $stream = ssh2_exec(ssh_open($dsn), $cmd);
      debug('stream_set_blocking');
      stream_set_blocking($stream, true);
      debug('ssh2_fetch_stream');
      $stream_out = ssh2_fetch_stream($stream, SSH2_STREAM_STDIO);
      stream_set_blocking($stream_out, true);
      debug('stream_get_contents');
      $data = ''; $stime = $oldtime = microtime(true); $data_len = 0;
      if(!$return){
          write_message("\033[0m".'  Execution Output:'.PHP_EOL.'    ');
      }
      while(!feof($stream_out)){
          $buff = fread($stream_out, $BUFFER_SIZE);
          if($buff===false)throw new Exception('Unexpected result from fread().');
          if($buff===''){
              debug('Empty result from fread()...breaking.');
              break;
          }
          $data .= $buff;
          if($return){
              $buff_len = strlen($buff);
              $data_len += $buff_len;
              $newtime = microtime(true);
              debugo('stream_get_contents '.bytes_to_human($data_len)
                  .' @ '.bytes_to_human($buff_len / ($newtime - $oldtime)).'/s'
                  .' ['.($size_est ? number_format($data_len / $size_est * 100, 2) : '???').'%]');
              $oldtime = $newtime;
          }else{
              echo str_replace(PHP_EOL, PHP_EOL.'    ', $buff);
          }
      }
      if($return){
          debugo('stream_get_contents Transferred '.bytes_to_human(strlen($data)).' in '.number_format(microtime(true) - $stime, 2).'s');
          return $data;
      }
  }
  </code></pre>
  
  <p><strong>Usage:</strong> The function is used like so:</p>
  
  <pre><code>$dsn = 'ssh2.exec://root:pass@host/';
  $size = ssh_size($dsn, 'bigfile.zip');
  $zip = ssh_exec($dsn, 'cat bigfile.zip', true, $size);
  </code></pre>
  
  <p><strong>Note 1:</strong> An explanation of some non-standard functions:</p>
  
  <ul>
  <li><code>debug($message)</code> - Writes debug message to console.</li>
  <li><code>ssh_open($dsn)</code> - Takes in an SSH URI and returns an SSH connection handle.</li>
  <li><code>bytes_to_human($bytes)</code> - Converts the number of bytes to human readable format (eg: 6gb)</li>
  <li><code>debugo($message)</code> - The same as <code>debug()</code> but overwrites last line.</li>
  </ul>
  
  <p><strong>Note 2:</strong> The parameter <code>$size_est</code> is used in the progress indicator; usually you'd first get the file size and then attempt to download it (as in my example). It is optional so that it can be ignored when you just want to run an SSH command.</p>
  
  <p><strong>The Problem:</strong> Running the same download operation via <code>scp root@host:/bigfile.zip ./</code>, I get speeds up to 1mb/s whereas this script seems to limit to 70kb/s. I'd like to know why and how to improve this.</p>
  
  <p><strong>Edit:</strong> Also, I'd like to know how/if <code>$BUFFER_SIZE</code> does any difference.</p>