Merge pull request #313 from brandonvu12/master

Preserve Size Information During Diff
diff --git a/src/bloaty.cc b/src/bloaty.cc
index 3dbc924..200c6b4 100644
--- a/src/bloaty.cc
+++ b/src/bloaty.cc
@@ -18,6 +18,12 @@
 // It's very hard to figure out why. For the moment this seems to fix it,
 // but ideally we'd have a better solution here.
 typedef size_t z_size_t;
+#include <assert.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <math.h>
+#include <signal.h>
+#include <stdlib.h>
 #include <zlib.h>
 
 #include <atomic>
@@ -33,13 +39,6 @@
 #include <thread>
 #include <unordered_map>
 #include <vector>
-
-#include <assert.h>
-#include <fcntl.h>
-#include <limits.h>
-#include <math.h>
-#include <signal.h>
-#include <stdlib.h>
 #if !defined(_WIN32)
 #include <sys/mman.h>
 #include <sys/wait.h>
@@ -53,14 +52,13 @@
 #include "absl/debugging/internal/demangle.h"
 #include "absl/memory/memory.h"
 #include "absl/strings/numbers.h"
-#include "absl/strings/string_view.h"
 #include "absl/strings/str_join.h"
+#include "absl/strings/string_view.h"
 #include "absl/strings/substitute.h"
-#include "google/protobuf/io/zero_copy_stream_impl.h"
-#include "google/protobuf/text_format.h"
-
 #include "bloaty.h"
 #include "bloaty.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
 #include "re.h"
 #include "util.h"
 
@@ -187,8 +185,7 @@
       return std::string(symbol);
     }
   } else if (source == DataSource::kFullSymbols) {
-    char* demangled =
-        __cxa_demangle(demangle_from.data(), NULL, NULL, NULL);
+    char* demangled = __cxa_demangle(demangle_from.data(), NULL, NULL, NULL);
     if (demangled) {
       std::string ret(demangled);
       free(demangled);
@@ -202,10 +199,10 @@
   }
 }
 
-
 // NameMunger //////////////////////////////////////////////////////////////////
 
-void NameMunger::AddRegex(const std::string& regex, const std::string& replacement) {
+void NameMunger::AddRegex(const std::string& regex,
+                          const std::string& replacement) {
   auto reg = absl::make_unique<ReImpl>(regex);
   regexes_.push_back(std::make_pair(std::move(reg), replacement));
 }
@@ -223,7 +220,6 @@
   return name_str;
 }
 
-
 // Rollup //////////////////////////////////////////////////////////////////////
 
 // A Rollup is a hierarchical tally of sizes.  Its graphical representation is
@@ -259,8 +255,8 @@
   Rollup(Rollup&& other) = default;
   Rollup& operator=(Rollup&& other) = default;
 
-  void AddSizes(const std::vector<std::string>& names,
-                uint64_t size, bool is_vmsize) {
+  void AddSizes(const std::vector<std::string>& names, uint64_t size,
+                bool is_vmsize) {
     // We start at 1 to exclude the base map (see base_map_).
     AddInternal(names, 1, size, is_vmsize);
   }
@@ -274,33 +270,17 @@
   void CreateDiffModeRollupOutput(Rollup* base, const Options& options,
                                   RollupOutput* output) const {
     RollupRow* row = &output->toplevel_row_;
-    row->vmsize = vm_total_;
-    row->filesize = file_total_;
-    row->filtered_vmsize = filtered_vm_total_;
-    row->filtered_filesize = filtered_file_total_;
+    row->size.vm = vm_total_;
+    row->size.file = file_total_;
+    row->filtered_size.vm = filtered_vm_total_;
+    row->filtered_size.file = filtered_file_total_;
     row->vmpercent = 100;
     row->filepercent = 100;
     output->diff_mode_ = true;
     CreateRows(row, base, options, true);
   }
 
-  void SetFilterRegex(const ReImpl* regex) {
-    filter_regex_ = regex;
-  }
-
-  // Subtract the values in "other" from this.
-  void Subtract(const Rollup& other) {
-    vm_total_ -= other.vm_total_;
-    file_total_ -= other.file_total_;
-
-    for (const auto& other_child : other.children_) {
-      auto& child = children_[other_child.first];
-      if (child.get() == NULL) {
-        child.reset(new Rollup());
-      }
-      child->Subtract(*other_child.second);
-    }
-  }
+  void SetFilterRegex(const ReImpl* regex) { filter_regex_ = regex; }
 
   // Add the values in "other" from this.
   void Add(const Rollup& other) {
@@ -316,6 +296,17 @@
     }
   }
 
+  // Create entries for all children which exist in "other" but not in this.
+  void AddEntriesFrom(const Rollup& other) {
+    for (const auto& other_child : other.children_) {
+      auto& child = children_[other_child.first];
+      if (child.get() == NULL) {
+        child.reset(new Rollup());
+      }
+      child->AddEntriesFrom(*other_child.second);
+    }
+  }
+
   int64_t file_total() const { return file_total_; }
   int64_t filtered_file_total() const { return filtered_file_total_; }
 
@@ -413,11 +404,33 @@
   }
 
   for (const auto& value : children_) {
-    if (value.second->vm_total_ != 0 || value.second->file_total_ != 0) {
+    int64_t vm_total = value.second->vm_total_;
+    int64_t file_total = value.second->file_total_;
+    Rollup* base_child = nullptr;
+
+    if (base) {
+      // Reassign sizes to base during a diff to compare to target sizes.
+      auto it = base->children_.find(value.first);
+      if (it != base->children_.end()) {
+        base_child = it->second.get();
+        vm_total -= base_child->vm_total_;
+        file_total -= base_child->file_total_;
+      }
+    }
+
+    if (vm_total != 0 || file_total != 0) {
       row->sorted_children.emplace_back(value.first);
       RollupRow& child_row = row->sorted_children.back();
-      child_row.vmsize = value.second->vm_total_;
-      child_row.filesize = value.second->file_total_;
+      child_row.size.vm = vm_total;
+      child_row.size.file = file_total;
+
+      // Preserve the old size for this label in the RollupRow output.
+      // If there is a diff base, the old sizes come from the size of the label
+      // in that base.  Otherwise, the old size stays 0.
+      if (base_child) {
+        child_row.old_size.vm = base_child->vm_total_;
+        child_row.old_size.file = base_child->file_total_;
+      }
     }
   }
 
@@ -452,14 +465,14 @@
   for (auto& child : child_rows) {
     switch (options.sort_by()) {
       case Options::SORTBY_VMSIZE:
-        child.sortkey = std::abs(child.vmsize);
+        child.sortkey = std::abs(child.size.vm);
         break;
       case Options::SORTBY_FILESIZE:
-        child.sortkey = std::abs(child.filesize);
+        child.sortkey = std::abs(child.size.file);
         break;
       case Options::SORTBY_BOTH:
         child.sortkey =
-            std::max(std::abs(child.vmsize), std::abs(child.filesize));
+            std::max(std::abs(child.size.vm), std::abs(child.size.file));
         break;
       default:
         BLOATY_UNREACHABLE();
@@ -478,8 +491,8 @@
   // out to "others_row".
   size_t i = child_rows.size() - 1;
   while (i >= options.max_rows_per_level()) {
-    CheckedAdd(&others_row.vmsize, child_rows[i].vmsize);
-    CheckedAdd(&others_row.filesize, child_rows[i].filesize);
+    CheckedAdd(&others_row.size.vm, child_rows[i].size.vm);
+    CheckedAdd(&others_row.size.file, child_rows[i].size.file);
     if (base) {
       auto it = base->children_.find(child_rows[i].name);
       if (it != base->children_.end()) {
@@ -492,26 +505,26 @@
     i--;
   }
 
-  if (std::abs(others_row.vmsize) > 0 || std::abs(others_row.filesize) > 0) {
+  if (std::abs(others_row.size.vm) > 0 || std::abs(others_row.size.file) > 0) {
     child_rows.push_back(others_row);
-    CheckedAdd(&others_rollup.vm_total_, others_row.vmsize);
-    CheckedAdd(&others_rollup.file_total_, others_row.filesize);
+    CheckedAdd(&others_rollup.vm_total_, others_row.size.vm);
+    CheckedAdd(&others_rollup.file_total_, others_row.size.file);
   }
 
   // Now sort by actual value (positive or negative).
   for (auto& child : child_rows) {
     switch (options.sort_by()) {
       case Options::SORTBY_VMSIZE:
-        child.sortkey = child.vmsize;
+        child.sortkey = child.size.vm;
         break;
       case Options::SORTBY_FILESIZE:
-        child.sortkey = child.filesize;
+        child.sortkey = child.size.file;
         break;
       case Options::SORTBY_BOTH:
-        if (std::abs(child.vmsize) > std::abs(child.filesize)) {
-          child.sortkey = child.vmsize;
+        if (std::abs(child.size.vm) > std::abs(child.size.file)) {
+          child.sortkey = child.size.vm;
         } else {
-          child.sortkey = child.filesize;
+          child.sortkey = child.size.file;
         }
         break;
       default:
@@ -524,8 +537,8 @@
   // For a non-diff, the percentage is compared to the total size of the parent.
   if (!base) {
     for (auto& child_row : child_rows) {
-      child_row.vmpercent = Percent(child_row.vmsize, row->vmsize);
-      child_row.filepercent = Percent(child_row.filesize, row->filesize);
+      child_row.vmpercent = Percent(child_row.size.vm, row->size.vm);
+      child_row.filepercent = Percent(child_row.size.file, row->size.file);
     }
   }
 
@@ -561,7 +574,6 @@
   }
 }
 
-
 // RollupOutput ////////////////////////////////////////////////////////////////
 
 // RollupOutput represents rollup data after we have applied output massaging
@@ -600,14 +612,14 @@
   return ret;
 }
 
-std::string DoubleStringPrintf(const char *fmt, double d) {
+std::string DoubleStringPrintf(const char* fmt, double d) {
   char buf[1024];
   snprintf(buf, sizeof(buf), fmt, d);
   return std::string(buf);
 }
 
 std::string SiPrint(int64_t size, bool force_sign) {
-  const char *prefixes[] = {"", "Ki", "Mi", "Gi", "Ti"};
+  const char* prefixes[] = {"", "Ki", "Mi", "Gi", "Ti"};
   size_t num_prefixes = 5;
   size_t n = 0;
   double size_d = size;
@@ -677,10 +689,10 @@
         PrettyPrint(options, out);
         break;
       case bloaty::OutputFormat::kCSV:
-        PrintToCSV(out, /*tabs=*/false);
+        PrintToCSV(out, /*tabs=*/false, options.showAllSizesCSV);
         break;
       case bloaty::OutputFormat::kTSV:
-        PrintToCSV(out, /*tabs=*/true);
+        PrintToCSV(out, /*tabs=*/true, options.showAllSizesCSV);
         break;
       default:
         BLOATY_UNREACHABLE();
@@ -698,8 +710,8 @@
   if (&row != &toplevel_row_) {
     // Avoid printing this row if it is only zero.
     // This can happen when using --domain if the row is zero for this domain.
-    if ((!ShowFile(options) && row.vmsize == 0) ||
-        (!ShowVM(options) && row.filesize == 0)) {
+    if ((!ShowFile(options) && row.size.vm == 0) ||
+        (!ShowVM(options) && row.size.file == 0)) {
       return;
     }
   }
@@ -708,12 +720,12 @@
 
   if (ShowFile(options)) {
     *out << PercentString(row.filepercent, diff_mode_) << " "
-         << SiPrint(row.filesize, diff_mode_) << " ";
+         << SiPrint(row.size.file, diff_mode_) << " ";
   }
 
   if (ShowVM(options)) {
     *out << PercentString(row.vmpercent, diff_mode_) << " "
-         << SiPrint(row.vmsize, diff_mode_) << " ";
+         << SiPrint(row.size.vm, diff_mode_) << " ";
   }
 
   *out << "   " << row.name << "\n";
@@ -737,7 +749,7 @@
   // Rows are printed before their sub-rows.
   PrettyPrintRow(row, indent, options, out);
 
-  if (!row.vmsize && !row.filesize) {
+  if (!row.size.vm && !row.size.file) {
     return;
   }
 
@@ -783,11 +795,12 @@
 
   uint64_t file_filtered = 0;
   uint64_t vm_filtered = 0;
+  uint64_t filtered = 0;
   if (ShowFile(options)) {
-    file_filtered = toplevel_row_.filtered_filesize;
+    filtered += toplevel_row_.filtered_size.file;
   }
   if (ShowVM(options)) {
-    vm_filtered = toplevel_row_.filtered_vmsize;
+    filtered += toplevel_row_.filtered_size.vm;
   }
 
   if (vm_filtered == 0 && file_filtered == 0) {
@@ -805,19 +818,28 @@
     *out << SiPrint(vm_filtered, /*force_sign=*/false);
   }
 
-   *out << " of entries\n";
+  *out << " of entries\n";
 }
 
 void RollupOutput::PrintRowToCSV(const RollupRow& row,
                                  std::vector<std::string> parent_labels,
-                                 std::ostream* out, bool tabs) const {
+                                 std::ostream* out, bool tabs, bool csvDiff) const {
   while (parent_labels.size() < source_names_.size()) {
     // If this label had no data at this level, append an empty string.
     parent_labels.push_back("");
   }
 
-  parent_labels.push_back(std::to_string(row.vmsize));
-  parent_labels.push_back(std::to_string(row.filesize));
+  parent_labels.push_back(std::to_string(row.size.vm));
+  parent_labels.push_back(std::to_string(row.size.file));
+
+  // If in diff where both old size are 0, get new size by adding diff size to
+  // old size.
+  if (csvDiff) {
+    parent_labels.push_back(std::to_string(row.old_size.vm));
+  	parent_labels.push_back(std::to_string(row.old_size.file));
+    parent_labels.push_back(std::to_string(row.old_size.vm + (row.size.vm)));
+    parent_labels.push_back(
+        std::to_string(row.old_size.file + (row.size.file)));}
 
   std::string sep = tabs ? "\t" : ",";
   *out << absl::StrJoin(parent_labels, sep) << "\n";
@@ -825,7 +847,7 @@
 
 void RollupOutput::PrintTreeToCSV(const RollupRow& row,
                                   std::vector<std::string> parent_labels,
-                                  std::ostream* out, bool tabs) const {
+                                  std::ostream* out, bool tabs, bool csvDiff) const {
   if (tabs) {
     parent_labels.push_back(row.name);
   } else {
@@ -834,21 +856,28 @@
 
   if (row.sorted_children.size() > 0) {
     for (const auto& child_row : row.sorted_children) {
-      PrintTreeToCSV(child_row, parent_labels, out, tabs);
+      PrintTreeToCSV(child_row, parent_labels, out, tabs, csvDiff);
     }
   } else {
-    PrintRowToCSV(row, parent_labels, out, tabs);
+    PrintRowToCSV(row, parent_labels, out, tabs, csvDiff);
   }
 }
 
-void RollupOutput::PrintToCSV(std::ostream* out, bool tabs) const {
+void RollupOutput::PrintToCSV(std::ostream* out, bool tabs,
+                              bool csvDiff) const {
   std::vector<std::string> names(source_names_);
   names.push_back("vmsize");
   names.push_back("filesize");
+  if (csvDiff) {
+    names.push_back("original_vmsize");
+    names.push_back("original_filesize");
+    names.push_back("current_vmsize");
+    names.push_back("current_filesize");
+  }
   std::string sep = tabs ? "\t" : ",";
   *out << absl::StrJoin(names, sep) << "\n";
   for (const auto& child_row : toplevel_row_.sorted_children) {
-    PrintTreeToCSV(child_row, std::vector<std::string>(), out, tabs);
+    PrintTreeToCSV(child_row, std::vector<std::string>(), out, tabs, csvDiff);
   }
 }
 
@@ -856,7 +885,6 @@
 
 constexpr uint64_t RangeSink::kUnknownSize;
 
-
 // MmapInputFile ///////////////////////////////////////////////////////////////
 
 #if !defined(_WIN32)
@@ -875,7 +903,6 @@
                         std::unique_ptr<InputFile>& file);
 };
 
-
 class FileDescriptor {
  public:
   FileDescriptor(int fd) : fd_(fd) {}
@@ -897,7 +924,7 @@
   std::string str(filename);
   FileDescriptor fd(open(str.c_str(), O_RDONLY));
   struct stat buf;
-  const char *map;
+  const char* map;
 
   if (fd.fd() < 0) {
     std::cerr << absl::Substitute("couldn't open file '$0': $1\n", filename,
@@ -945,7 +972,7 @@
   return ret;
 }
 
-#else // !_WIN32
+#else  // !_WIN32
 
 // MmapInputFile ///////////////////////////////////////////////////////////////
 
@@ -989,9 +1016,9 @@
 bool Win32MMapInputFile::DoTryOpen(absl::string_view filename,
                                    std::unique_ptr<InputFile>& file) {
   std::string str(filename);
-  Win32Handle fd(::CreateFileA(str.c_str(), FILE_GENERIC_READ,
-                               FILE_SHARE_READ, NULL, OPEN_EXISTING,
-                               FILE_ATTRIBUTE_NORMAL, NULL));
+  Win32Handle fd(::CreateFileA(str.c_str(), FILE_GENERIC_READ, FILE_SHARE_READ,
+                               NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
+                               NULL));
   LARGE_INTEGER li = {};
   const char* map;
 
@@ -1046,11 +1073,14 @@
 
 // RangeSink ///////////////////////////////////////////////////////////////////
 
-RangeSink::RangeSink(const InputFile *file, const Options &options,
-                     DataSource data_source, const DualMap *translator,
-                     google::protobuf::Arena *arena)
-    : file_(file), options_(options), data_source_(data_source),
-      translator_(translator), arena_(arena) {}
+RangeSink::RangeSink(const InputFile* file, const Options& options,
+                     DataSource data_source, const DualMap* translator,
+                     google::protobuf::Arena* arena)
+    : file_(file),
+      options_(options),
+      data_source_(data_source),
+      translator_(translator),
+      arena_(arena) {}
 
 RangeSink::~RangeSink() {}
 
@@ -1346,21 +1376,23 @@
   }
   uint64_t mb = 1 << 20;
   // Limit for uncompressed size is 30x the compressed size + 128MB.
-  if (uncompressed_size > static_cast<uint64_t>(data.size()) * 30 + (128 * mb)) {
+  if (uncompressed_size >
+      static_cast<uint64_t>(data.size()) * 30 + (128 * mb)) {
     fprintf(stderr,
             "warning: ignoring compressed debug data, implausible uncompressed "
             "size (compressed: %zu, uncompressed: %" PRIu64 ")\n",
             data.size(), uncompressed_size);
     return absl::string_view();
   }
-  unsigned char *dbuf =
+  unsigned char* dbuf =
       arena_->google::protobuf::Arena::CreateArray<unsigned char>(
           arena_, uncompressed_size);
   uLongf zliblen = uncompressed_size;
-  if (uncompress(dbuf, &zliblen, (unsigned char*)(data.data()), data.size()) != Z_OK) {
+  if (uncompress(dbuf, &zliblen, (unsigned char*)(data.data()), data.size()) !=
+      Z_OK) {
     THROW("Error decompressing debug info");
   }
-  string_view sv(reinterpret_cast<char *>(dbuf), zliblen);
+  string_view sv(reinterpret_cast<char*>(dbuf), zliblen);
   return sv;
 }
 
@@ -1403,7 +1435,6 @@
   const int max_;
 };
 
-
 // Bloaty //////////////////////////////////////////////////////////////////////
 
 // Represents a program execution and associated state.
@@ -1501,8 +1532,9 @@
   std::unique_ptr<google::protobuf::Arena> arena_;
 };
 
-Bloaty::Bloaty(const InputFileFactory &factory, const Options &options)
-    : file_factory_(factory), options_(options),
+Bloaty::Bloaty(const InputFileFactory& factory, const Options& options)
+    : file_factory_(factory),
+      options_(options),
       arena_(std::make_unique<google::protobuf::Arena>()) {
   AddBuiltInSources(data_sources, options);
 }
@@ -1563,8 +1595,10 @@
   auto iter = all_known_sources_.find(source.base_data_source());
 
   if (iter == all_known_sources_.end()) {
-    THROWF("custom data source '$0': no such base source '$1'.\nTry --list-sources to see valid sources.", source.name(),
-           source.base_data_source());
+    THROWF(
+        "custom data source '$0': no such base source '$1'.\nTry "
+        "--list-sources to see valid sources.",
+        source.name(), source.base_data_source());
   } else if (!iter->second->munger->IsEmpty()) {
     THROWF("custom data source '$0' tries to depend on custom data source '$1'",
            source.name(), source.base_data_source());
@@ -1582,7 +1616,8 @@
   source_names_.emplace_back(name);
   auto it = all_known_sources_.find(name);
   if (it == all_known_sources_.end()) {
-    THROWF("no such data source: $0.\nTry --list-sources to see valid sources.", name);
+    THROWF("no such data source: $0.\nTry --list-sources to see valid sources.",
+           name);
   }
 
   sources_.emplace_back(it->second.get());
@@ -1649,7 +1684,8 @@
     return ret;
   }
 
-  void PrintMapRow(string_view str, uint64_t start, uint64_t end, int hex_digits) {
+  void PrintMapRow(string_view str, uint64_t start, uint64_t end,
+                   int hex_digits) {
     printf("%.*" PRIx64 "-%.*" PRIx64 "\t %s\t\t%.*s\n", hex_digits, start,
            hex_digits, end, LeftPad(std::to_string(end - start), 10).c_str(),
            (int)str.size(), str.data());
@@ -1677,7 +1713,7 @@
   std::vector<std::unique_ptr<DualMap>> maps_;
 };
 
-void Bloaty::ScanAndRollupFile(const std::string &filename, Rollup* rollup,
+void Bloaty::ScanAndRollupFile(const std::string& filename, Rollup* rollup,
                                std::vector<std::string>* out_build_ids) const {
   auto file = GetObjectFile(filename);
 
@@ -1694,9 +1730,9 @@
   sink_ptrs.push_back(sinks.back().get());
 
   for (auto source : sources_) {
-    sinks.push_back(absl::make_unique<RangeSink>(&file->file_data(), options_,
-                                                 source->effective_source,
-                                                 maps.base_map(), arena_.get()));
+    sinks.push_back(absl::make_unique<RangeSink>(
+        &file->file_data(), options_, source->effective_source, maps.base_map(),
+        arena_.get()));
     sinks.back()->AddOutput(maps.AppendMap(), source->munger.get());
     // We handle the kInputFiles data source internally, without handing it off
     // to the file format implementation.  This seems slightly simpler, since
@@ -1719,8 +1755,8 @@
     }
   }
 
-  int64_t filesize_before = rollup->file_total() +
-      rollup->filtered_file_total();
+  int64_t filesize_before =
+      rollup->file_total() + rollup->filtered_file_total();
   file->ProcessFile(sink_ptrs);
 
   // kInputFile source: Copy the base map to the filename sink(s).
@@ -1740,8 +1776,8 @@
   maps.ComputeRollup(rollup);
 
   // The ObjectFile implementation must guarantee this.
-  int64_t filesize = rollup->file_total() +
-      rollup->filtered_file_total() - filesize_before;
+  int64_t filesize =
+      rollup->file_total() + rollup->filtered_file_total() - filesize_before;
   (void)filesize;
   assert(filesize == file->file_data().data().size());
 
@@ -1758,10 +1794,9 @@
   }
 }
 
-void Bloaty::ScanAndRollupFiles(
-    const std::vector<std::string>& filenames,
-    std::vector<std::string>* build_ids,
-    Rollup * rollup) const {
+void Bloaty::ScanAndRollupFiles(const std::vector<std::string>& filenames,
+                                std::vector<std::string>* build_ids,
+                                Rollup* rollup) const {
   int num_cpus = std::thread::hardware_concurrency();
   int num_threads = std::min(num_cpus, static_cast<int>(filenames.size()));
 
@@ -1782,16 +1817,18 @@
   for (int i = 0; i < num_threads; i++) {
     thread_data[i].rollup.SetFilterRegex(regex.get());
 
-    threads[i] = std::thread([this, &index, &filenames](PerThreadData* data) {
-      try {
-        int j;
-        while (index.TryGetNext(&j)) {
-          ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids);
-        }
-      } catch (const bloaty::Error& e) {
-        index.Abort(e.what());
-      }
-    }, &thread_data[i]);
+    threads[i] = std::thread(
+        [this, &index, &filenames](PerThreadData* data) {
+          try {
+            int j;
+            while (index.TryGetNext(&j)) {
+              ScanAndRollupFile(filenames[j], &data->rollup, &data->build_ids);
+            }
+          } catch (const bloaty::Error& e) {
+            index.Abort(e.what());
+          }
+        },
+        &thread_data[i]);
   }
 
   for (int i = 0; i < num_threads; i++) {
@@ -1803,8 +1840,7 @@
       rollup->Add(data->rollup);
     }
 
-    build_ids->insert(build_ids->end(),
-                      data->build_ids.begin(),
+    build_ids->insert(build_ids->end(), data->build_ids.begin(),
                       data->build_ids.end());
   }
 
@@ -1838,7 +1874,7 @@
       base_filenames.push_back(file_info.filename_);
     }
     ScanAndRollupFiles(base_filenames, &build_ids, &base);
-    rollup.Subtract(base);
+    rollup.AddEntriesFrom(base);
     rollup.CreateDiffModeRollupOutput(&base, options, output);
   } else {
     rollup.CreateRollupOutput(options, output);
@@ -1854,8 +1890,7 @@
     std::string unused_debug;
     for (const auto& pair : debug_files_) {
       unused_debug += absl::Substitute(
-          "$0   $1\n",
-          absl::BytesToHexString(pair.first).c_str(),
+          "$0   $1\n", absl::BytesToHexString(pair.first).c_str(),
           pair.second.c_str());
     }
 
@@ -1869,9 +1904,8 @@
           "$0   $1\n", absl::BytesToHexString(file_info.build_id_).c_str(),
           file_info.filename_.c_str());
     }
-    THROWF(
-        "Debug file(s) did not match any input file:\n$0\nInput Files:\n$1",
-        unused_debug.c_str(), input_files.c_str());
+    THROWF("Debug file(s) did not match any input file:\n$0\nInput Files:\n$1",
+           unused_debug.c_str(), input_files.c_str());
   }
 }
 
@@ -1961,9 +1995,7 @@
     return ret;
   }
 
-  void ConsumeAndSaveArg() {
-    (*out_argv_)[(*out_argc_)++] = argv_[index_++];
-  }
+  void ConsumeAndSaveArg() { (*out_argv_)[(*out_argc_)++] = argv_[index_++]; }
 
   // Singular flag like --csv or -v.
   bool TryParseFlag(string_view flag) {
@@ -2154,6 +2186,7 @@
       }
     } else {
       if (saw_separator) {
+        output_options->showAllSizesCSV = true;
         options->add_base_filename(std::string(args.ConsumeArg()));
       } else {
         options->add_filename(std::string(args.ConsumeArg()));
diff --git a/src/bloaty.h b/src/bloaty.h
index 82c0c55..5f8d470 100644
--- a/src/bloaty.h
+++ b/src/bloaty.h
@@ -337,18 +337,26 @@
 
 class Rollup;
 
+struct DomainSizes {
+  int64_t vm;
+  int64_t file;
+};
+
 struct RollupRow {
   RollupRow(const std::string& name_) : name(name_) {}
 
   std::string name;
-  int64_t vmsize = 0;
-  int64_t filesize = 0;
-  int64_t filtered_vmsize = 0;
-  int64_t filtered_filesize = 0;
+  DomainSizes size = {0, 0};
+  DomainSizes filtered_size = {0, 0};
+
   int64_t other_count = 0;
   int64_t sortkey;
   double vmpercent;
   double filepercent;
+
+  // The size of the base in a diff mode. Otherwise stay 0.
+  DomainSizes old_size = {0, 0};
+  
   std::vector<RollupRow> sorted_children;
 
   static bool Compare(const RollupRow& a, const RollupRow& b) {
@@ -377,6 +385,7 @@
   OutputFormat output_format = OutputFormat::kPrettyPrint;
   size_t max_label_len = 80;
   ShowDomain show = ShowDomain::kShowBoth;
+  bool showAllSizesCSV = false;
 };
 
 struct RollupOutput {
@@ -413,17 +422,17 @@
 
   static bool IsSame(const std::string& a, const std::string& b);
   void PrettyPrint(const OutputOptions& options, std::ostream* out) const;
-  void PrintToCSV(std::ostream* out, bool tabs) const;
+  void PrintToCSV(std::ostream* out, bool tabs, bool csvDiff) const;
   void PrettyPrintRow(const RollupRow& row, size_t indent,
                       const OutputOptions& options, std::ostream* out) const;
   void PrettyPrintTree(const RollupRow& row, size_t indent,
                        const OutputOptions& options, std::ostream* out) const;
   void PrintRowToCSV(const RollupRow& row,
                      std::vector<std::string> parent_labels,
-                     std::ostream* out, bool tabs) const;
+                     std::ostream* out, bool tabs, bool csvDiff) const;
   void PrintTreeToCSV(const RollupRow& row,
                       std::vector<std::string> parent_labels,
-                      std::ostream* out, bool tabs) const;
+                      std::ostream* out, bool tabs, bool csvDiff) const;
 };
 
 bool ParseOptions(bool skip_unknown, int* argc, char** argv[], Options* options,
diff --git a/tests/bloaty_misc_test.cc b/tests/bloaty_misc_test.cc
index 3a4bf41..a1d5084 100644
--- a/tests/bloaty_misc_test.cc
+++ b/tests/bloaty_misc_test.cc
@@ -27,7 +27,7 @@
       {"bloaty", "-d", "compileunits", "03-small-binary-that-crashed-inlines.bin"});
   RunBloaty(
       {"bloaty", "-d", "inlines", "03-small-binary-that-crashed-inlines.bin"});
-  EXPECT_EQ(top_row_->vmsize, 2340);
+  EXPECT_EQ(top_row_->size.vm, 2340);
 }
 
 TEST_F(BloatyTest, GoBinary) {
@@ -46,7 +46,7 @@
 
 TEST_F(BloatyTest, MultiThreaded) {
   RunBloaty({"bloaty", "02-section-count-overflow.o"});
-  size_t file_size = top_row_->filesize;
+  size_t file_size = top_row_->size.file;
 
   // Bloaty doesn't know or care that you are passing the same file multiple
   // times.
@@ -56,5 +56,5 @@
     args.push_back("02-section-count-overflow.o");
   }
   RunBloaty(args);  // Heavily multithreaded test.
-  EXPECT_EQ(top_row_->filesize, file_size * 100);
+  EXPECT_EQ(top_row_->size.file, file_size * 100);
 }
diff --git a/tests/bloaty_test.cc b/tests/bloaty_test.cc
index 8187aca..68eb9df 100644
--- a/tests/bloaty_test.cc
+++ b/tests/bloaty_test.cc
@@ -21,20 +21,20 @@
 
   // Empty .c file should result in a .o file with no vmsize.
   RunBloaty({"bloaty", file});
-  EXPECT_EQ(top_row_->vmsize, 0);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_EQ(top_row_->size.vm, 0);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // Same with segments (we fake segments on .o files).
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_EQ(top_row_->vmsize, 0);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_EQ(top_row_->size.vm, 0);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // Same with symbols.
   RunBloaty({"bloaty", "-d", "symbols", file});
-  EXPECT_EQ(top_row_->vmsize, 0);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_EQ(top_row_->size.vm, 0);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // We can't run any of these targets against object files.
@@ -50,16 +50,16 @@
 
   // Test "-n 0" which should return an unlimited number of rows.
   RunBloaty({"bloaty", "-n", "0", file});
-  EXPECT_GT(top_row_->vmsize, 64);
-  EXPECT_LT(top_row_->vmsize, 300);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 64);
+  EXPECT_LT(top_row_->size.vm, 300);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // Same with segments (we fake segments on .o files).
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 64);
-  EXPECT_LT(top_row_->vmsize, 300);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 64);
+  EXPECT_LT(top_row_->size.vm, 300);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 1);
 
   // For inputfiles we should get everything attributed to the input file.
@@ -111,15 +111,15 @@
   ASSERT_TRUE(GetFileSize(file, &size));
 
   RunBloaty({"bloaty", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  //EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  //EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 3);
 
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  //EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  //EXPECT_EQ(top_row_->size.file, size);
 
   RunBloaty({"bloaty", "-d", "symbols", "-n", "40", "-s", "vm", file});
   AssertChildren(*top_row_, {
@@ -174,15 +174,15 @@
   ASSERT_TRUE(GetFileSize(file, &size));
 
   RunBloaty({"bloaty", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 3);
 
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
 
   RunBloaty({"bloaty", "-d", "symbols", "-n", "50", file});
   AssertChildren(*top_row_, {
@@ -199,15 +199,15 @@
   ASSERT_TRUE(GetFileSize(file, &size));
 
   RunBloaty({"bloaty", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
   EXPECT_GT(top_row_->sorted_children.size(), 3);
 
   RunBloaty({"bloaty", "-d", "segments", file});
-  EXPECT_GT(top_row_->vmsize, 8000);
-  EXPECT_LT(top_row_->vmsize, 12000);
-  EXPECT_EQ(top_row_->filesize, size);
+  EXPECT_GT(top_row_->size.vm, 8000);
+  EXPECT_LT(top_row_->size.vm, 12000);
+  EXPECT_EQ(top_row_->size.file, size);
 
   RunBloaty({"bloaty", "-d", "symbols", "-n", "50", "-s", "vm", file});
   AssertChildren(*top_row_, {
diff --git a/tests/test.h b/tests/test.h
index dc864f0..99517d7 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -81,16 +81,16 @@
       uint64_t vmtotal = 0;
       uint64_t filetotal = 0;
       for (const auto& child : row.sorted_children) {
-        vmtotal += child.vmsize;
-        filetotal += child.filesize;
+        vmtotal += child.size.vm;
+        filetotal += child.size.file;
         CheckConsistencyForRow(child, false, diff_mode, count);
         ASSERT_TRUE(names.insert(child.name).second);
-        ASSERT_FALSE(child.vmsize == 0 && child.filesize == 0);
+        ASSERT_FALSE(child.size.vm == 0 && child.size.file == 0);
       }
 
       if (!diff_mode) {
-        ASSERT_EQ(vmtotal, row.vmsize);
-        ASSERT_EQ(filetotal, row.filesize);
+        ASSERT_EQ(vmtotal, row.size.vm);
+        ASSERT_EQ(filetotal, row.size.file);
       }
     } else {
       // Count leaf rows.
@@ -147,7 +147,7 @@
         ASSERT_TRUE(GetFileSize(filename, &size));
         total_input_size += size;
       }
-      ASSERT_EQ(top_row_->filesize, total_input_size);
+      ASSERT_EQ(top_row_->size.file, total_input_size);
     }
 
     int rows = 0;
@@ -248,21 +248,21 @@
       if (expected_vm == kUnknown) {
         // Always pass.
       } else if (expected_vm > 0) {
-        EXPECT_GE(child.vmsize, expected_vm);
+        EXPECT_GE(child.size.vm, expected_vm);
         // Allow some overhead.
-        EXPECT_LE(child.vmsize, (expected_vm * 1.1) + 100);
+        EXPECT_LE(child.size.vm, (expected_vm * 1.1) + 100);
       } else {
         ASSERT_TRUE(false);
       }
 
       if (expected_file == kSameAsVM) {
-        expected_file = child.vmsize;
+        expected_file = child.size.vm;
       }
 
       if (expected_file != kUnknown) {
-        EXPECT_GE(child.filesize, expected_file);
+        EXPECT_GE(child.size.file, expected_file);
         // Allow some overhead.
-        EXPECT_LE(child.filesize, (expected_file * 1.2) + 180);
+        EXPECT_LE(child.size.file, (expected_file * 1.2) + 180);
       }
 
       if (++i == children.size()) {
diff --git a/tests/testdata/make_all_msvc_test_files.bat b/tests/testdata/make_all_msvc_test_files.bat
old mode 100644
new mode 100755