Repository: jokkedk/webgrind (language: php)

Issue:
Support gzip compressed callgrind files
A bit pre-mature here, but Xdebug 3.1 will start compressing profiles, which was added in https://github.com/xdebug/xdebug/pull/734. For the PHP preprocessor, it looks like either the `compress.zlib://` [stream wrapper](https://www.php.net/manual/en/wrappers.compression.php) can be used with the existing functions or they can be swapped out for the [zlib](https://www.php.net/manual/en/ref.zlib.php) functions:

`fopen()` -> `gzopen()`
`fgets()` -> `gzgets()`
`fscanf()` -> `sscanf(gzgets())`

I'm not sure what need changes need to be made in the cpp preprocessor.


Discussion / hints:
For PHP, probably only `gzopen()` is required; the other file operations are supposed to be transparent.

For C++, would require some more lifting.  To avoid external dependencies, could embed the minimal deflate decompressor: https://github.com/madler/zlib/tree/master/contrib/puff .  To reuse the current preprocessor implementation, would need to create a custom istream.
Since my original post, the `xdebug.use_compression` setting was added in https://github.com/xdebug/xdebug/pull/768 to give users control over profile compression. So, this is no longer critical for Xdebug 3.1 support, though it would be a nice feature to have.

Fix patch:
diff --git a/library/FileHandler.php b/library/FileHandler.php
index 36432a2..d09ac9f 100644
--- a/library/FileHandler.php
+++ b/library/FileHandler.php
@@ -55,14 +55,14 @@ private function getInvokeUrl($file) {
             return 'Webgrind internal';
 
         // Grab name of invoked file.
-        $fp = fopen($file, 'r');
+        $fp = gzopen($file, 'r');
         $invokeUrl = '';
         while ((($line = fgets($fp)) !== FALSE) && !strlen($invokeUrl)) {
             if (preg_match('/^cmd: (.*)$/', $line, $parts)) {
                 $invokeUrl = isset($parts[1]) ? $parts[1] : '';
             }
         }
-        fclose($fp);
+        gzclose($fp);
         if (!strlen($invokeUrl))
             $invokeUrl = 'Unknown!';
 
diff --git a/library/Preprocessor.php b/library/Preprocessor.php
index 7582d47..8535910 100644
--- a/library/Preprocessor.php
+++ b/library/Preprocessor.php
@@ -49,7 +49,7 @@ static function parse($inFile, $outFile)
             return;
         }
 
-        $in = @fopen($inFile, 'rb');
+        $in = @gzopen($inFile, 'rb');
         if (!$in)
             throw new Exception('Could not open '.$inFile.' for reading.');
         $out = @fopen($outFile, 'w+b');
@@ -199,7 +199,7 @@ static function parse($inFile, $outFile)
             fwrite($out, pack(self::NR_FORMAT, $address));
         }
 
-        fclose($in);
+        gzclose($in);
         fclose($out);
     }
 
diff --git a/library/gzstream.h b/library/gzstream.h
new file mode 100644
index 0000000..be684b2
--- /dev/null
+++ b/library/gzstream.h
@@ -0,0 +1,219 @@
+// ============================================================================
+// gzstream, C++ iostream classes wrapping the zlib compression library.
+// Copyright (C) 2001  Deepak Bandyopadhyay, Lutz Kettner
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+// ============================================================================
+//
+// File          : gzstream.h
+// Revision      : $Revision: 1.5 $
+// Revision_date : $Date: 2002/04/26 23:30:15 $
+// Author(s)     : Deepak Bandyopadhyay, Lutz Kettner
+// 
+// Standard streambuf implementation following Nicolai Josuttis, "The 
+// Standard C++ Library".
+// ============================================================================
+
+#ifndef GZSTREAM_H
+#define GZSTREAM_H 1
+
+// standard C++ with new header file names and std:: namespace
+#include <iostream>
+#include <fstream>
+#include <zlib.h>
+#include <string>
+
+#ifdef GZSTREAM_NAMESPACE
+namespace GZSTREAM_NAMESPACE {
+#endif
+
+// ----------------------------------------------------------------------------
+// Internal classes to implement gzstream. See below for user classes.
+// ----------------------------------------------------------------------------
+
+class gzstreambuf : public std::streambuf {
+private:
+    static const int bufferSize = 47+256;    // size of data buff
+    // totals 512 bytes under g++ for igzstream at the end.
+
+    gzFile           file;               // file handle for compressed file
+    char             buffer[bufferSize]; // data buffer
+    char             opened;             // open/close state of stream
+    int              mode;               // I/O mode
+
+    
+    int flush_buffer() {
+        // Separate the writing of the buffer from overflow() and
+        // sync() operation.
+        int w = pptr() - pbase();
+        if ( gzwrite( file, pbase(), w) != w)
+            return EOF;
+        pbump( -w);
+        return w;
+    }
+public:
+    gzstreambuf() : opened(0) {
+        setp( buffer, buffer + (bufferSize-1));
+        setg( buffer + 4,     // beginning of putback area
+              buffer + 4,     // read position
+              buffer + 4);    // end position      
+        // ASSERT: both input & output capabilities will not be used together
+    }
+    int is_open() { return opened; }
+
+    ~gzstreambuf() { close(); }
+    gzstreambuf* open( const char* name, int open_mode) {
+        if ( is_open())
+            return (gzstreambuf*)0;
+        mode = open_mode;
+        // no append nor read/write mode
+        if ((mode & std::ios::ate) || (mode & std::ios::app)
+            || ((mode & std::ios::in) && (mode & std::ios::out)))
+            return (gzstreambuf*)0;
+        char  fmode[10];
+        char* fmodeptr = fmode;
+        if ( mode & std::ios::in)
+            *fmodeptr++ = 'r';
+        else if ( mode & std::ios::out)
+            *fmodeptr++ = 'w';
+        *fmodeptr++ = 'b';
+        *fmodeptr = '\0';
+        file = gzopen( name, fmode);
+        if (file == 0)
+            return (gzstreambuf*)0;
+        opened = 1;
+        return this;
+    }
+    
+    gzstreambuf * close() {
+        if ( is_open()) {
+            sync();
+            opened = 0;
+            if ( gzclose( file) == Z_OK)
+                return this;
+        }
+        return (gzstreambuf*)0;
+    }
+    
+    virtual int underflow() { // used for input buffer only
+        if ( gptr() && ( gptr() < egptr()))
+            return * reinterpret_cast<unsigned char *>( gptr());
+        
+        if ( ! (mode & std::ios::in) || ! opened)
+            return EOF;
+        // Josuttis' implementation of inbuf
+        int n_putback = gptr() - eback();
+        if ( n_putback > 4)
+            n_putback = 4;
+        memcpy( buffer + (4 - n_putback), gptr() - n_putback, n_putback);
+        
+        int num = gzread( file, buffer+4, bufferSize-4);
+        if (num <= 0) // ERROR or EOF
+            return EOF;
+        
+        // reset buffer pointers
+        setg( buffer + (4 - n_putback),   // beginning of putback area
+             buffer + 4,                 // read position
+             buffer + 4 + num);          // end of buffer
+        
+        // return next character
+        return * reinterpret_cast<unsigned char *>( gptr());    
+    }
+    
+    virtual int overflow( int c=EOF) { // used for output buffer only
+        if ( ! ( mode & std::ios::out) || ! opened)
+            return EOF;
+        if (c != EOF) {
+            *pptr() = c;
+            pbump(1);
+        }
+        if ( flush_buffer() == EOF)
+            return EOF;
+        return c;
+    }
+    
+    virtual int sync() {
+        // Changed to use flush_buffer() instead of overflow( EOF)
+        // which caused improper behavior with std::endl and flush(),
+        // bug reported by Vincent Ricard.
+        if ( pptr() && pptr() > pbase()) {
+            if ( flush_buffer() == EOF)
+                return -1;
+        }
+        return 0;
+    }    
+};
+
+class gzstreambase : virtual public std::ios {
+protected:
+    gzstreambuf buf;
+public:
+    
+    gzstreambase() { init(&buf); }
+    gzstreambase( const char* name, int mode) {
+        init( &buf);
+        open( name, mode);
+    }
+    ~gzstreambase() {
+        buf.close();
+    }
+    void open( const char* name, int open_mode) {
+        if ( ! buf.open( name, open_mode))
+            clear( rdstate() | std::ios::badbit);
+    }
+    
+    void close() {
+        if ( buf.is_open())
+            if ( ! buf.close())
+                clear( rdstate() | std::ios::badbit);
+    }
+    gzstreambuf* rdbuf() { return &buf; }
+};
+
+// ----------------------------------------------------------------------------
+// User classes. Use igzstream and ogzstream analogously to ifstream and
+// ofstream respectively. They read and write files based on the gz* 
+// function interface of the zlib. Files are compatible with gzip compression.
+// ----------------------------------------------------------------------------
+
+class igzstream : public gzstreambase, public std::istream {
+public:
+    igzstream() : std::istream( &buf) {} 
+    igzstream( const char* name, int open_mode = std::ios::in)
+        : gzstreambase( name, open_mode), std::istream( &buf) {}  
+    gzstreambuf* rdbuf() { return gzstreambase::rdbuf(); }
+    void open( const char* name, int open_mode = std::ios::in) {
+        gzstreambase::open( name, open_mode);
+    }
+};
+
+class ogzstream : public gzstreambase, public std::ostream {
+public:
+    ogzstream() : std::ostream( &buf) {}
+    ogzstream( const char* name, int mode = std::ios::out)
+        : gzstreambase( name, mode), std::ostream( &buf) {}  
+    gzstreambuf* rdbuf() { return gzstreambase::rdbuf(); }
+    void open( const char* name, int open_mode = std::ios::out) {
+        gzstreambase::open( name, open_mode);
+    }
+};
+
+#ifdef GZSTREAM_NAMESPACE
+} // namespace GZSTREAM_NAMESPACE
+#endif
+
+#endif // GZSTREAM_H
+// ============================================================================
+// EOF //
diff --git a/library/preprocessor.cpp b/library/preprocessor.cpp
index 74a8c7e..cc8494c 100644
--- a/library/preprocessor.cpp
+++ b/library/preprocessor.cpp
@@ -22,6 +22,11 @@
 #include <string>
 #include <vector>
 
+#ifdef WITH_ZLIB
+#include <cstring>
+#include "gzstream.h"
+#endif
+
 /**
  * Fileformat version. Embedded in the output for parsers to use.
  */
@@ -122,7 +127,11 @@ class Webgrind_Preprocessor
      */
     void parse(const char* inFile, const char* outFile, std::vector<std::string>& proxyFunctions)
     {
+#ifdef WITH_ZLIB
+        igzstream in(inFile);
+#else
         std::ifstream in(inFile);
+#endif
         std::ofstream out(outFile, std::ios::out | std::ios::binary | std::ios::trunc);
 
         std::map< int, std::queue<ProxyData> > proxyQueue;
diff --git a/makefile b/makefile
index bbbcf23..50013dc 100644
--- a/makefile
+++ b/makefile
@@ -15,6 +15,6 @@ clean:
 	rm -f $(OUT)
 
 $(OUT): $(SRCS)
-	$(CXX) -o $(OUT) -O2 -s $(SRCS)
+	$(CXX) -o $(OUT) -O2 -s $(SRCS) -lz -DWITH_ZLIB
 
 .PHONY: all help clean


Test patch: