pkg_size: create a simple CLI tool to dump a system size report

pkg_size dumps the sizes of the packages contained within a built root.
It will also push gauge data (package size) per package and per
invocation (total root size) to the build api metrics append-only queue
via append_metrics_log. This data is to be used for build metrics trend
reporting.

BUG=chromium:1000449
TEST=cros_sdk -- '$HOME/trunk/chromite/run_tests'

Cq-Depend: chromium:1834120
Change-Id: I735057f9bcfe4de367a8df888e76a09836e371d1
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1801037
Tested-by: Will Bradley <wbbradley@chromium.org>
Commit-Queue: Will Bradley <wbbradley@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Alex Klein <saklein@chromium.org>
diff --git a/api/metrics.py b/api/metrics.py
index cdf438e..1dba763 100644
--- a/api/metrics.py
+++ b/api/metrics.py
@@ -38,14 +38,14 @@
   # Reduce over the input events to append output_events.
   for input_event in metrics.read_metrics_events():
     if input_event.op == metrics.OP_START_TIMER:
-      timers[input_event.key] = (input_event.name,
+      timers[input_event.arg] = (input_event.name,
                                  input_event.timestamp_epoch_millis)
     elif input_event.op == metrics.OP_STOP_TIMER:
       # TODO(wbbradley): Drop the None fallback https://crbug.com/1001909.
-      timer = timers.pop(input_event.key, None)
+      timer = timers.pop(input_event.arg, None)
       if timer is None:
         logging.error('%s: stop timer recorded, but missing start timer!?',
-                      input_event.key)
+                      input_event.arg)
       if timer:
         assert input_event.name == timer[0]
         output_event = output_events.add()
@@ -57,6 +57,11 @@
       output_event = output_events.add()
       output_event.name = make_name(input_event.name)
       output_event.timestamp_milliseconds = input_event.timestamp_epoch_millis
+    elif input_event.op == metrics.OP_GAUGE:
+      output_event = output_events.add()
+      output_event.name = make_name(input_event.name)
+      output_event.timestamp_milliseconds = input_event.timestamp_epoch_millis
+      output_event.gauge = input_event.arg
     else:
       raise ValueError('unexpected op "%s" found in metric event: %s' % (
           input_event.op, input_event))
diff --git a/api/metrics_unittest.py b/api/metrics_unittest.py
index f900f89..a4b47d1 100644
--- a/api/metrics_unittest.py
+++ b/api/metrics_unittest.py
@@ -12,8 +12,8 @@
 from chromite.api import metrics
 from chromite.api.gen.chromite.api import build_api_test_pb2
 from chromite.lib import cros_test_lib
-from chromite.utils.metrics import (MetricEvent, OP_NAMED_EVENT, OP_START_TIMER,
-                                    OP_STOP_TIMER)
+from chromite.utils.metrics import (MetricEvent, OP_GAUGE, OP_NAMED_EVENT,
+                                    OP_START_TIMER, OP_STOP_TIMER)
 
 
 class MetricsTest(cros_test_lib.TestCase):
@@ -23,8 +23,8 @@
     """Test timer math and deserialization into proto objects."""
     response = build_api_test_pb2.TestResultMessage()
     mock_events = [
-        MetricEvent(600, 'a.b', OP_START_TIMER, key='100'),
-        MetricEvent(1000, 'a.b', OP_STOP_TIMER, key='100'),
+        MetricEvent(600, 'a.b', OP_START_TIMER, arg='100'),
+        MetricEvent(1000, 'a.b', OP_STOP_TIMER, arg='100'),
     ]
     with mock.patch('chromite.api.metrics.metrics.read_metrics_events',
                     return_value=mock_events):
@@ -41,7 +41,7 @@
     """
     response = build_api_test_pb2.TestResultMessage()
     mock_events = [
-        MetricEvent(1000, 'a.named_event', OP_NAMED_EVENT, key=None),
+        MetricEvent(1000, 'a.named_event', OP_NAMED_EVENT, arg=None),
     ]
     with mock.patch('chromite.api.metrics.metrics.read_metrics_events',
                     return_value=mock_events):
@@ -50,3 +50,17 @@
       self.assertEqual(response.events[0].name, 'prefix.a.named_event')
       self.assertEqual(response.events[0].timestamp_milliseconds, 1000)
       self.assertFalse(response.events[0].duration_milliseconds)
+
+  def testDeserializeGauge(self):
+    """Test deserialization of a gauge."""
+    response = build_api_test_pb2.TestResultMessage()
+    mock_events = [
+        MetricEvent(1000, 'a.gauge', OP_GAUGE, arg=17),
+    ]
+    with mock.patch('chromite.api.metrics.metrics.read_metrics_events',
+                    return_value=mock_events):
+      metrics.deserialize_metrics_log(response.events)
+      self.assertEqual(len(response.events), 1)
+      self.assertEqual(response.events[0].name, 'a.gauge')
+      self.assertEqual(response.events[0].timestamp_milliseconds, 1000)
+      self.assertEqual(response.events[0].gauge, 17)