Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bigquery): add script statistics to job resource #9428

Merged
merged 2 commits into from
Oct 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions bigquery/google/cloud/bigquery/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,15 @@ def parent_job_id(self):
"""
return _helpers._get_sub_prop(self._properties, ["statistics", "parentJobId"])

@property
def script_statistics(self):
resource = _helpers._get_sub_prop(
self._properties, ["statistics", "scriptStatistics"]
)
if resource is None:
return None
return ScriptStatistics(resource)

@property
def num_child_jobs(self):
"""The number of child jobs executed.
Expand Down Expand Up @@ -3456,3 +3465,83 @@ def from_api_repr(cls, resource, client):
resource["jobReference"] = job_ref_properties
job._properties = resource
return job


class ScriptStackFrame(object):
"""Stack frame showing the line/column/procedure name where the current
evaluation happened.

Args:
resource (Map[str, Any]):
JSON representation of object.
"""

def __init__(self, resource):
self._properties = resource

@property
def procedure_id(self):
"""Optional[str]: Name of the active procedure.

Omitted if in a top-level script.
"""
return self._properties.get("procedureId")

@property
def text(self):
"""str: Text of the current statement/expression."""
return self._properties.get("text")

@property
def start_line(self):
"""int: One-based start line."""
return _helpers._int_or_none(self._properties.get("startLine"))

@property
def start_column(self):
"""int: One-based start column."""
return _helpers._int_or_none(self._properties.get("startColumn"))

@property
def end_line(self):
"""int: One-based end line."""
return _helpers._int_or_none(self._properties.get("endLine"))

@property
def end_column(self):
"""int: One-based end column."""
return _helpers._int_or_none(self._properties.get("endColumn"))


class ScriptStatistics(object):
"""Statistics for a child job of a script.

Args:
resource (Map[str, Any]):
JSON representation of object.
"""

def __init__(self, resource):
self._properties = resource

@property
def stack_frames(self):
"""List[ScriptStackFrame]: Stack trace where the current evaluation
happened.

Shows line/column/procedure name of each frame on the stack at the
point where the current evaluation happened.

The leaf frame is first, the primary script is last.
"""
return [
ScriptStackFrame(frame) for frame in self._properties.get("stackFrames", [])
]

@property
def evaluation_kind(self):
"""str: Indicates the type of child job.

Possible values include ``STATEMENT`` and ``EXPRESSION``.
"""
return self._properties.get("evaluationKind")
117 changes: 117 additions & 0 deletions bigquery/tests/unit/test_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,37 @@ def test_parent_job_id(self):
job._properties["statistics"] = {"parentJobId": "parent-job-123"}
self.assertEqual(job.parent_job_id, "parent-job-123")

def test_script_statistics(self):
client = _make_client(project=self.PROJECT)
job = self._make_one(self.JOB_ID, client)

self.assertIsNone(job.script_statistics)
job._properties["statistics"] = {
"scriptStatistics": {
"evaluationKind": "EXPRESSION",
"stackFrames": [
{
"startLine": 5,
"startColumn": 29,
"endLine": 9,
"endColumn": 14,
"text": "QUERY TEXT",
}
],
}
}
script_stats = job.script_statistics
self.assertEqual(script_stats.evaluation_kind, "EXPRESSION")
stack_frames = script_stats.stack_frames
self.assertEqual(len(stack_frames), 1)
stack_frame = stack_frames[0]
self.assertIsNone(stack_frame.procedure_id)
self.assertEqual(stack_frame.start_line, 5)
self.assertEqual(stack_frame.start_column, 29)
self.assertEqual(stack_frame.end_line, 9)
self.assertEqual(stack_frame.end_column, 14)
self.assertEqual(stack_frame.text, "QUERY TEXT")

def test_num_child_jobs(self):
client = _make_client(project=self.PROJECT)
job = self._make_one(self.JOB_ID, client)
Expand Down Expand Up @@ -5339,6 +5370,92 @@ def test_end(self):
self.assertEqual(entry.end.strftime(_RFC3339_MICROS), self.END_RFC3339_MICROS)


class TestScriptStackFrame(unittest.TestCase, _Base):
def _make_one(self, resource):
from google.cloud.bigquery.job import ScriptStackFrame

return ScriptStackFrame(resource)

def test_procedure_id(self):
frame = self._make_one({"procedureId": "some-procedure"})
self.assertEqual(frame.procedure_id, "some-procedure")
del frame._properties["procedureId"]
self.assertIsNone(frame.procedure_id)

def test_start_line(self):
frame = self._make_one({"startLine": 5})
self.assertEqual(frame.start_line, 5)
frame._properties["startLine"] = "5"
self.assertEqual(frame.start_line, 5)

def test_start_column(self):
frame = self._make_one({"startColumn": 29})
self.assertEqual(frame.start_column, 29)
frame._properties["startColumn"] = "29"
self.assertEqual(frame.start_column, 29)

def test_end_line(self):
frame = self._make_one({"endLine": 9})
self.assertEqual(frame.end_line, 9)
frame._properties["endLine"] = "9"
self.assertEqual(frame.end_line, 9)

def test_end_column(self):
frame = self._make_one({"endColumn": 14})
self.assertEqual(frame.end_column, 14)
frame._properties["endColumn"] = "14"
self.assertEqual(frame.end_column, 14)

def test_text(self):
frame = self._make_one({"text": "QUERY TEXT"})
self.assertEqual(frame.text, "QUERY TEXT")


class TestScriptStatistics(unittest.TestCase, _Base):
def _make_one(self, resource):
from google.cloud.bigquery.job import ScriptStatistics

return ScriptStatistics(resource)

def test_evalutation_kind(self):
stats = self._make_one({"evaluationKind": "EXPRESSION"})
self.assertEqual(stats.evaluation_kind, "EXPRESSION")
self.assertEqual(stats.stack_frames, [])

def test_stack_frames(self):
stats = self._make_one(
{
"stackFrames": [
{
"procedureId": "some-procedure",
"startLine": 5,
"startColumn": 29,
"endLine": 9,
"endColumn": 14,
"text": "QUERY TEXT",
},
{},
]
}
)
stack_frames = stats.stack_frames
self.assertEqual(len(stack_frames), 2)
stack_frame = stack_frames[0]
self.assertEqual(stack_frame.procedure_id, "some-procedure")
self.assertEqual(stack_frame.start_line, 5)
self.assertEqual(stack_frame.start_column, 29)
self.assertEqual(stack_frame.end_line, 9)
self.assertEqual(stack_frame.end_column, 14)
self.assertEqual(stack_frame.text, "QUERY TEXT")
stack_frame = stack_frames[1]
self.assertIsNone(stack_frame.procedure_id)
self.assertIsNone(stack_frame.start_line)
self.assertIsNone(stack_frame.start_column)
self.assertIsNone(stack_frame.end_line)
self.assertIsNone(stack_frame.end_column)
self.assertIsNone(stack_frame.text)


class TestTimelineEntry(unittest.TestCase, _Base):
ELAPSED_MS = 101
ACTIVE_UNITS = 50
Expand Down