pw_bloat: Fix data sources GN template

Specfiying only 1 data sources broke the difference algorithm
as it relied on having 2 or more data sources to calculate the
difference. Fix the algorithm by only subtracting the lowest
level within the DataSourceMap struct. Adding diff label
title to ASCII table outputs as well.

Change-Id: Ib5b1fb18a6145a7f954c885712915e2fd9142ed3
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/108750
Reviewed-by: Alexei Frolov <frolv@google.com>
Commit-Queue: Brandon Vu <brandonvu@google.com>
diff --git a/pw_bloat/bloat.gni b/pw_bloat/bloat.gni
index dbf6aae..0171bcf 100644
--- a/pw_bloat/bloat.gni
+++ b/pw_bloat/bloat.gni
@@ -248,6 +248,7 @@
         _binary_source_filter = ""
       }
 
+      _binary_data_sources = []
       if (defined(binary.data_sources)) {
         _binary_data_sources = string_split(binary.data_sources, ",")
       } else if (defined(_global_data_sources)) {
diff --git a/pw_bloat/py/label_test.py b/pw_bloat/py/label_test.py
index 5a760e7..b2ec1c1 100644
--- a/pw_bloat/py/label_test.py
+++ b/pw_bloat/py/label_test.py
@@ -189,7 +189,7 @@
             [label.parents[0], label.parents[1], 'foobar()'], label.size + 20)
 
         ds_map2.insert_label_hierachy(["LOAD #5", 'random_load', 'func()'],
-                                      label.size + 20)
+                                      250)
 
         list_labels_ds_b = [
             Label(name='FLASH',
@@ -203,7 +203,7 @@
                   exists_both=True,
                   parents=()),
             Label(name='LOAD #5',
-                  size=240,
+                  size=250,
                   capacity=None,
                   exists_both=False,
                   parents=())
diff --git a/pw_bloat/py/pw_bloat/bloat.py b/pw_bloat/py/pw_bloat/bloat.py
index b37fff5..fcbfaa7 100755
--- a/pw_bloat/py/pw_bloat/bloat.py
+++ b/pw_bloat/py/pw_bloat/bloat.py
@@ -229,8 +229,11 @@
             single_output_target.decode().splitlines())
         diff_dsm = target_dsm.diff(base_dsm)
 
-        diff_report += BloatTableOutput(diff_dsm, MAX_COL_WIDTH,
-                                        LineCharset).create_table()
+        diff_report += BloatTableOutput(
+            diff_dsm,
+            MAX_COL_WIDTH,
+            LineCharset,
+            diff_label=curr_diff_binary['label']).create_table()
 
         print(diff_report)
         curr_rst_report = RstOutput(diff_dsm, curr_diff_binary['label'])
diff --git a/pw_bloat/py/pw_bloat/label.py b/pw_bloat/py/pw_bloat/label.py
index 26653f0..d5b6db8 100644
--- a/pw_bloat/py/pw_bloat/label.py
+++ b/pw_bloat/py/pw_bloat/label.py
@@ -148,51 +148,43 @@
         curr_parent = self._BASE_TOTAL_LABEL
 
         # Iterate through base labels at each datasource index.
-        for data_source_i in range(1, len(base.get_ds_names())):
-            last_data_source_index = (data_source_i == (
-                len(base.get_ds_names()) - 1))
-            for b_label in base.labels(data_source_i):
-                if data_source_i > 0:
-                    curr_parent = b_label.parents[-1]
-                lb_hierachy_names = [*b_label.parents, b_label.name]
+        last_data_source = len(base.get_ds_names()) - 1
+        parent_data_source_index = last_data_source + 1
+        for b_label in base.labels(last_data_source):
+            if last_data_source > 0:
+                curr_parent = b_label.parents[-1]
+            lb_hierachy_names = [*b_label.parents, b_label.name]
 
-                # Check if label exists in target binary DataSourceMap.
-                # Subtract base from target size and insert diff size
-                # into DiffDataSourceMap.
-                if self.label_exists(data_source_i + 1, curr_parent,
-                                     b_label.name):
-                    diff_size = ((self._data_sources[data_source_i + 1]
-                                  [curr_parent][b_label.name].size) -
-                                 b_label.size)
+            # Check if label exists in target binary DataSourceMap.
+            # Subtract base from target size and insert diff size
+            # into DiffDataSourceMap.
+            if self.label_exists(parent_data_source_index, curr_parent,
+                                 b_label.name):
+                diff_size = ((self._data_sources[parent_data_source_index]
+                              [curr_parent][b_label.name].size) - b_label.size)
 
-                    if diff_size and last_data_source_index:
-                        diff_dsm.insert_label_hierachy(lb_hierachy_names,
-                                                       diff_size, True)
-                    else:
-                        diff_dsm.insert_label_hierachy(lb_hierachy_names, 0,
-                                                       True)
-
-                # label is not present in target - insert with negative size
+                if diff_size:
+                    diff_dsm.insert_label_hierachy(lb_hierachy_names,
+                                                   diff_size, True)
                 else:
-                    if last_data_source_index:
-                        diff_dsm.insert_label_hierachy(lb_hierachy_names,
-                                                       -1 * b_label.size,
-                                                       False)
-                    else:
-                        diff_dsm.insert_label_hierachy(lb_hierachy_names, 0,
-                                                       False)
-            # Iterate through all of target labels
-            # to find labels new to target from base.
-            for t_label in self.labels(data_source_i):
-                if data_source_i > 0:
-                    curr_parent = t_label.parents[-1]
+                    diff_dsm.insert_label_hierachy(lb_hierachy_names, 0, True)
 
-                # New addition to target
-                if not base.label_exists(data_source_i + 1, curr_parent,
-                                         t_label.name):
-                    diff_dsm.insert_label_hierachy(
-                        [*t_label.parents, f"{t_label.name}"], t_label.size,
-                        False)
+            # label is not present in target - insert with negative size
+            else:
+                diff_dsm.insert_label_hierachy(lb_hierachy_names,
+                                               -1 * b_label.size, False)
+
+        # Iterate through all of target labels
+        # to find labels new to target from base.
+        for t_label in self.labels(last_data_source):
+            if last_data_source > 0:
+                curr_parent = t_label.parents[-1]
+
+            # New addition to target
+            if not base.label_exists(parent_data_source_index, curr_parent,
+                                     t_label.name):
+                diff_dsm.insert_label_hierachy(
+                    [*t_label.parents, f"{t_label.name}"], t_label.size, False)
 
         return diff_dsm
 
@@ -245,9 +237,13 @@
     """DataSourceMap that holds diff information."""
     def has_diff_sublabels(self, top_ds_label: str) -> bool:
         """Checks if first datasource is identical."""
-
-        return any(label.parents[0] == top_ds_label and label.size != 0
-                   for label in self.labels())
+        for label in self.labels():
+            if label.size != 0:
+                if (label.parents and
+                    (label.parents[0] == top_ds_label)) or (label.name
+                                                            == top_ds_label):
+                    return True
+        return False
 
 
 def from_bloaty_tsv(raw_tsv: Iterable[str]) -> DataSourceMap:
diff --git a/pw_bloat/py/pw_bloat/label_output.py b/pw_bloat/py/pw_bloat/label_output.py
index 438c739..5ae4b17 100644
--- a/pw_bloat/py/pw_bloat/label_output.py
+++ b/pw_bloat/py/pw_bloat/label_output.py
@@ -77,6 +77,7 @@
     """ASCII Table generator from DataSourceMap."""
 
     _RST_PADDING_WIDTH = 6
+    _MIN_TABLE_PADDING = 5
 
     class _LabelContent(NamedTuple):
         name: str
@@ -88,12 +89,14 @@
                  col_max_width: int,
                  charset: Union[Type[AsciiCharset],
                                 Type[LineCharset]] = AsciiCharset,
-                 rst_output: bool = False):
+                 rst_output: bool = False,
+                 diff_label: str = None):
         self._data_source_map = ds_map
         self._cs = charset
         self._total_size = 0
         col_names = [*self._data_source_map.get_ds_names(), 'sizes']
         self._diff_mode = False
+        self._diff_label = diff_label
         if isinstance(self._data_source_map, DiffDataSourceMap):
             col_names = ['diff', *col_names]
             self._diff_mode = True
@@ -112,7 +115,8 @@
         """Find column width for all data sources and sizes."""
         max_len_size = 0
         col_list = [
-            len(ds_name) for ds_name in self._data_source_map.get_ds_names()
+            len(ds_name) + self._MIN_TABLE_PADDING
+            for ds_name in self._data_source_map.get_ds_names()
         ]
         for curr_label in self._data_source_map.labels():
             self._total_size += curr_label.size
@@ -152,10 +156,31 @@
 
         return tuple(diff_list)
 
+    def label_title_row(self) -> List[str]:
+        label_rows = []
+        label_cells = ''
+        divider_cells = ''
+        for width in self._col_widths:
+            label_cells += ' ' * width + ' '
+            divider_cells += (self._cs.H.value * width) + self._cs.H.value
+        if self._diff_label is not None:
+            label_cells = self._diff_label.center(len(label_cells[:-1]), ' ')
+        label_rows.extend([
+            f"{self._cs.TL.value}{divider_cells[:-1]}{self._cs.TR.value}",
+            f"{self._cs.V.value}{label_cells}{self._cs.V.value}",
+            f"{self._cs.ML.value}{divider_cells[:-1]}{self._cs.MR.value}"
+        ])
+        return label_rows
+
     def create_table(self) -> str:
         """Parse DataSourceMap to create ASCII table."""
         curr_lb_hierachy = None
         last_diff_name = ''
+        if self._diff_mode:
+            self._ascii_table_rows.extend([*self.label_title_row()])
+        else:
+            self._ascii_table_rows.extend(
+                [self.create_border(True, self._cs.H.value)])
         self._ascii_table_rows.extend([*self.create_title_row()])
         for curr_label in self._data_source_map.labels():
             if curr_label.size == 0:
@@ -168,6 +193,7 @@
             diff_list = self._diff_label_names(curr_lb_hierachy,
                                                new_lb_hierachy)
             curr_lb_hierachy = new_lb_hierachy
+
             if curr_label.parents and curr_label.parents[0] == last_diff_name:
                 continue
             if self._diff_mode and diff_list[0].name and (not cast(
@@ -175,7 +201,8 @@
                     self._data_source_map).has_diff_sublabels(
                         diff_list[0].name)):
                 if (len(self._ascii_table_rows) >
-                        3) and (self._ascii_table_rows[-1][0] != '+'):
+                        5) and (self._ascii_table_rows[-1][0] != '+'):
+
                     self._ascii_table_rows.append(
                         self.row_divider(len(self._col_names),
                                          self._cs.H.value))
@@ -255,7 +282,7 @@
                     if cell_index == index + diff_index:
                         if cell_index == diff_index and len(
                                 self._ascii_table_rows
-                        ) > 3 and not self._rst_output:
+                        ) > 5 and not self._rst_output:
                             diff_rows.append(
                                 self.row_divider(len(self._col_names),
                                                  self._cs.H.value))
@@ -291,7 +318,7 @@
                     align: Optional[_Align] = _Align.RIGHT) -> str:
         v_border = self._cs.V.value
         if self._rst_output and content:
-            content = f" ``{content}`` "
+            content = content.replace('_', '\\_')
         pad_diff = self._col_widths[col_index] - len(content)
         padding = (pad_diff // 2) * ' '
         odd_pad = ' ' if pad_diff % 2 == 1 else ''
@@ -361,13 +388,13 @@
         title_rows = []
         title_cells = ''
         last_cell = False
-        for index, name in enumerate(self._col_names):
+        for index, curr_name in enumerate(self._col_names):
             if index == len(self._col_names) - 1:
                 last_cell = True
-            title_cells += self.create_cell(name, last_cell, index,
+            title_cells += self.create_cell(curr_name, last_cell, index,
                                             _Align.CENTER)
         title_rows.extend([
-            self.create_border(True, self._cs.H.value), title_cells,
+            title_cells,
             self.row_divider(len(self._col_names), self._cs.HH.value)
         ])
         return title_rows
@@ -427,9 +454,12 @@
     def _label_status_unchanged(self, parent_lb_name: str) -> bool:
         """Determines if parent label has no status change in diff mode."""
         for curr_lb in self._data_source_map.labels():
-            if curr_lb.size != 0 and (parent_lb_name == curr_lb.parents[0]):
-                if get_label_status(curr_lb) != '':
-                    return False
+            if curr_lb.size != 0:
+                if (curr_lb.parents and
+                    (parent_lb_name == curr_lb.parents[0])) or (
+                        curr_lb.name == parent_lb_name):
+                    if get_label_status(curr_lb) != '':
+                        return False
         return True
 
     def add_report_row(self) -> str:
@@ -461,15 +491,18 @@
                 else:
                     skip_status = 0, ' '
                 for curr_lb in self._data_source_map.labels():
-                    if curr_lb.size != 0 and (parent_lb.name
-                                              == curr_lb.parents[0]):
+                    if (curr_lb.size != 0) and (
+                        (curr_lb.parents and
+                         (parent_lb.name == curr_lb.parents[0])) or
+                        (curr_lb.name == parent_lb.name)):
                         sign_size = diff_sign_sizes(curr_lb.size,
                                                     self._diff_mode)
                         curr_status = get_label_status(curr_lb)
+                        curr_name = curr_lb.name.replace('_', '\\_')
                         curr_row.extend([
                             f"               * - {curr_status}",
                             f"               {skip_status[1]} - {sign_size}",
-                            f"                 - {curr_lb.name}\n"
+                            f"                 - {curr_name}\n"
                         ][skip_status[0]:])
                 curr_row.append(
                     f"     - {diff_sign_sizes(parent_lb.size, self._diff_mode)}"