Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 7eaf980

Browse files
geobeaureyang
authored andcommitted
Add Threading integration (#329)
* Add Threading integration * Fix bad descriptions and documentation to remove ref to httplib/request * Fix date of copyright
1 parent 366066f commit 7eaf980

5 files changed

Lines changed: 186 additions & 1 deletion

File tree

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,12 @@ You can enable Google Cloud client libraries integration by specifying ``'google
430430

431431
.. _Cloud client libraries: https://github.com/GoogleCloudPlatform/google-cloud-python#google-cloud-python-client
432432

433+
Threading
434+
~~~~~~~~~
435+
436+
Census can propagate trace across threads when using the Threading package.
437+
438+
You can enable Threading integration by specifying ``'threading'`` to ``trace_integrations``.
433439

434440
------
435441
Stats

opencensus/trace/config_integration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
log = logging.getLogger(__name__)
1919

2020
SUPPORTED_INTEGRATIONS = ['httplib', 'mysql', 'postgresql', 'pymysql',
21-
'requests', 'sqlalchemy', 'google_cloud_clientlibs']
21+
'requests', 'sqlalchemy', 'google_cloud_clientlibs',
22+
'threading']
2223

2324
PATH_PREFIX = 'opencensus.trace.ext'
2425

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2018, OpenCensus 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.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright 2018, OpenCensus 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+
import logging
16+
import threading
17+
18+
from opencensus.trace import execution_context
19+
20+
log = logging.getLogger(__name__)
21+
22+
MODULE_NAME = "threading"
23+
24+
25+
def trace_integration(tracer=None):
26+
"""Wrap threading functions to trace."""
27+
log.info("Integrated module: {}".format(MODULE_NAME))
28+
# Wrap the threading start function
29+
start_func = getattr(threading.Thread, "start")
30+
setattr(threading.Thread, start_func.__name__,
31+
wrap_threading_start(start_func))
32+
33+
# Wrap the threading run function
34+
run_func = getattr(threading.Thread, "run")
35+
setattr(threading.Thread, run_func.__name__,
36+
wrap_threading_run(run_func))
37+
38+
39+
def wrap_threading_start(start_func):
40+
"""Wrap the start function from thread. Put the tracer informations in the
41+
threading object.
42+
"""
43+
44+
def call(self):
45+
self.__opencensus_tracer = execution_context.get_opencensus_tracer()
46+
return start_func(self)
47+
48+
return call
49+
50+
51+
def wrap_threading_run(run_func):
52+
"""Wrap the run function from thread. Get the tracer informations from the
53+
threading object and set it as current tracer.
54+
"""
55+
56+
def call(self):
57+
execution_context.set_opencensus_tracer(self.__opencensus_tracer)
58+
return run_func(self)
59+
60+
return call
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2018, OpenCensus 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+
import unittest
16+
import threading
17+
import mock
18+
19+
from opencensus.trace import span as span_module
20+
from opencensus.trace.ext.threading import trace
21+
from opencensus.trace import execution_context
22+
23+
class Test_threading_trace(unittest.TestCase):
24+
25+
def tearDown(self):
26+
execution_context.clear()
27+
28+
29+
def test_trace_integration(self):
30+
mock_wrap_start = mock.Mock()
31+
mock_wrap_run = mock.Mock()
32+
mock_threading = mock.Mock()
33+
34+
wrap_start_result = 'wrap start result'
35+
wrap_run_result = 'wrap run result'
36+
mock_wrap_start.return_value = wrap_start_result
37+
mock_wrap_run.return_value = wrap_run_result
38+
39+
mock_start_func = mock.Mock()
40+
mock_run_func = mock.Mock()
41+
mock_start_func.__name__ = 'start'
42+
mock_run_func.__name__ = 'run'
43+
setattr(mock_threading.Thread, 'start', mock_start_func)
44+
setattr(mock_threading.Thread, 'run', mock_run_func)
45+
46+
patch_wrap_start = mock.patch(
47+
'opencensus.trace.ext.threading.trace.wrap_threading_start',
48+
mock_wrap_start)
49+
patch_wrap_run = mock.patch(
50+
'opencensus.trace.ext.threading.trace.wrap_threading_run',
51+
mock_wrap_run)
52+
patch_threading = mock.patch(
53+
'opencensus.trace.ext.threading.trace.threading', mock_threading)
54+
55+
with patch_wrap_start, patch_wrap_run, patch_threading:
56+
trace.trace_integration()
57+
58+
self.assertEqual(getattr(mock_threading.Thread, 'start'),
59+
wrap_start_result)
60+
self.assertEqual(getattr(mock_threading.Thread, 'run'),
61+
wrap_run_result)
62+
63+
def test_wrap_threading(self):
64+
global global_tracer
65+
mock_span = mock.Mock()
66+
span_id = '1234'
67+
mock_span.span_id = span_id
68+
mock_tracer = MockTracer(mock_span)
69+
execution_context.set_opencensus_tracer(mock_tracer)
70+
71+
trace.trace_integration()
72+
73+
t = threading.Thread(target=self.fake_threaded_func)
74+
t.start()
75+
t.join()
76+
assert isinstance(global_tracer, MockTracer)
77+
78+
def fake_threaded_func(self):
79+
global global_tracer
80+
global_tracer = execution_context.get_opencensus_tracer()
81+
82+
83+
class MockTracer(object):
84+
def __init__(self, span=None):
85+
self.span = span
86+
87+
def current_span(self):
88+
return self.span
89+
90+
def start_span(self):
91+
span = mock.Mock()
92+
span.attributes = {}
93+
span.context_tracer = mock.Mock()
94+
span.context_tracer.span_context = mock.Mock()
95+
span.context_tracer.span_context.trace_id = '123'
96+
span.context_tracer.span_context.span_id = '456'
97+
span.context_tracer.span_context.tracestate = None
98+
self.span = span
99+
return span
100+
101+
def end_span(self):
102+
pass
103+
104+
def add_attribute_to_current_span(self, key, value):
105+
self.span.attributes[key] = value

0 commit comments

Comments
 (0)