diff --git a/docs/reference.rst b/docs/reference.rst index 6b802e2a5..52d916f96 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -62,6 +62,7 @@ Job-Related Types job.QueryPlanEntry job.QueryPlanEntryStep job.QueryPriority + job.ReservationUsage job.SourceFormat job.WriteDisposition job.SchemaUpdateOption diff --git a/google/cloud/bigquery/job/__init__.py b/google/cloud/bigquery/job/__init__.py index 26ecf8d3c..4945841d9 100644 --- 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 index 5ba01aa67..d8f5d6528 100644 --- 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. diff --git a/tests/unit/job/test_base.py b/tests/unit/job/test_base.py index 44bbc2c77..bbeffba50 100644 --- 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)