Skip to content

Commit 8d88c00

Browse files
authored
feat: add util functions to get URLs for Tensorboard web app. (#635)
1 parent 52a7b7c commit 8d88c00

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright 2021 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
from typing import Sequence, Dict
18+
from google.cloud.aiplatform_v1beta1.services.tensorboard_service.client import (
19+
TensorboardServiceClient,
20+
)
21+
22+
_SERVING_DOMAIN = "tensorboard.googleusercontent.com"
23+
24+
25+
def _parse_experiment_name(experiment_name: str) -> Dict[str, str]:
26+
"""Parses an experiment_name into its component segments.
27+
28+
Args:
29+
experiment_name: Resource name of the TensorboardExperiment. E.g.
30+
"projects/123/locations/asia-east1/tensorboards/456/experiments/exp1"
31+
32+
Returns:
33+
Components of the experiment name.
34+
35+
Raises:
36+
ValueError if the experiment_name is invalid.
37+
"""
38+
matched = TensorboardServiceClient.parse_tensorboard_experiment_path(
39+
experiment_name
40+
)
41+
if not matched:
42+
raise ValueError(f"Invalid experiment name: {experiment_name}.")
43+
return matched
44+
45+
46+
def get_experiment_url(experiment_name: str) -> str:
47+
"""Get URL for comparing experiments.
48+
49+
Args:
50+
experiment_name: Resource name of the TensorboardExperiment. E.g.
51+
"projects/123/locations/asia-east1/tensorboards/456/experiments/exp1"
52+
53+
Returns:
54+
URL for the tensorboard web app.
55+
"""
56+
location = _parse_experiment_name(experiment_name)["location"]
57+
name_for_url = experiment_name.replace("/", "+")
58+
return f"https://{location}.{_SERVING_DOMAIN}/experiment/{name_for_url}"
59+
60+
61+
def get_experiments_compare_url(experiment_names: Sequence[str]) -> str:
62+
"""Get URL for comparing experiments.
63+
64+
Args:
65+
experiment_names: Resource names of the TensorboardExperiments that needs to
66+
be compared.
67+
68+
Returns:
69+
URL for the tensorboard web app.
70+
"""
71+
if len(experiment_names) < 2:
72+
raise ValueError("At least two experiment_names are required.")
73+
74+
locations = {
75+
_parse_experiment_name(experiment_name)["location"]
76+
for experiment_name in experiment_names
77+
}
78+
if len(locations) != 1:
79+
raise ValueError(
80+
f"Got experiments from different locations: {', '.join(locations)}."
81+
)
82+
location = locations.pop()
83+
84+
experiment_url_segments = []
85+
for idx, experiment_name in enumerate(experiment_names):
86+
name_segments = _parse_experiment_name(experiment_name)
87+
experiment_url_segments.append(
88+
"{cnt}-{experiment}:{project}+{location}+{tensorboard}+{experiment}".format(
89+
cnt=idx + 1, **name_segments
90+
)
91+
)
92+
encoded_names = ",".join(experiment_url_segments)
93+
return f"https://{location}.{_SERVING_DOMAIN}/compare/{encoded_names}"

tests/unit/aiplatform/test_utils.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from google.cloud.aiplatform import compat
2929
from google.cloud.aiplatform import utils
3030
from google.cloud.aiplatform.utils import pipeline_utils
31+
from google.cloud.aiplatform.utils import tensorboard_utils
3132

3233
from google.cloud.aiplatform_v1beta1.services.model_service import (
3334
client as model_service_client_v1beta1,
@@ -454,3 +455,54 @@ def test_pipeline_utils_runtime_config_builder_parameter_not_found(self):
454455
my_builder.build()
455456

456457
assert e.match(regexp=r"The pipeline parameter no_such_param is not found")
458+
459+
460+
class TestTensorboardUtils:
461+
def test_tensorboard_get_experiment_url(self):
462+
actual = tensorboard_utils.get_experiment_url(
463+
"projects/123/locations/asia-east1/tensorboards/456/experiments/exp1"
464+
)
465+
assert actual == (
466+
"https://asia-east1.tensorboard."
467+
+ "googleusercontent.com/experiment/projects+123+locations+asia-east1+tensorboards+456+experiments+exp1"
468+
)
469+
470+
def test_get_experiments_url_bad_experiment_name(self):
471+
with pytest.raises(ValueError, match="Invalid experiment name: foo-bar."):
472+
tensorboard_utils.get_experiment_url("foo-bar")
473+
474+
def test_tensorboard_get_experiments_compare_url(self):
475+
actual = tensorboard_utils.get_experiments_compare_url(
476+
(
477+
"projects/123/locations/asia-east1/tensorboards/456/experiments/exp1",
478+
"projects/123/locations/asia-east1/tensorboards/456/experiments/exp2",
479+
)
480+
)
481+
assert actual == (
482+
"https://asia-east1.tensorboard."
483+
+ "googleusercontent.com/compare/1-exp1:123+asia-east1+456+exp1,"
484+
+ "2-exp2:123+asia-east1+456+exp2"
485+
)
486+
487+
def test_tensorboard_get_experiments_compare_url_fail_just_one_exp(self):
488+
with pytest.raises(
489+
ValueError, match="At least two experiment_names are required."
490+
):
491+
tensorboard_utils.get_experiments_compare_url(
492+
("projects/123/locations/asia-east1/tensorboards/456/experiments/exp1",)
493+
)
494+
495+
def test_tensorboard_get_experiments_compare_url_fail_diff_region(self):
496+
with pytest.raises(
497+
ValueError, match="Got experiments from different locations: asia-east.",
498+
):
499+
tensorboard_utils.get_experiments_compare_url(
500+
(
501+
"projects/123/locations/asia-east1/tensorboards/456/experiments/exp1",
502+
"projects/123/locations/asia-east2/tensorboards/456/experiments/exp2",
503+
)
504+
)
505+
506+
def test_get_experiments_compare_url_bad_experiment_name(self):
507+
with pytest.raises(ValueError, match="Invalid experiment name: foo-bar."):
508+
tensorboard_utils.get_experiments_compare_url(("foo-bar", "foo-bar1"))

0 commit comments

Comments
 (0)