Skip to content
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
5 changes: 2 additions & 3 deletions google/cloud/spanner_v1/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
from google.cloud.spanner_v1._helpers import _metadata_with_prefix
from google.cloud.spanner_v1.instance import Instance
from google.cloud.spanner_v1.metrics.constants import (
ENABLE_SPANNER_METRICS_ENV_VAR,
METRIC_EXPORT_INTERVAL_MS,
)
from google.cloud.spanner_v1.metrics.spanner_metrics_tracer_factory import (
Expand All @@ -75,7 +74,7 @@

_CLIENT_INFO = client_info.ClientInfo(client_library_version=__version__)
EMULATOR_ENV_VAR = "SPANNER_EMULATOR_HOST"
ENABLE_BUILTIN_METRICS_ENV_VAR = "SPANNER_ENABLE_BUILTIN_METRICS"
SPANNER_DISABLE_BUILTIN_METRICS_ENV_VAR = "SPANNER_DISABLE_BUILTIN_METRICS"
_EMULATOR_HOST_HTTP_SCHEME = (
"%s contains a http scheme. When used with a scheme it may cause gRPC's "
"DNS resolver to endlessly attempt to resolve. %s is intended to be used "
Expand All @@ -102,7 +101,7 @@ def _get_spanner_optimizer_statistics_package():


def _get_spanner_enable_builtin_metrics():
return os.getenv(ENABLE_SPANNER_METRICS_ENV_VAR) == "true"
return os.getenv(SPANNER_DISABLE_BUILTIN_METRICS_ENV_VAR) != "true"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are making Metrics default enabled with this changes, please make sure that metrics are not enabled when emulator is enabled and when customers use NoCredentials

Check https://github.com/googleapis/java-spanner/blob/main/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java#L2088

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes these cases are getting handled.
Emulator condition: https://github.com/googleapis/python-spanner/blob/main/google/cloud/spanner_v1/client.py#L257

NoCredential user condition: https://github.com/googleapis/python-spanner/blob/main/google/cloud/spanner_v1/client.py#L224-L225
This sets emulator setting for NoCredentials users making them act like emulator which means disabled metrics.



class Client(ClientWithProject):
Expand Down
1 change: 0 additions & 1 deletion google/cloud/spanner_v1/metrics/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
GOOGLE_CLOUD_REGION_KEY = "cloud.region"
GOOGLE_CLOUD_REGION_GLOBAL = "global"
SPANNER_METHOD_PREFIX = "/google.spanner.v1."
ENABLE_SPANNER_METRICS_ENV_VAR = "SPANNER_ENABLE_BUILTIN_METRICS"

# Monitored resource labels
MONITORED_RES_LABEL_KEY_PROJECT = "project_id"
Expand Down
92 changes: 92 additions & 0 deletions tests/system/test_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import mock
import pytest

from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader

from google.cloud.spanner_v1 import Client

# System tests are skipped if the environment variables are not set.
PROJECT = os.environ.get("GOOGLE_CLOUD_PROJECT")
INSTANCE_ID = os.environ.get("SPANNER_TEST_INSTANCE")
DATABASE_ID = "test_metrics_db_system"


pytestmark = pytest.mark.skipif(
not all([PROJECT, INSTANCE_ID]), reason="System test environment variables not set."
)


@pytest.fixture(scope="module")
def metrics_database():
"""Create a database for the test."""
client = Client(project=PROJECT)
instance = client.instance(INSTANCE_ID)
database = instance.database(DATABASE_ID)
if database.exists(): # Clean up from previous failed run
database.drop()
op = database.create()
op.result(timeout=300) # Wait for creation to complete
yield database
if database.exists():
database.drop()


def test_builtin_metrics_with_default_otel(metrics_database):
"""
Verifies that built-in metrics are collected by default when a
transaction is executed.
"""
reader = InMemoryMetricReader()
meter_provider = MeterProvider(metric_readers=[reader])

# Patch the client's metric setup to use our in-memory reader.
with mock.patch(
"google.cloud.spanner_v1.client.MeterProvider",
return_value=meter_provider,
):
with mock.patch.dict(os.environ, {"SPANNER_DISABLE_BUILTIN_METRICS": "false"}):
with metrics_database.snapshot() as snapshot:
list(snapshot.execute_sql("SELECT 1"))

metric_data = reader.get_metrics_data()

assert len(metric_data.resource_metrics) >= 1
assert len(metric_data.resource_metrics[0].scope_metrics) >= 1

collected_metrics = {
metric.name
for metric in metric_data.resource_metrics[0].scope_metrics[0].metrics
}
expected_metrics = {
"spanner/operation_latencies",
"spanner/attempt_latencies",
"spanner/operation_count",
"spanner/attempt_count",
"spanner/gfe_latencies",
}
assert expected_metrics.issubset(collected_metrics)

for metric in metric_data.resource_metrics[0].scope_metrics[0].metrics:
if metric.name == "spanner/operation_count":
point = next(iter(metric.data.data_points))
assert point.value == 1
assert point.attributes["method"] == "ExecuteSql"
return

pytest.fail("Metric 'spanner/operation_count' not found.")
6 changes: 3 additions & 3 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from tests._builders import build_scoped_credentials


@mock.patch.dict(os.environ, {"SPANNER_DISABLE_BUILTIN_METRICS": "true"})
class TestClient(unittest.TestCase):
PROJECT = "PROJECT"
PATH = "projects/%s" % (PROJECT,)
Expand Down Expand Up @@ -161,8 +162,7 @@ def test_constructor_custom_client_info(self):
creds = build_scoped_credentials()
self._constructor_test_helper(expected_scopes, creds, client_info=client_info)

# Disable metrics to avoid google.auth.default calls from Metric Exporter
@mock.patch.dict(os.environ, {"SPANNER_ENABLE_BUILTIN_METRICS": ""})
# Metrics are disabled by default for tests in this class
def test_constructor_implicit_credentials(self):
from google.cloud.spanner_v1 import client as MUT

Expand Down Expand Up @@ -255,8 +255,8 @@ def test_constructor_w_directed_read_options(self):
expected_scopes, creds, directed_read_options=self.DIRECTED_READ_OPTIONS
)

@mock.patch.dict(os.environ, {"SPANNER_ENABLE_BUILTIN_METRICS": "true"})
@mock.patch("google.cloud.spanner_v1.client.SpannerMetricsTracerFactory")
@mock.patch.dict(os.environ, {"SPANNER_DISABLE_BUILTIN_METRICS": "false"})
def test_constructor_w_metrics_initialization_error(
self, mock_spanner_metrics_factory
):
Expand Down
Loading