cli_info: Dump per frame metrics for trace files

Added extra information to cli_info output which includes:
* container type (compression method)
* container size (the size of the data in the storage container)
* actual data size (the size of uncompressed data)
* per frame metrics which include the folowing information:
  * first call_id
  * last call_id
  * the total amount of calls in a frame
  * uncompressed frame size

Usage:
  apitrace info --dump-frames /path/to/file.trace

An example output:

{
  "FileName": "/path/to/file.trace",
  "ContainerVersion": 6,
  "ContainerType": "Snappy",
  "API": "OpenGL + GLX/WGL/CGL",
  "FramesCount": 1789,
  "ActualDataSize": 5272672851,
  "ContainerSize": 1547699157,
  "Frames": [{
    "FirstCallId": 0,
    "LastCallId": 2231,
    "TotalCalls": 2232,
    "SizeInBytes": 1259924
  }, {
    "FirstCallId": 2232,
    "LastCallId": 2251,
    "TotalCalls": 20,
    "SizeInBytes": 449
  }, {
    [...]
  ]}
}

This information could be useful to analyze the data distribution in a
trace file, find transition points menu/game/etc, choose better point
for a trace file trimming, and so on.

Change-Id: I7eb98a8303393af229b6afdcb3036fea873b38d1
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/apitrace/+/2272088
Tested-by: Robert Tarasov <tutankhamen@chromium.org>
Commit-Queue: Robert Tarasov <tutankhamen@chromium.org>
Reviewed-by: David Riley <davidriley@chromium.org>
diff --git a/cli/cli_info.cpp b/cli/cli_info.cpp
index 4c5fd0c..2845fb3 100644
--- a/cli/cli_info.cpp
+++ b/cli/cli_info.cpp
@@ -48,8 +48,7 @@
 #include "trace_callset.hpp"
 #include "trace_option.hpp"
 
-static trace::CallSet calls(trace::FREQUENCY_ALL);
-static const char *synopsis = "Print given trace file(s) information.";
+static const char *synopsis = "Print given trace file(s) information in JSON format";
 
 static void
 usage(void)
@@ -59,13 +58,13 @@
         << synopsis << "\n"
         "\n"
         "    -h, --help        show this help message and exit\n"
-        "    --json            output trace file information in JSON format\n"
+        "    --dump-frames     dump per frame information\n"
         "\n"
     ;
 }
 
 enum {
-    JSON_OPT = CHAR_MAX + 1,
+    DUMP_FRAMES_OPT = CHAR_MAX + 1,
 };
 
 const static char *
@@ -74,7 +73,7 @@
 const static struct option
 longOptions[] = {
     {"help", no_argument, 0, 'h'},
-    {"json", no_argument, 0, JSON_OPT},
+    {"dump-frames", no_argument, 0, DUMP_FRAMES_OPT},
     {0, 0, 0, 0}
 };
 
@@ -85,18 +84,24 @@
   return trace::API_NAMES[api];
 }
 
+struct FrameEntry {
+    size_t firstCallId, lastCallId;
+    size_t totalCalls;
+    size_t sizeInBytes;
+};
+
 static int
 command(int argc, char *argv[])
 {
-    bool json = false;
+    bool flagDumpFrames = false;
     int opt;
     while ((opt = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
         switch (opt) {
         case 'h':
             usage();
             return 0;
-        case JSON_OPT:
-            json = true;
+        case DUMP_FRAMES_OPT:
+            flagDumpFrames = true;
             break;
         default:
             std::cerr << "error: unexpected option `" << (char)opt << "`\n";
@@ -105,6 +110,9 @@
         }
     }
 
+    typedef std::vector<FrameEntry> FrameEntries;
+    FrameEntries frames;
+
     for (int i = optind; i < argc; ++i) {
         unsigned long framesCount = 0;
         trace::API api = trace::API_UNKNOWN;
@@ -115,31 +123,68 @@
         }
 
         trace::Call *call;
+        size_t callsInFrame = 0;
+        size_t firstCallId = 0;
+        size_t frameBytesOffset = 0;
+        bool endFrame = true;
         while ((call = p.parse_call())) {
+            if (flagDumpFrames) {
+                ++callsInFrame;
+                if (endFrame) {
+                    firstCallId = call->no;
+                    endFrame = false;
+                }
+            }
             if (api == trace::API_UNKNOWN && p.api != trace::API_UNKNOWN)
-              api = p.api;
-            if (call->flags & trace::CALL_FLAG_END_FRAME)
-              ++framesCount;
+                api = p.api;
+            if (call->flags & trace::CALL_FLAG_END_FRAME) {
+                ++framesCount;
+                if (flagDumpFrames) {
+                    size_t curBytesOffset = p.dataBytesRead();
+                    frames.push_back(
+                        FrameEntry {
+                            firstCallId,
+                            call->no,
+                            callsInFrame,
+                            curBytesOffset-frameBytesOffset
+                        }
+                    );
+                    frameBytesOffset = curBytesOffset;
+                    endFrame = true;
+                    callsInFrame = 0;
+                }
+            }
             delete call;
         }
 
-        if (json) {
+        std::cout <<
+            "{" << std::endl <<
+            "  \"FileName\": \"" << argv[i] << "\"," << std::endl <<
+            "  \"ContainerVersion\": " << p.getVersion() << "," << std::endl <<
+            "  \"ContainerType\": \"" << p.containerType() << "\"," << std::endl <<
+            "  \"API\": \"" << getApiName(api) << "\"," << std::endl <<
+            "  \"FramesCount\": " << framesCount << "," << std::endl <<
+            "  \"ActualDataSize\": " << p.dataBytesRead() << "," << std::endl <<
+            "  \"ContainerSize\": " << p.containerSizeInBytes();
+        if (flagDumpFrames) {
+            std::cout << "," << std::endl;
             std::cout <<
-                "{" <<
-                    "\"FileName\":\"" << argv[i] << "\"," <<
-                    "\"ContainerVersion\":" << p.getVersion() << "," <<
-                    "\"API\":\"" << getApiName(api) << "\"," <<
-                    "\"FramesCount\":" << framesCount << "" <<
-                "}" << std::endl;
+                "  \"Frames\": [{" << std::endl;
+            for (auto it = frames.begin(); it != frames.end(); ++it) {
+                std::cout <<
+                    "    \"FirstCallId\": " << it->firstCallId << "," << std::endl <<
+                    "    \"LastCallId\": " << it->lastCallId << "," << std::endl <<
+                    "    \"TotalCalls\": " << it->totalCalls << "," << std::endl <<
+                    "    \"SizeInBytes\": " << it->sizeInBytes << std::endl;
+                if (it != std::prev(frames.end())) {
+                    std::cout << "  }, {" << std::endl;
+                }
+            }
+            std::cout << "  }]" << std::endl;
         } else {
-            std::cout <<
-                std::endl <<
-                "FileName: " << argv[i] << std::endl <<
-                "ContainerVersion: " << p.getVersion() << std::endl <<
-                "API: " << getApiName(api) << std::endl <<
-                "FramesCount: " << framesCount << std::endl <<
-                std::endl;
+            std::cout << std::endl;
         }
+        std::cout << "}" << std::endl;
     }
 
     return 0;
diff --git a/lib/trace/trace_file.hpp b/lib/trace/trace_file.hpp
index 26b525c..5d70dc8 100644
--- a/lib/trace/trace_file.hpp
+++ b/lib/trace/trace_file.hpp
@@ -59,7 +59,16 @@
     void close(void);
     int getc(void);
     bool skip(size_t length);
-    int percentRead(void);
+    int percentRead(void) const;
+
+    // returns the size of (compressed/serialized) data in the container in bytes
+    virtual size_t containerSizeInBytes(void) const = 0;
+    // returns the amount of bytes read from the container
+    virtual size_t containerBytesRead(void) const = 0;
+    // returns the size of uncompressed data read in bytes
+    virtual size_t dataBytesRead(void) const = 0;
+    // returns the name of the continer type as a user friendly string
+    virtual const char* containerType() const = 0;
 
     virtual bool supportsOffsets(void) const;
     virtual File::Offset currentOffset(void) const;
@@ -70,7 +79,6 @@
     virtual int rawGetc(void) = 0;
     virtual void rawClose(void) = 0;
     virtual bool rawSkip(size_t length) = 0;
-    virtual int rawPercentRead(void) = 0;
 
 protected:
     bool m_isOpened = false;
@@ -99,12 +107,14 @@
     return rawRead(buffer, length);
 }
 
-inline int File::percentRead(void)
+inline int File::percentRead(void) const
 {
-    if (!m_isOpened) {
-        return 0;
+    size_t size = containerSizeInBytes();
+    size_t read = containerBytesRead();
+    if (size) {
+        return static_cast<int>((100*read)/size);
     }
-    return rawPercentRead();
+    return 0;
 }
 
 inline void File::close(void)
diff --git a/lib/trace/trace_file_brotli.cpp b/lib/trace/trace_file_brotli.cpp
index 998f390..5053a45 100644
--- a/lib/trace/trace_file_brotli.cpp
+++ b/lib/trace/trace_file_brotli.cpp
@@ -51,14 +51,20 @@
     virtual int rawGetc(void) override;
     virtual void rawClose(void) override;
     virtual bool rawSkip(size_t length) override;
-    virtual int  rawPercentRead(void) override;
+
+    size_t containerSizeInBytes(void) const override;
+    size_t containerBytesRead(void) const override;
+    size_t dataBytesRead(void) const override;
+    const char *containerType(void) const override;
 private:
     BrotliDecoderState *state;
-    std::ifstream m_stream;
+    mutable std::ifstream m_stream;
     static const size_t kFileBufferSize = 65536;
     uint8_t input[kFileBufferSize];
     const uint8_t* next_in;
     size_t available_in;
+    std::streampos m_endPos = 0;
+    size_t m_dataBytesRead = 0;
 };
 
 BrotliFile::BrotliFile(void)
@@ -80,6 +86,14 @@
                                   | std::fstream::in;
 
     m_stream.open(filename, fmode);
+
+    if (m_stream.is_open()) {
+        m_stream.seekg(0, std::ios::end);
+        m_endPos = m_stream.tellg();
+        m_stream.seekg(0, std::ios::beg);
+        m_dataBytesRead = 0;
+    }
+
     return m_stream.is_open();
 }
 
@@ -117,6 +131,8 @@
 
     assert(next_out - output <= length);
 
+    m_dataBytesRead += static_cast<size_t>(next_out - output);
+
     return next_out - output;
 }
 
@@ -139,11 +155,21 @@
     return false;
 }
 
-int BrotliFile::rawPercentRead(void)
-{
-    return 0;
+size_t BrotliFile::containerSizeInBytes(void) const {
+    return static_cast<size_t>(m_endPos);
 }
 
+size_t BrotliFile::containerBytesRead(void) const {
+    return static_cast<size_t>(m_stream.tellg());
+}
+
+size_t BrotliFile::dataBytesRead(void) const {
+    return m_dataBytesRead;
+}
+
+const char *BrotliFile::containerType(void) const {
+    return "Brotli";
+}
 
 File * File::createBrotli(void) {
     return new BrotliFile;
diff --git a/lib/trace/trace_file_snappy.cpp b/lib/trace/trace_file_snappy.cpp
index 119e414..cb72b09 100644
--- a/lib/trace/trace_file_snappy.cpp
+++ b/lib/trace/trace_file_snappy.cpp
@@ -85,7 +85,11 @@
     virtual int rawGetc(void) override;
     virtual void rawClose(void) override;
     virtual bool rawSkip(size_t length) override;
-    virtual int rawPercentRead(void) override;
+
+    size_t containerSizeInBytes(void) const override;
+    size_t containerBytesRead(void) const override;
+    size_t dataBytesRead(void) const override;
+    const char* containerType() const override;
 
 private:
     inline size_t usedCacheSize(void) const
@@ -111,7 +115,7 @@
     void createCache(size_t size);
     size_t readCompressedLength();
 private:
-    std::ifstream m_stream;
+    mutable std::ifstream m_stream;
     size_t m_cacheMaxSize;
     size_t m_cacheSize;
     char *m_cache;
@@ -119,8 +123,9 @@
 
     char *m_compressedCache;
 
-    uint64_t m_currentChunkOffset;
-    std::streampos m_endPos;
+    uint64_t m_currentChunkOffset = 0;
+    std::streampos m_endPos = 0;
+    size_t m_dataBytesRead = 0;
 };
 
 SnappyFile::SnappyFile(void)
@@ -155,6 +160,8 @@
         m_endPos = m_stream.tellg();
         m_stream.seekg(0, std::ios::beg);
 
+        m_dataBytesRead = 0;
+
         // read the snappy file identifier
         unsigned char byte1, byte2;
         m_stream >> byte1;
@@ -175,6 +182,7 @@
     if (freeCacheSize() >= length) {
         memcpy(buffer, m_cachePtr, length);
         m_cachePtr += length;
+        m_dataBytesRead += length;
     } else {
         size_t sizeToRead = length;
         size_t offset = 0;
@@ -183,6 +191,7 @@
             offset = length - sizeToRead;
             memcpy((char*)buffer + offset, m_cachePtr, chunkSize);
             m_cachePtr += chunkSize;
+            m_dataBytesRead += chunkSize;
             sizeToRead -= chunkSize;
             if (sizeToRead > 0) {
                 flushReadCache();
@@ -324,11 +333,13 @@
 
     if (freeCacheSize() >= length) {
         m_cachePtr += length;
+        m_dataBytesRead += length;
     } else {
         size_t sizeToRead = length;
         while (sizeToRead) {
             size_t chunkSize = std::min(freeCacheSize(), sizeToRead);
             m_cachePtr += chunkSize;
+            m_dataBytesRead += chunkSize;
             sizeToRead -= chunkSize;
             if (sizeToRead > 0) {
                 flushReadCache(sizeToRead);
@@ -342,11 +353,21 @@
     return true;
 }
 
-int SnappyFile::rawPercentRead(void)
-{
-    return int(100 * (double(m_stream.tellg()) / double(m_endPos)));
+size_t SnappyFile::containerSizeInBytes(void) const {
+    return static_cast<size_t>(m_endPos);
 }
 
+size_t SnappyFile::containerBytesRead(void) const {
+    return  m_currentChunkOffset;
+}
+
+size_t SnappyFile::dataBytesRead(void) const {
+    return static_cast<size_t>(m_dataBytesRead);
+}
+
+const char *SnappyFile::containerType(void) const {
+    return "Snappy";
+}
 
 File* File::createSnappy(void) {
     return new SnappyFile;
diff --git a/lib/trace/trace_file_zlib.cpp b/lib/trace/trace_file_zlib.cpp
index f0dd456..424f726 100644
--- a/lib/trace/trace_file_zlib.cpp
+++ b/lib/trace/trace_file_zlib.cpp
@@ -59,11 +59,16 @@
     virtual int rawGetc(void) override;
     virtual void rawClose(void) override;
     virtual bool rawSkip(size_t length) override;
-    virtual int  rawPercentRead(void) override;
+
+    size_t containerSizeInBytes(void) const override;
+    size_t containerBytesRead(void) const override;
+    size_t dataBytesRead(void) const override;
+    const char *containerType(void) const override;
 private:
     int fd = 0;
     gzFile m_gzFile = nullptr;
     off_t m_endOffset = 0;
+    size_t m_dataBytesRead = 0;
 };
 
 ZLibFile::ZLibFile(void)
@@ -104,6 +109,7 @@
         off_t loc = lseek(fd, 0, SEEK_CUR);
         m_endOffset = lseek(fd, 0, SEEK_END);
         lseek(fd, loc, SEEK_SET);
+        m_dataBytesRead = 0;
     }
 
     return m_gzFile != NULL;
@@ -112,6 +118,9 @@
 size_t ZLibFile::rawRead(void *buffer, size_t length)
 {
     int ret = gzread(m_gzFile, buffer, unsigned(length));
+    if (ret > 0) {
+        m_dataBytesRead += static_cast<size_t>(ret);
+    }
     return ret < 0 ? 0 : ret;
 }
 
@@ -133,9 +142,20 @@
     return false;
 }
 
-int ZLibFile::rawPercentRead(void)
-{
-    return int(100 * (lseek(fd, 0, SEEK_CUR) / m_endOffset));
+size_t ZLibFile::containerSizeInBytes(void) const {
+    return static_cast<size_t>(m_endOffset);
+}
+
+size_t ZLibFile::containerBytesRead(void) const {
+    return static_cast<size_t>(lseek(fd, 0, SEEK_CUR));
+}
+
+size_t ZLibFile::dataBytesRead(void) const {
+    return m_dataBytesRead;
+}
+
+const char *ZLibFile::containerType(void) const {
+    return "ZLib";
 }
 
 
diff --git a/lib/trace/trace_parser.hpp b/lib/trace/trace_parser.hpp
index e957ca8..6791190 100644
--- a/lib/trace/trace_parser.hpp
+++ b/lib/trace/trace_parser.hpp
@@ -151,11 +151,27 @@
         return properties;
     }
 
-    int percentRead()
-    {
+
+    int percentRead() const {
         return file->percentRead();
     }
 
+    size_t containerSizeInBytes() const {
+        return file->containerSizeInBytes();
+    }
+
+    size_t containerBytesRead() const {
+        return file->containerBytesRead();
+    }
+
+    size_t dataBytesRead() const {
+        return file->dataBytesRead();
+    }
+
+    const char *containerType() const {
+        return file->containerType();
+    }
+
     Call *scan_call() {
         return parse_call(SCAN);
     }