Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/opencensus/trace/span.py: 65%

194 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-06 06:04 +0000

1# Copyright 2017, 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 

15try: 

16 from collections.abc import MutableMapping 

17 from collections.abc import Sequence 

18except ImportError: 

19 from collections import MutableMapping 

20 from collections import Sequence 

21 

22import threading 

23from collections import OrderedDict, deque 

24from datetime import datetime 

25from itertools import chain 

26 

27from opencensus.common import utils 

28from opencensus.trace import attributes as attributes_module 

29from opencensus.trace import base_span 

30from opencensus.trace import link as link_module 

31from opencensus.trace import stack_trace as stack_trace_module 

32from opencensus.trace import status as status_module 

33from opencensus.trace import time_event 

34from opencensus.trace.span_context import generate_span_id 

35from opencensus.trace.tracers import base 

36 

37# https://github.com/census-instrumentation/opencensus-specs/blob/master/trace/TraceConfig.md # noqa 

38MAX_NUM_ATTRIBUTES = 32 

39MAX_NUM_ANNOTATIONS = 32 

40MAX_NUM_MESSAGE_EVENTS = 128 

41MAX_NUM_LINKS = 32 

42 

43 

44class BoundedList(Sequence): 

45 """An append only list with a fixed max size.""" 

46 def __init__(self, maxlen): 

47 self.dropped = 0 

48 self._dq = deque(maxlen=maxlen) 

49 self._lock = threading.Lock() 

50 

51 def __repr__(self): 

52 return ("{}({}, maxlen={})" 

53 .format( 

54 type(self).__name__, 

55 list(self._dq), 

56 self._dq.maxlen 

57 )) 

58 

59 def __getitem__(self, index): 

60 return self._dq[index] 

61 

62 def __len__(self): 

63 return len(self._dq) 

64 

65 def __iter__(self): 

66 return iter(self._dq) 

67 

68 def append(self, item): 

69 with self._lock: 

70 if len(self._dq) == self._dq.maxlen: 

71 self.dropped += 1 

72 self._dq.append(item) 

73 

74 def extend(self, seq): 

75 with self._lock: 

76 to_drop = len(seq) + len(self._dq) - self._dq.maxlen 

77 if to_drop > 0: 

78 self.dropped += to_drop 

79 self._dq.extend(seq) 

80 

81 @classmethod 

82 def from_seq(cls, maxlen, seq): 

83 seq = tuple(seq) 

84 if len(seq) > maxlen: 

85 raise ValueError 

86 bounded_list = cls(maxlen) 

87 bounded_list._dq = deque(seq, maxlen=maxlen) 

88 return bounded_list 

89 

90 

91class BoundedDict(MutableMapping): 

92 """A dict with a fixed max capacity.""" 

93 def __init__(self, maxlen): 

94 self.maxlen = maxlen 

95 self.dropped = 0 

96 self._dict = OrderedDict() 

97 self._lock = threading.Lock() 

98 

99 def __repr__(self): 

100 return ("{}({}, maxlen={})" 

101 .format( 

102 type(self).__name__, 

103 dict(self._dict), 

104 self.maxlen 

105 )) 

106 

107 def __getitem__(self, key): 

108 return self._dict[key] 

109 

110 def __setitem__(self, key, value): 

111 with self._lock: 

112 if key in self._dict: 

113 del self._dict[key] 

114 elif len(self._dict) == self.maxlen: 

115 del self._dict[next(iter(self._dict.keys()))] 

116 self.dropped += 1 

117 self._dict[key] = value 

118 

119 def __delitem__(self, key): 

120 del self._dict[key] 

121 

122 def __iter__(self): 

123 return iter(self._dict) 

124 

125 def __len__(self): 

126 return len(self._dict) 

127 

128 @classmethod 

129 def from_map(cls, maxlen, mapping): 

130 mapping = OrderedDict(mapping) 

131 if len(mapping) > maxlen: 

132 raise ValueError 

133 bounded_dict = cls(maxlen) 

134 bounded_dict._dict = mapping 

135 return bounded_dict 

136 

137 

138class SpanKind(object): 

139 UNSPECIFIED = 0 

140 SERVER = 1 

141 CLIENT = 2 

142 

143 

144class Span(base_span.BaseSpan): 

145 """A span is an individual timed event which forms a node of the trace 

146 tree. Each span has its name, span id and parent id. The parent id 

147 indicates the causal relationships between the individual spans in a 

148 single distributed trace. Span that does not have a parent id is called 

149 root span. All spans associated with a specific trace also share a common 

150 trace id. Spans do not need to be continuous, there can be gaps between 

151 two spans. 

152 

153 :type name: str 

154 :param name: The name of the span. 

155 

156 :type parent_span: :class:`~opencensus.trace.span.Span` 

157 :param parent_span: (Optional) Parent span. 

158 

159 :type attributes: dict 

160 :param attributes: Collection of attributes associated with the span. 

161 Attribute keys must be less than 128 bytes. 

162 Attribute values must be less than 16 kilobytes. 

163 

164 :type start_time: str 

165 :param start_time: (Optional) Start of the time interval (inclusive) 

166 during which the trace data was collected from the 

167 application. 

168 

169 :type end_time: str 

170 :param end_time: (Optional) End of the time interval (inclusive) during 

171 which the trace data was collected from the application. 

172 

173 :type span_id: int 

174 :param span_id: Identifier for the span, unique within a trace. 

175 

176 :type stack_trace: :class: `~opencensus.trace.stack_trace.StackTrace` 

177 :param stack_trace: (Optional) A call stack appearing in a trace 

178 

179 :type annotations: list(:class:`opencensus.trace.time_event.Annotation`) 

180 :param annotations: (Optional) The list of span annotations. 

181 

182 :type message_events: 

183 list(:class:`opencensus.trace.time_event.MessageEvent`) 

184 :param message_events: (Optional) The list of span message events. 

185 

186 :type links: list 

187 :param links: (Optional) Links associated with the span. You can have up 

188 to 128 links per Span. 

189 

190 :type status: :class: `~opencensus.trace.status.Status` 

191 :param status: (Optional) An optional final status for this span. 

192 

193 :type same_process_as_parent_span: bool 

194 :param same_process_as_parent_span: (Optional) A highly recommended but not 

195 required flag that identifies when a 

196 trace crosses a process boundary. 

197 True when the parent_span belongs to 

198 the same process as the current span. 

199 

200 :type context_tracer: :class:`~opencensus.trace.tracers.context_tracer. 

201 ContextTracer` 

202 :param context_tracer: The tracer that holds a stack of spans. If this is 

203 not None, then when exiting a span, use the end_span 

204 method in the tracer class to finish a span. If no 

205 tracer is passed in, then just finish the span using 

206 the finish method in the Span class. 

207 

208 :type span_kind: int 

209 :param span_kind: (Optional) Highly recommended flag that denotes the type 

210 of span (valid values defined by :class: 

211 `opencensus.trace.span.SpanKind`) 

212 """ 

213 

214 def __init__( 

215 self, 

216 name, 

217 parent_span=None, 

218 attributes=None, 

219 start_time=None, 

220 end_time=None, 

221 span_id=None, 

222 stack_trace=None, 

223 annotations=None, 

224 message_events=None, 

225 links=None, 

226 status=None, 

227 same_process_as_parent_span=None, 

228 context_tracer=None, 

229 span_kind=SpanKind.UNSPECIFIED): 

230 self.name = name 

231 self.parent_span = parent_span 

232 self.start_time = start_time 

233 self.end_time = end_time 

234 

235 if span_id is None: 

236 span_id = generate_span_id() 

237 

238 if attributes is None: 

239 self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES) 

240 else: 

241 self.attributes = BoundedDict.from_map( 

242 MAX_NUM_ATTRIBUTES, attributes) 

243 

244 # Do not manipulate spans directly using the methods in Span Class, 

245 # make sure to use the Tracer. 

246 if parent_span is None: 

247 parent_span = base.NullContextManager() 

248 

249 if annotations is None: 

250 self.annotations = BoundedList(MAX_NUM_ANNOTATIONS) 

251 else: 

252 self.annotations = BoundedList.from_seq(MAX_NUM_LINKS, annotations) 

253 

254 if message_events is None: 

255 self.message_events = BoundedList(MAX_NUM_MESSAGE_EVENTS) 

256 else: 

257 self.message_events = BoundedList.from_seq( 

258 MAX_NUM_LINKS, message_events) 

259 

260 if links is None: 

261 self.links = BoundedList(MAX_NUM_LINKS) 

262 else: 

263 self.links = BoundedList.from_seq(MAX_NUM_LINKS, links) 

264 

265 if status is None: 

266 self.status = status_module.Status.as_ok() 

267 else: 

268 self.status = status 

269 

270 self.span_id = span_id 

271 self.stack_trace = stack_trace 

272 self.same_process_as_parent_span = same_process_as_parent_span 

273 self._child_spans = [] 

274 self.context_tracer = context_tracer 

275 self.span_kind = span_kind 

276 for callback in Span._on_create_callbacks: 

277 callback(self) 

278 

279 _on_create_callbacks = [] 

280 

281 @staticmethod 

282 def on_create(callback): 

283 Span._on_create_callbacks.append(callback) 

284 

285 @property 

286 def children(self): 

287 """The child spans of the current span.""" 

288 return self._child_spans 

289 

290 def span(self, name='child_span'): 

291 """Create a child span for the current span and append it to the child 

292 spans list. 

293 

294 :type name: str 

295 :param name: (Optional) The name of the child span. 

296 

297 :rtype: :class: `~opencensus.trace.span.Span` 

298 :returns: A child Span to be added to the current span. 

299 """ 

300 child_span = Span(name, parent_span=self) 

301 self._child_spans.append(child_span) 

302 return child_span 

303 

304 def add_attribute(self, attribute_key, attribute_value): 

305 """Add attribute to span. 

306 

307 :type attribute_key: str 

308 :param attribute_key: Attribute key. 

309 

310 :type attribute_value:str 

311 :param attribute_value: Attribute value. 

312 """ 

313 self.attributes[attribute_key] = attribute_value 

314 

315 def add_annotation(self, description, **attrs): 

316 """Add an annotation to span. 

317 

318 :type description: str 

319 :param description: A user-supplied message describing the event. 

320 The maximum length for the description is 256 bytes. 

321 

322 :type attrs: kwargs 

323 :param attrs: keyworded arguments e.g. failed=True, name='Caching' 

324 """ 

325 self.annotations.append(time_event.Annotation( 

326 datetime.utcnow(), 

327 description, 

328 attributes_module.Attributes(attrs) 

329 )) 

330 

331 def add_message_event(self, message_event): 

332 """Add a message event to this span. 

333 

334 :type message_event: :class:`opencensus.trace.time_event.MessageEvent` 

335 :param message_event: The message event to attach to this span. 

336 """ 

337 self.message_events.append(message_event) 

338 

339 def add_link(self, link): 

340 """Add a Link. 

341 

342 :type link: :class: `~opencensus.trace.link.Link` 

343 :param link: A Link object. 

344 """ 

345 if isinstance(link, link_module.Link): 

346 self.links.append(link) 

347 else: 

348 raise TypeError("Type Error: received {}, but requires Link.". 

349 format(type(link).__name__)) 

350 

351 def set_status(self, status): 

352 """Sets span status. 

353 

354 :type code: :class: `~opencensus.trace.status.Status` 

355 :param code: A Status object. 

356 """ 

357 if isinstance(status, status_module.Status): 

358 self.status = status 

359 else: 

360 raise TypeError("Type Error: received {}, but requires Status.". 

361 format(type(status).__name__)) 

362 

363 def start(self): 

364 """Set the start time for a span.""" 

365 self.start_time = utils.to_iso_str() 

366 

367 def finish(self): 

368 """Set the end time for a span.""" 

369 self.end_time = utils.to_iso_str() 

370 

371 def __iter__(self): 

372 """Iterate through the span tree.""" 

373 for span in chain.from_iterable(map(iter, self.children)): 

374 yield span 

375 yield self 

376 

377 def __enter__(self): 

378 """Start a span.""" 

379 self.start() 

380 return self 

381 

382 def __exit__(self, exception_type, exception_value, traceback): 

383 """Finish a span.""" 

384 if traceback is not None: 

385 self.stack_trace =\ 

386 stack_trace_module.StackTrace.from_traceback(traceback) 

387 if exception_value is not None: 

388 self.status = status_module.Status.from_exception(exception_value) 

389 if self.context_tracer is not None: 

390 self.context_tracer.end_span() 

391 return 

392 

393 self.finish() 

394 

395 

396def format_span_json(span): 

397 """Helper to format a Span in JSON format. 

398 

399 :type span: :class:`~opencensus.trace.span.Span` 

400 :param span: A Span to be transferred to JSON format. 

401 

402 :rtype: dict 

403 :returns: Formatted Span. 

404 """ 

405 span_json = { 

406 'displayName': utils.get_truncatable_str(span.name), 

407 'spanId': span.span_id, 

408 'startTime': span.start_time, 

409 'endTime': span.end_time, 

410 'childSpanCount': len(span._child_spans) 

411 } 

412 

413 parent_span_id = None 

414 

415 if span.parent_span is not None: 

416 parent_span_id = span.parent_span.span_id 

417 

418 if parent_span_id is not None: 

419 span_json['parentSpanId'] = parent_span_id 

420 

421 if span.attributes: 

422 span_json['attributes'] = attributes_module.Attributes( 

423 span.attributes).format_attributes_json() 

424 

425 if span.stack_trace is not None: 

426 span_json['stackTrace'] = span.stack_trace.format_stack_trace_json() 

427 

428 formatted_time_events = [] 

429 if span.annotations: 

430 formatted_time_events.extend( 

431 {'time': aa.timestamp, 

432 'annotation': aa.format_annotation_json()} 

433 for aa in span.annotations) 

434 if span.message_events: 

435 formatted_time_events.extend( 

436 {'time': aa.timestamp, 

437 'message_event': aa.format_message_event_json()} 

438 for aa in span.message_events) 

439 if formatted_time_events: 

440 span_json['timeEvents'] = { 

441 'timeEvent': formatted_time_events 

442 } 

443 

444 if span.links: 

445 span_json['links'] = { 

446 'link': [ 

447 link.format_link_json() for link in span.links] 

448 } 

449 

450 if span.status is not None: 

451 span_json['status'] = span.status.format_status_json() 

452 

453 if span.same_process_as_parent_span is not None: 

454 span_json['sameProcessAsParentSpan'] = \ 

455 span.same_process_as_parent_span 

456 

457 return span_json