Repository: googleapis/python-bigquery

Issue:
BigQuery Feature: reservation usage in job statistics
# Summary

This FR tracks work to expose reservation usage information that may be reported as part of BigQuery job statistics, namely [`JobStatistics.reservationUsage`](https://cloud.google.com/bigquery/docs/reference/rest/v2/Job#JobStatistics)

# Details

More detailed information about this work has previously been shared out of band with library maintainers.  



Discussion:

Resolving patch:
diff --git a/google/cloud/bigquery/job/__init__.py b/google/cloud/bigquery/job/__init__.py
--- a/google/cloud/bigquery/job/__init__.py
+++ b/google/cloud/bigquery/job/__init__.py
@@ -19,6 +19,7 @@
 from google.cloud.bigquery.job.base import _DONE_STATE
 from google.cloud.bigquery.job.base import _JobConfig
 from google.cloud.bigquery.job.base import _JobReference
+from google.cloud.bigquery.job.base import ReservationUsage
 from google.cloud.bigquery.job.base import ScriptStatistics
 from google.cloud.bigquery.job.base import ScriptStackFrame
 from google.cloud.bigquery.job.base import UnknownJob
@@ -51,6 +52,7 @@
     "_DONE_STATE",
     "_JobConfig",
     "_JobReference",
+    "ReservationUsage",
     "ScriptStatistics",
     "ScriptStackFrame",
     "UnknownJob",
diff --git a/google/cloud/bigquery/job/base.py b/google/cloud/bigquery/job/base.py
--- a/google/cloud/bigquery/job/base.py
+++ b/google/cloud/bigquery/job/base.py
@@ -14,6 +14,7 @@
 
 """Base classes and helpers for job classes."""
 
+from collections import namedtuple
 import copy
 import http
 import threading
@@ -73,6 +74,16 @@ def _error_result_to_exception(error_result):
     )
 
 
+ReservationUsage = namedtuple("ReservationUsage", "name slot_ms")
+ReservationUsage.__doc__ = "Job resource usage for a reservation."
+ReservationUsage.name.__doc__ = (
+    'Reservation name or "unreserved" for on-demand resources usage.'
+)
+ReservationUsage.slot_ms.__doc__ = (
+    "Total slot milliseconds used by the reservation for a particular job."
+)
+
+
 class _JobReference(object):
     """A reference to a job.
 
@@ -305,6 +316,22 @@ def _job_statistics(self):
         statistics = self._properties.get("statistics", {})
         return statistics.get(self._JOB_TYPE, {})
 
+    @property
+    def reservation_usage(self):
+        """Job resource usage breakdown by reservation.
+
+        Returns:
+            List[google.cloud.bigquery.job.ReservationUsage]:
+                Reservation usage stats. Can be empty if not set from the server.
+        """
+        usage_stats_raw = _helpers._get_sub_prop(
+            self._properties, ["statistics", "reservationUsage"], default=()
+        )
+        return [
+            ReservationUsage(name=usage["name"], slot_ms=int(usage["slotMs"]))
+            for usage in usage_stats_raw
+        ]
+
     @property
     def error_result(self):
         """Error information about the job as a whole.


Test patch:
diff --git a/tests/unit/job/test_base.py b/tests/unit/job/test_base.py
--- a/tests/unit/job/test_base.py
+++ b/tests/unit/job/test_base.py
@@ -319,6 +319,30 @@ def test_ended(self):
         stats["endTime"] = millis
         self.assertEqual(job.ended, now)
 
+    def test_reservation_usage_no_stats(self):
+        client = _make_client(project=self.PROJECT)
+        job = self._make_one(self.JOB_ID, client)
+        job._properties["statistics"] = {}
+        self.assertEqual(job.reservation_usage, [])
+
+    def test_reservation_usage_stats_exist(self):
+        from google.cloud.bigquery.job import ReservationUsage
+
+        client = _make_client(project=self.PROJECT)
+        job = self._make_one(self.JOB_ID, client)
+        job._properties["statistics"] = {
+            "reservationUsage": [
+                {"name": "slot_foo", "slotMs": "42"},
+                {"name": "slot_bar", "slotMs": "123"},
+            ],
+        }
+
+        expected = [
+            ReservationUsage(name="slot_foo", slot_ms=42),
+            ReservationUsage(name="slot_bar", slot_ms=123),
+        ]
+        self.assertEqual(job.reservation_usage, expected)
+
     def test__job_statistics(self):
         statistics = {"foo": "bar"}
         client = _make_client(project=self.PROJECT)