grpcio-observability 1.71.0__cp313-cp313-musllinux_1_1_aarch64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- grpc_observability/__init__.py +17 -0
- grpc_observability/_cyobservability.cpython-313-aarch64-linux-musl.so +0 -0
- grpc_observability/_measures.py +91 -0
- grpc_observability/_observability.py +119 -0
- grpc_observability/_observability_config.py +129 -0
- grpc_observability/_open_census_exporter.py +301 -0
- grpc_observability/_open_telemetry_measures.py +98 -0
- grpc_observability/_open_telemetry_observability.py +525 -0
- grpc_observability/_open_telemetry_plugin.py +171 -0
- grpc_observability/_views.py +287 -0
- grpcio_observability-1.71.0.dist-info/LICENSE +610 -0
- grpcio_observability-1.71.0.dist-info/METADATA +109 -0
- grpcio_observability-1.71.0.dist-info/RECORD +15 -0
- grpcio_observability-1.71.0.dist-info/WHEEL +5 -0
- grpcio_observability-1.71.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2023 gRPC authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from grpc_observability._open_telemetry_plugin import OpenTelemetryPlugin
|
16
|
+
|
17
|
+
__all__ = ("OpenTelemetryPlugin",)
|
Binary file
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Copyright 2023 gRPC authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from opencensus.stats import measure
|
16
|
+
|
17
|
+
# These measure definitions should be kept in sync across opencensus implementations.
|
18
|
+
# https://github.com/census-instrumentation/opencensus-java/blob/master/contrib/grpc_metrics/src/main/java/io/opencensus/contrib/grpc/metrics/RpcMeasureConstants.java.
|
19
|
+
|
20
|
+
# Unit constants
|
21
|
+
UNIT_BYTES = "By"
|
22
|
+
UNIT_MILLISECONDS = "ms"
|
23
|
+
UNIT_COUNT = "1"
|
24
|
+
|
25
|
+
# Client
|
26
|
+
CLIENT_STARTED_RPCS_MEASURE = measure.MeasureInt(
|
27
|
+
"grpc.io/client/started_rpcs",
|
28
|
+
"The total number of client RPCs ever opened, including those that have not been completed.",
|
29
|
+
UNIT_COUNT,
|
30
|
+
)
|
31
|
+
|
32
|
+
CLIENT_COMPLETED_RPCS_MEASURE = measure.MeasureInt(
|
33
|
+
"grpc.io/client/completed_rpcs",
|
34
|
+
"The total number of completed client RPCs",
|
35
|
+
UNIT_COUNT,
|
36
|
+
)
|
37
|
+
|
38
|
+
CLIENT_ROUNDTRIP_LATENCY_MEASURE = measure.MeasureFloat(
|
39
|
+
"grpc.io/client/roundtrip_latency",
|
40
|
+
"Time between first byte of request sent to last byte of response received, or terminal error",
|
41
|
+
UNIT_MILLISECONDS,
|
42
|
+
)
|
43
|
+
|
44
|
+
CLIENT_API_LATENCY_MEASURE = measure.MeasureInt(
|
45
|
+
"grpc.io/client/api_latency",
|
46
|
+
"End-to-end time taken to complete an RPC",
|
47
|
+
UNIT_MILLISECONDS,
|
48
|
+
)
|
49
|
+
|
50
|
+
CLIENT_SEND_BYTES_PER_RPC_MEASURE = measure.MeasureFloat(
|
51
|
+
"grpc.io/client/sent_bytes_per_rpc",
|
52
|
+
"Total bytes sent across all request messages per RPC",
|
53
|
+
UNIT_BYTES,
|
54
|
+
)
|
55
|
+
|
56
|
+
CLIENT_RECEIVED_BYTES_PER_RPC_MEASURE = measure.MeasureFloat(
|
57
|
+
"grpc.io/client/received_bytes_per_rpc",
|
58
|
+
"Total bytes received across all response messages per RPC",
|
59
|
+
UNIT_BYTES,
|
60
|
+
)
|
61
|
+
|
62
|
+
# Server
|
63
|
+
SERVER_STARTED_RPCS_MEASURE = measure.MeasureInt(
|
64
|
+
"grpc.io/server/started_rpcs",
|
65
|
+
"Total bytes sent across all request messages per RPC",
|
66
|
+
UNIT_COUNT,
|
67
|
+
)
|
68
|
+
|
69
|
+
SERVER_COMPLETED_RPCS_MEASURE = measure.MeasureInt(
|
70
|
+
"grpc.io/server/completed_rpcs",
|
71
|
+
"The total number of completed server RPCs",
|
72
|
+
UNIT_COUNT,
|
73
|
+
)
|
74
|
+
|
75
|
+
SERVER_SENT_BYTES_PER_RPC_MEASURE = measure.MeasureFloat(
|
76
|
+
"grpc.io/server/sent_bytes_per_rpc",
|
77
|
+
"Total bytes sent across all messages per RPC",
|
78
|
+
UNIT_BYTES,
|
79
|
+
)
|
80
|
+
|
81
|
+
SERVER_RECEIVED_BYTES_PER_RPC_MEASURE = measure.MeasureFloat(
|
82
|
+
"grpc.io/server/received_bytes_per_rpc",
|
83
|
+
"Total bytes received across all messages per RPC",
|
84
|
+
UNIT_BYTES,
|
85
|
+
)
|
86
|
+
|
87
|
+
SERVER_SERVER_LATENCY_MEASURE = measure.MeasureFloat(
|
88
|
+
"grpc.io/server/server_latency",
|
89
|
+
"Time between first byte of request received to last byte of response sent, or terminal error",
|
90
|
+
UNIT_MILLISECONDS,
|
91
|
+
)
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# Copyright 2023 gRPC authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
from __future__ import annotations
|
15
|
+
|
16
|
+
import abc
|
17
|
+
from dataclasses import dataclass
|
18
|
+
from dataclasses import field
|
19
|
+
import enum
|
20
|
+
from typing import AnyStr, Dict, List, Mapping, Set, Tuple
|
21
|
+
|
22
|
+
|
23
|
+
class Exporter(metaclass=abc.ABCMeta):
|
24
|
+
"""Abstract base class for census data exporters."""
|
25
|
+
|
26
|
+
@abc.abstractmethod
|
27
|
+
def export_stats_data(self, stats_data: List[TracingData]) -> None:
|
28
|
+
"""Exports a list of TracingData objects to the exporter's destination.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
stats_data: A list of TracingData objects to export.
|
32
|
+
"""
|
33
|
+
raise NotImplementedError()
|
34
|
+
|
35
|
+
@abc.abstractmethod
|
36
|
+
def export_tracing_data(self, tracing_data: List[StatsData]) -> None:
|
37
|
+
"""Exports a list of StatsData objects to the exporter's destination.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
tracing_data: A list of StatsData objects to export.
|
41
|
+
"""
|
42
|
+
raise NotImplementedError()
|
43
|
+
|
44
|
+
|
45
|
+
@dataclass(frozen=True)
|
46
|
+
class StatsData:
|
47
|
+
"""A data class representing stats data.
|
48
|
+
|
49
|
+
Attributes:
|
50
|
+
name: An element of grpc_observability._cyobservability.MetricsName, e.g.
|
51
|
+
MetricsName.CLIENT_STARTED_RPCS.
|
52
|
+
measure_double: A bool indicate whether the metric is a floating-point
|
53
|
+
value.
|
54
|
+
value_int: The actual metric value if measure_double is False.
|
55
|
+
value_float: The actual metric value if measure_double is True.
|
56
|
+
include_exchange_labels: Whether this data should include exchanged labels.
|
57
|
+
labels: A dictionary that maps label tags associated with this metric to
|
58
|
+
corresponding label value.
|
59
|
+
identifiers: A set of strings identifying which stats plugins this StatsData
|
60
|
+
belongs to.
|
61
|
+
registered_method: Whether the method in this data is a registered method
|
62
|
+
in stubs.
|
63
|
+
"""
|
64
|
+
|
65
|
+
name: "grpc_observability._cyobservability.MetricsName"
|
66
|
+
measure_double: bool
|
67
|
+
value_int: int = 0
|
68
|
+
value_float: float = 0.0
|
69
|
+
include_exchange_labels: bool = False
|
70
|
+
labels: Dict[str, AnyStr] = field(default_factory=dict)
|
71
|
+
identifiers: Set[str] = field(default_factory=set)
|
72
|
+
registered_method: bool = False
|
73
|
+
|
74
|
+
|
75
|
+
@dataclass(frozen=True)
|
76
|
+
class TracingData:
|
77
|
+
"""A data class representing tracing data.
|
78
|
+
|
79
|
+
Attributes:
|
80
|
+
name: The name for tracing data, also the name for the Span.
|
81
|
+
start_time: The start time for the span in RFC3339 UTC "Zulu" format, e.g.
|
82
|
+
2014-10-02T15:01:23Z
|
83
|
+
end_time: The end time for the span in RFC3339 UTC "Zulu" format, e.g.
|
84
|
+
2014-10-02T15:01:23Z
|
85
|
+
trace_id: The identifier for the trace associated with this span as a
|
86
|
+
32-character hexadecimal encoded string,
|
87
|
+
e.g. 26ed0036f2eff2b7317bccce3e28d01f
|
88
|
+
span_id: The identifier for the span as a 16-character hexadecimal encoded
|
89
|
+
string. e.g. 113ec879e62583bc
|
90
|
+
parent_span_id: An option identifier for the span's parent id.
|
91
|
+
status: An element of grpc.StatusCode in string format representing the
|
92
|
+
final status for the trace data.
|
93
|
+
should_sample: A bool indicates whether the span is sampled.
|
94
|
+
child_span_count: The number of child span associated with this span.
|
95
|
+
span_labels: A dictionary that maps labels tags associated with this
|
96
|
+
span to corresponding label value.
|
97
|
+
span_annotations: A dictionary that maps annotation timeStamp with
|
98
|
+
description. The timeStamp have a format which can be converted
|
99
|
+
to Python datetime.datetime, e.g. 2023-05-29 17:07:09.895
|
100
|
+
"""
|
101
|
+
|
102
|
+
name: str
|
103
|
+
start_time: str
|
104
|
+
end_time: str
|
105
|
+
trace_id: str
|
106
|
+
span_id: str
|
107
|
+
parent_span_id: str
|
108
|
+
status: str
|
109
|
+
should_sample: bool
|
110
|
+
child_span_count: int
|
111
|
+
span_labels: Mapping[str, AnyStr] = field(default_factory=dict)
|
112
|
+
span_annotations: List[Tuple[str, str]] = field(default_factory=list)
|
113
|
+
|
114
|
+
|
115
|
+
@enum.unique
|
116
|
+
class OptionalLabelType(enum.Enum):
|
117
|
+
"""What kinds of optional labels to add to metrics."""
|
118
|
+
|
119
|
+
XDS_SERVICE_LABELS = "kXdsServiceLabels"
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# Copyright 2023 gRPC authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
"""Helper to read observability config."""
|
15
|
+
|
16
|
+
from dataclasses import dataclass
|
17
|
+
from dataclasses import field
|
18
|
+
import json
|
19
|
+
import os
|
20
|
+
from typing import Mapping, Optional
|
21
|
+
|
22
|
+
GRPC_GCP_OBSERVABILITY_CONFIG_FILE_ENV = "GRPC_GCP_OBSERVABILITY_CONFIG_FILE"
|
23
|
+
GRPC_GCP_OBSERVABILITY_CONFIG_ENV = "GRPC_GCP_OBSERVABILITY_CONFIG"
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class GcpObservabilityConfig:
|
28
|
+
project_id: str = ""
|
29
|
+
stats_enabled: bool = False
|
30
|
+
tracing_enabled: bool = False
|
31
|
+
labels: Optional[Mapping[str, str]] = field(default_factory=dict)
|
32
|
+
sampling_rate: Optional[float] = 0.0
|
33
|
+
|
34
|
+
def load_from_string_content(self, config_contents: str) -> None:
|
35
|
+
"""Loads the configuration from a string.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
config_contents: The configuration string.
|
39
|
+
|
40
|
+
Raises:
|
41
|
+
ValueError: If the configuration is invalid.
|
42
|
+
"""
|
43
|
+
try:
|
44
|
+
config_json = json.loads(config_contents)
|
45
|
+
except json.decoder.JSONDecodeError:
|
46
|
+
raise ValueError("Failed to load Json configuration.")
|
47
|
+
|
48
|
+
if config_json and not isinstance(config_json, dict):
|
49
|
+
raise ValueError("Found invalid configuration.")
|
50
|
+
|
51
|
+
self.project_id = config_json.get("project_id", "")
|
52
|
+
self.labels = config_json.get("labels", {})
|
53
|
+
self.stats_enabled = "cloud_monitoring" in config_json.keys()
|
54
|
+
self.tracing_enabled = "cloud_trace" in config_json.keys()
|
55
|
+
tracing_config = config_json.get("cloud_trace", {})
|
56
|
+
self.sampling_rate = tracing_config.get("sampling_rate", 0.0)
|
57
|
+
|
58
|
+
|
59
|
+
def read_config() -> GcpObservabilityConfig:
|
60
|
+
"""Reads the GCP observability config from the environment variables.
|
61
|
+
|
62
|
+
Returns:
|
63
|
+
The GCP observability config.
|
64
|
+
|
65
|
+
Raises:
|
66
|
+
ValueError: If the configuration is invalid.
|
67
|
+
"""
|
68
|
+
config_contents = _get_gcp_observability_config_contents()
|
69
|
+
config = GcpObservabilityConfig()
|
70
|
+
config.load_from_string_content(config_contents)
|
71
|
+
|
72
|
+
if not config.project_id:
|
73
|
+
# Get project ID from GCP environment variables since project ID was not
|
74
|
+
# set it in the GCP observability config.
|
75
|
+
config.project_id = _get_gcp_project_id_from_env_var()
|
76
|
+
if not config.project_id:
|
77
|
+
# Could not find project ID from GCP environment variables either.
|
78
|
+
raise ValueError("GCP Project ID not found.")
|
79
|
+
return config
|
80
|
+
|
81
|
+
|
82
|
+
def _get_gcp_project_id_from_env_var() -> Optional[str]:
|
83
|
+
"""Gets the project ID from the GCP environment variables.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
The project ID, or an empty string if the project ID could not be found.
|
87
|
+
"""
|
88
|
+
|
89
|
+
project_id = ""
|
90
|
+
project_id = os.getenv("GCP_PROJECT")
|
91
|
+
if project_id:
|
92
|
+
return project_id
|
93
|
+
|
94
|
+
project_id = os.getenv("GCLOUD_PROJECT")
|
95
|
+
if project_id:
|
96
|
+
return project_id
|
97
|
+
|
98
|
+
project_id = os.getenv("GOOGLE_CLOUD_PROJECT")
|
99
|
+
if project_id:
|
100
|
+
return project_id
|
101
|
+
|
102
|
+
return project_id
|
103
|
+
|
104
|
+
|
105
|
+
def _get_gcp_observability_config_contents() -> str:
|
106
|
+
"""Get the contents of the observability config from environment variable or file.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
The content from environment variable.
|
110
|
+
|
111
|
+
Raises:
|
112
|
+
ValueError: If no configuration content was found.
|
113
|
+
"""
|
114
|
+
|
115
|
+
contents_str = ""
|
116
|
+
# First try get config from GRPC_GCP_OBSERVABILITY_CONFIG_FILE_ENV.
|
117
|
+
config_path = os.getenv(GRPC_GCP_OBSERVABILITY_CONFIG_FILE_ENV)
|
118
|
+
if config_path:
|
119
|
+
with open(config_path, "r") as f:
|
120
|
+
contents_str = f.read()
|
121
|
+
|
122
|
+
# Next, try GRPC_GCP_OBSERVABILITY_CONFIG_ENV env var.
|
123
|
+
if not contents_str:
|
124
|
+
contents_str = os.getenv(GRPC_GCP_OBSERVABILITY_CONFIG_ENV)
|
125
|
+
|
126
|
+
if not contents_str:
|
127
|
+
raise ValueError("Configuration content not found.")
|
128
|
+
|
129
|
+
return contents_str
|
@@ -0,0 +1,301 @@
|
|
1
|
+
# Copyright 2023 gRPC authors.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from datetime import datetime
|
16
|
+
import os
|
17
|
+
from typing import List, Mapping, Optional, Tuple
|
18
|
+
|
19
|
+
from google.rpc import code_pb2
|
20
|
+
from grpc_observability import _observability # pytype: disable=pyi-error
|
21
|
+
from grpc_observability import _observability_config
|
22
|
+
from grpc_observability import _views
|
23
|
+
from opencensus.common.transports import async_
|
24
|
+
from opencensus.ext.stackdriver import stats_exporter
|
25
|
+
from opencensus.ext.stackdriver import trace_exporter
|
26
|
+
from opencensus.stats import stats as stats_module
|
27
|
+
from opencensus.stats.stats_recorder import StatsRecorder
|
28
|
+
from opencensus.stats.view_manager import ViewManager
|
29
|
+
from opencensus.tags.tag_key import TagKey
|
30
|
+
from opencensus.tags.tag_map import TagMap
|
31
|
+
from opencensus.tags.tag_value import TagValue
|
32
|
+
from opencensus.trace import execution_context
|
33
|
+
from opencensus.trace import samplers
|
34
|
+
from opencensus.trace import span
|
35
|
+
from opencensus.trace import span_context as span_context_module
|
36
|
+
from opencensus.trace import span_data as span_data_module
|
37
|
+
from opencensus.trace import status
|
38
|
+
from opencensus.trace import time_event
|
39
|
+
from opencensus.trace import trace_options
|
40
|
+
from opencensus.trace import tracer
|
41
|
+
|
42
|
+
# 60s is the default time for open census to call export.
|
43
|
+
CENSUS_UPLOAD_INTERVAL_SECS = int(
|
44
|
+
os.environ.get("GRPC_PYTHON_CENSUS_EXPORT_UPLOAD_INTERVAL_SECS", 20)
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
class StackDriverAsyncTransport(async_.AsyncTransport):
|
49
|
+
"""Wrapper class used to pass wait_period.
|
50
|
+
|
51
|
+
This is required because current StackDriver Tracing Exporter doesn't allow
|
52
|
+
us pass wait_period to AsyncTransport directly.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
exporter: An opencensus.trace.base_exporter.Exporter object.
|
56
|
+
"""
|
57
|
+
|
58
|
+
def __init__(self, exporter):
|
59
|
+
super().__init__(exporter, wait_period=CENSUS_UPLOAD_INTERVAL_SECS)
|
60
|
+
|
61
|
+
|
62
|
+
class OpenCensusExporter(_observability.Exporter):
|
63
|
+
config: _observability_config.GcpObservabilityConfig
|
64
|
+
default_labels: Optional[Mapping[str, str]]
|
65
|
+
project_id: str
|
66
|
+
tracer: Optional[tracer.Tracer]
|
67
|
+
stats_recorder: Optional[StatsRecorder]
|
68
|
+
view_manager: Optional[ViewManager]
|
69
|
+
|
70
|
+
def __init__(self, config: _observability_config.GcpObservabilityConfig):
|
71
|
+
self.config = config.get()
|
72
|
+
self.default_labels = self.config.labels
|
73
|
+
self.project_id = self.config.project_id
|
74
|
+
self.tracer = None
|
75
|
+
self.stats_recorder = None
|
76
|
+
self.view_manager = None
|
77
|
+
self._setup_open_census_stackdriver_exporter()
|
78
|
+
|
79
|
+
def _setup_open_census_stackdriver_exporter(self) -> None:
|
80
|
+
if self.config.stats_enabled:
|
81
|
+
stats = stats_module.stats
|
82
|
+
self.stats_recorder = stats.stats_recorder
|
83
|
+
self.view_manager = stats.view_manager
|
84
|
+
# If testing locally please add resource="global" to Options, otherwise
|
85
|
+
# StackDriver might override project_id based on detected resource.
|
86
|
+
options = stats_exporter.Options(project_id=self.project_id)
|
87
|
+
metrics_exporter = stats_exporter.new_stats_exporter(
|
88
|
+
options, interval=CENSUS_UPLOAD_INTERVAL_SECS
|
89
|
+
)
|
90
|
+
self.view_manager.register_exporter(metrics_exporter)
|
91
|
+
self._register_open_census_views()
|
92
|
+
|
93
|
+
if self.config.tracing_enabled:
|
94
|
+
current_tracer = execution_context.get_opencensus_tracer()
|
95
|
+
trace_id = current_tracer.span_context.trace_id
|
96
|
+
span_id = current_tracer.span_context.span_id
|
97
|
+
if not span_id:
|
98
|
+
span_id = span_context_module.generate_span_id()
|
99
|
+
span_context = span_context_module.SpanContext(
|
100
|
+
trace_id=trace_id, span_id=span_id
|
101
|
+
)
|
102
|
+
# Create and Saves Tracer and Sampler to ContextVar
|
103
|
+
sampler = samplers.ProbabilitySampler(
|
104
|
+
rate=self.config.sampling_rate
|
105
|
+
)
|
106
|
+
self.trace_exporter = trace_exporter.StackdriverExporter(
|
107
|
+
project_id=self.project_id,
|
108
|
+
transport=StackDriverAsyncTransport,
|
109
|
+
)
|
110
|
+
self.tracer = tracer.Tracer(
|
111
|
+
sampler=sampler,
|
112
|
+
span_context=span_context,
|
113
|
+
exporter=self.trace_exporter,
|
114
|
+
)
|
115
|
+
|
116
|
+
def export_stats_data(
|
117
|
+
self, stats_data: List[_observability.StatsData]
|
118
|
+
) -> None:
|
119
|
+
if not self.config.stats_enabled:
|
120
|
+
return
|
121
|
+
for data in stats_data:
|
122
|
+
measure = _views.METRICS_NAME_TO_MEASURE.get(data.name, None)
|
123
|
+
if not measure:
|
124
|
+
continue
|
125
|
+
# Create a measurement map for each metric, otherwise metrics will
|
126
|
+
# be overridden instead of accumulate.
|
127
|
+
measurement_map = self.stats_recorder.new_measurement_map()
|
128
|
+
# Add data label to default labels.
|
129
|
+
labels = data.labels
|
130
|
+
labels.update(self.default_labels)
|
131
|
+
tag_map = TagMap()
|
132
|
+
for key, value in labels.items():
|
133
|
+
tag_map.insert(TagKey(key), TagValue(value))
|
134
|
+
|
135
|
+
if data.measure_double:
|
136
|
+
measurement_map.measure_float_put(measure, data.value_float)
|
137
|
+
else:
|
138
|
+
measurement_map.measure_int_put(measure, data.value_int)
|
139
|
+
measurement_map.record(tag_map)
|
140
|
+
|
141
|
+
def export_tracing_data(
|
142
|
+
self, tracing_data: List[_observability.TracingData]
|
143
|
+
) -> None:
|
144
|
+
if not self.config.tracing_enabled:
|
145
|
+
return
|
146
|
+
for span_data in tracing_data:
|
147
|
+
# Only traced data will be exported, thus TraceOptions=1.
|
148
|
+
span_context = span_context_module.SpanContext(
|
149
|
+
trace_id=span_data.trace_id,
|
150
|
+
span_id=span_data.span_id,
|
151
|
+
trace_options=trace_options.TraceOptions(1),
|
152
|
+
)
|
153
|
+
span_datas = _get_span_data(
|
154
|
+
span_data, span_context, self.default_labels
|
155
|
+
)
|
156
|
+
self.trace_exporter.export(span_datas)
|
157
|
+
|
158
|
+
def _register_open_census_views(self) -> None:
|
159
|
+
# Client
|
160
|
+
self.view_manager.register_view(
|
161
|
+
_views.client_started_rpcs(self.default_labels)
|
162
|
+
)
|
163
|
+
self.view_manager.register_view(
|
164
|
+
_views.client_completed_rpcs(self.default_labels)
|
165
|
+
)
|
166
|
+
self.view_manager.register_view(
|
167
|
+
_views.client_roundtrip_latency(self.default_labels)
|
168
|
+
)
|
169
|
+
self.view_manager.register_view(
|
170
|
+
_views.client_api_latency(self.default_labels)
|
171
|
+
)
|
172
|
+
self.view_manager.register_view(
|
173
|
+
_views.client_sent_compressed_message_bytes_per_rpc(
|
174
|
+
self.default_labels
|
175
|
+
)
|
176
|
+
)
|
177
|
+
self.view_manager.register_view(
|
178
|
+
_views.client_received_compressed_message_bytes_per_rpc(
|
179
|
+
self.default_labels
|
180
|
+
)
|
181
|
+
)
|
182
|
+
|
183
|
+
# Server
|
184
|
+
self.view_manager.register_view(
|
185
|
+
_views.server_started_rpcs(self.default_labels)
|
186
|
+
)
|
187
|
+
self.view_manager.register_view(
|
188
|
+
_views.server_completed_rpcs(self.default_labels)
|
189
|
+
)
|
190
|
+
self.view_manager.register_view(
|
191
|
+
_views.server_sent_compressed_message_bytes_per_rpc(
|
192
|
+
self.default_labels
|
193
|
+
)
|
194
|
+
)
|
195
|
+
self.view_manager.register_view(
|
196
|
+
_views.server_received_compressed_message_bytes_per_rpc(
|
197
|
+
self.default_labels
|
198
|
+
)
|
199
|
+
)
|
200
|
+
self.view_manager.register_view(
|
201
|
+
_views.server_server_latency(self.default_labels)
|
202
|
+
)
|
203
|
+
|
204
|
+
|
205
|
+
def _get_span_annotations(
|
206
|
+
span_annotations: List[Tuple[str, str]]
|
207
|
+
) -> List[time_event.Annotation]:
|
208
|
+
annotations = []
|
209
|
+
|
210
|
+
for time_stamp, description in span_annotations:
|
211
|
+
time = datetime.fromisoformat(time_stamp)
|
212
|
+
annotations.append(time_event.Annotation(time, description))
|
213
|
+
|
214
|
+
return annotations
|
215
|
+
|
216
|
+
|
217
|
+
# pylint: disable=too-many-return-statements
|
218
|
+
# pylint: disable=too-many-branches
|
219
|
+
def _status_to_span_status(span_status: str) -> Optional[status.Status]:
|
220
|
+
if status == "OK":
|
221
|
+
return status.Status(code_pb2.OK, message=span_status)
|
222
|
+
elif status == "CANCELLED":
|
223
|
+
return status.Status(code_pb2.CANCELLED, message=span_status)
|
224
|
+
elif status == "UNKNOWN":
|
225
|
+
return status.Status(code_pb2.UNKNOWN, message=span_status)
|
226
|
+
elif status == "INVALID_ARGUMENT":
|
227
|
+
return status.Status(code_pb2.INVALID_ARGUMENT, message=span_status)
|
228
|
+
elif status == "DEADLINE_EXCEEDED":
|
229
|
+
return status.Status(code_pb2.DEADLINE_EXCEEDED, message=span_status)
|
230
|
+
elif status == "NOT_FOUND":
|
231
|
+
return status.Status(code_pb2.NOT_FOUND, message=span_status)
|
232
|
+
elif status == "ALREADY_EXISTS":
|
233
|
+
return status.Status(code_pb2.ALREADY_EXISTS, message=span_status)
|
234
|
+
elif status == "PERMISSION_DENIED":
|
235
|
+
return status.Status(code_pb2.PERMISSION_DENIED, message=span_status)
|
236
|
+
elif status == "UNAUTHENTICATED":
|
237
|
+
return status.Status(code_pb2.UNAUTHENTICATED, message=span_status)
|
238
|
+
elif status == "RESOURCE_EXHAUSTED":
|
239
|
+
return status.Status(code_pb2.RESOURCE_EXHAUSTED, message=span_status)
|
240
|
+
elif status == "FAILED_PRECONDITION":
|
241
|
+
return status.Status(code_pb2.FAILED_PRECONDITION, message=span_status)
|
242
|
+
elif status == "ABORTED":
|
243
|
+
return status.Status(code_pb2.ABORTED, message=span_status)
|
244
|
+
elif status == "OUT_OF_RANGE":
|
245
|
+
return status.Status(code_pb2.OUT_OF_RANGE, message=span_status)
|
246
|
+
elif status == "UNIMPLEMENTED":
|
247
|
+
return status.Status(code_pb2.UNIMPLEMENTED, message=span_status)
|
248
|
+
elif status == "INTERNAL":
|
249
|
+
return status.Status(code_pb2.INTERNAL, message=span_status)
|
250
|
+
elif status == "UNAVAILABLE":
|
251
|
+
return status.Status(code_pb2.UNAVAILABLE, message=span_status)
|
252
|
+
elif status == "DATA_LOSS":
|
253
|
+
return status.Status(code_pb2.DATA_LOSS, message=span_status)
|
254
|
+
else:
|
255
|
+
return None
|
256
|
+
|
257
|
+
|
258
|
+
def _get_span_data(
|
259
|
+
span_data: _observability.TracingData,
|
260
|
+
span_context: span_context_module.SpanContext,
|
261
|
+
labels: Mapping[str, str],
|
262
|
+
) -> List[span_data_module.SpanData]:
|
263
|
+
"""Extracts a list of SpanData tuples from a span.
|
264
|
+
|
265
|
+
Args:
|
266
|
+
span_data: _observability.TracingData to convert.
|
267
|
+
span_context: The context related to the span_data.
|
268
|
+
labels: Labels to be added to SpanData.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
A list of opencensus.trace.span_data.SpanData.
|
272
|
+
"""
|
273
|
+
span_attributes = span_data.span_labels
|
274
|
+
span_attributes.update(labels)
|
275
|
+
span_status = _status_to_span_status(span_data.status)
|
276
|
+
span_annotations = _get_span_annotations(span_data.span_annotations)
|
277
|
+
span_datas = [
|
278
|
+
span_data_module.SpanData(
|
279
|
+
name=span_data.name,
|
280
|
+
context=span_context,
|
281
|
+
span_id=span_data.span_id,
|
282
|
+
parent_span_id=span_data.parent_span_id
|
283
|
+
if span_data.parent_span_id
|
284
|
+
else None,
|
285
|
+
attributes=span_attributes,
|
286
|
+
start_time=span_data.start_time,
|
287
|
+
end_time=span_data.end_time,
|
288
|
+
child_span_count=span_data.child_span_count,
|
289
|
+
stack_trace=None,
|
290
|
+
annotations=span_annotations,
|
291
|
+
message_events=None,
|
292
|
+
links=None,
|
293
|
+
status=span_status,
|
294
|
+
same_process_as_parent_span=True
|
295
|
+
if span_data.parent_span_id
|
296
|
+
else None,
|
297
|
+
span_kind=span.SpanKind.UNSPECIFIED,
|
298
|
+
)
|
299
|
+
]
|
300
|
+
|
301
|
+
return span_datas
|