Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/opentelemetry/metrics/_internal/instrument.py: 62%

120 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +0000

1# Copyright The OpenTelemetry 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# pylint: disable=too-many-ancestors 

16 

17 

18from abc import ABC, abstractmethod 

19from dataclasses import dataclass 

20from logging import getLogger 

21from re import compile as re_compile 

22from typing import ( 

23 Callable, 

24 Dict, 

25 Generator, 

26 Generic, 

27 Iterable, 

28 Optional, 

29 Sequence, 

30 TypeVar, 

31 Union, 

32) 

33 

34# pylint: disable=unused-import; needed for typing and sphinx 

35from opentelemetry import metrics 

36from opentelemetry.metrics._internal.observation import Observation 

37from opentelemetry.util.types import Attributes 

38 

39_logger = getLogger(__name__) 

40 

41_name_regex = re_compile(r"[a-zA-Z][-_.a-zA-Z0-9]{0,62}") 

42_unit_regex = re_compile(r"[\x00-\x7F]{0,63}") 

43 

44 

45@dataclass(frozen=True) 

46class CallbackOptions: 

47 """Options for the callback 

48 

49 Args: 

50 timeout_millis: Timeout for the callback's execution. If the callback does asynchronous 

51 work (e.g. HTTP requests), it should respect this timeout. 

52 """ 

53 

54 timeout_millis: float = 10_000 

55 

56 

57InstrumentT = TypeVar("InstrumentT", bound="Instrument") 

58CallbackT = Union[ 

59 Callable[[CallbackOptions], Iterable[Observation]], 

60 Generator[Iterable[Observation], CallbackOptions, None], 

61] 

62 

63 

64class Instrument(ABC): 

65 """Abstract class that serves as base for all instruments.""" 

66 

67 @abstractmethod 

68 def __init__( 

69 self, 

70 name: str, 

71 unit: str = "", 

72 description: str = "", 

73 ) -> None: 

74 pass 

75 

76 @staticmethod 

77 def _check_name_unit_description( 

78 name: str, unit: str, description: str 

79 ) -> Dict[str, Optional[str]]: 

80 """ 

81 Checks the following instrument name, unit and description for 

82 compliance with the spec. 

83 

84 Returns a dict with keys "name", "unit" and "description", the 

85 corresponding values will be the checked strings or `None` if the value 

86 is invalid. If valid, the checked strings should be used instead of the 

87 original values. 

88 """ 

89 

90 result: Dict[str, Optional[str]] = {} 

91 

92 if _name_regex.fullmatch(name) is not None: 

93 result["name"] = name 

94 else: 

95 result["name"] = None 

96 

97 if unit is None: 

98 unit = "" 

99 if _unit_regex.fullmatch(unit) is not None: 

100 result["unit"] = unit 

101 else: 

102 result["unit"] = None 

103 

104 if description is None: 

105 result["description"] = "" 

106 else: 

107 result["description"] = description 

108 

109 return result 

110 

111 

112class _ProxyInstrument(ABC, Generic[InstrumentT]): 

113 def __init__( 

114 self, 

115 name: str, 

116 unit: str = "", 

117 description: str = "", 

118 ) -> None: 

119 self._name = name 

120 self._unit = unit 

121 self._description = description 

122 self._real_instrument: Optional[InstrumentT] = None 

123 

124 def on_meter_set(self, meter: "metrics.Meter") -> None: 

125 """Called when a real meter is set on the creating _ProxyMeter""" 

126 

127 # We don't need any locking on proxy instruments because it's OK if some 

128 # measurements get dropped while a real backing instrument is being 

129 # created. 

130 self._real_instrument = self._create_real_instrument(meter) 

131 

132 @abstractmethod 

133 def _create_real_instrument(self, meter: "metrics.Meter") -> InstrumentT: 

134 """Create an instance of the real instrument. Implement this.""" 

135 

136 

137class _ProxyAsynchronousInstrument(_ProxyInstrument[InstrumentT]): 

138 def __init__( 

139 self, 

140 name: str, 

141 callbacks: Optional[Sequence[CallbackT]] = None, 

142 unit: str = "", 

143 description: str = "", 

144 ) -> None: 

145 super().__init__(name, unit, description) 

146 self._callbacks = callbacks 

147 

148 

149class Synchronous(Instrument): 

150 """Base class for all synchronous instruments""" 

151 

152 

153class Asynchronous(Instrument): 

154 """Base class for all asynchronous instruments""" 

155 

156 @abstractmethod 

157 def __init__( 

158 self, 

159 name: str, 

160 callbacks: Optional[Sequence[CallbackT]] = None, 

161 unit: str = "", 

162 description: str = "", 

163 ) -> None: 

164 super().__init__(name, unit=unit, description=description) 

165 

166 

167class Counter(Synchronous): 

168 """A Counter is a synchronous `Instrument` which supports non-negative increments.""" 

169 

170 @abstractmethod 

171 def add( 

172 self, 

173 amount: Union[int, float], 

174 attributes: Optional[Attributes] = None, 

175 ) -> None: 

176 pass 

177 

178 

179class NoOpCounter(Counter): 

180 """No-op implementation of `Counter`.""" 

181 

182 def __init__( 

183 self, 

184 name: str, 

185 unit: str = "", 

186 description: str = "", 

187 ) -> None: 

188 super().__init__(name, unit=unit, description=description) 

189 

190 def add( 

191 self, 

192 amount: Union[int, float], 

193 attributes: Optional[Attributes] = None, 

194 ) -> None: 

195 return super().add(amount, attributes=attributes) 

196 

197 

198class _ProxyCounter(_ProxyInstrument[Counter], Counter): 

199 def add( 

200 self, 

201 amount: Union[int, float], 

202 attributes: Optional[Attributes] = None, 

203 ) -> None: 

204 if self._real_instrument: 

205 self._real_instrument.add(amount, attributes) 

206 

207 def _create_real_instrument(self, meter: "metrics.Meter") -> Counter: 

208 return meter.create_counter(self._name, self._unit, self._description) 

209 

210 

211class UpDownCounter(Synchronous): 

212 """An UpDownCounter is a synchronous `Instrument` which supports increments and decrements.""" 

213 

214 @abstractmethod 

215 def add( 

216 self, 

217 amount: Union[int, float], 

218 attributes: Optional[Attributes] = None, 

219 ) -> None: 

220 pass 

221 

222 

223class NoOpUpDownCounter(UpDownCounter): 

224 """No-op implementation of `UpDownCounter`.""" 

225 

226 def __init__( 

227 self, 

228 name: str, 

229 unit: str = "", 

230 description: str = "", 

231 ) -> None: 

232 super().__init__(name, unit=unit, description=description) 

233 

234 def add( 

235 self, 

236 amount: Union[int, float], 

237 attributes: Optional[Attributes] = None, 

238 ) -> None: 

239 return super().add(amount, attributes=attributes) 

240 

241 

242class _ProxyUpDownCounter(_ProxyInstrument[UpDownCounter], UpDownCounter): 

243 def add( 

244 self, 

245 amount: Union[int, float], 

246 attributes: Optional[Attributes] = None, 

247 ) -> None: 

248 if self._real_instrument: 

249 self._real_instrument.add(amount, attributes) 

250 

251 def _create_real_instrument(self, meter: "metrics.Meter") -> UpDownCounter: 

252 return meter.create_up_down_counter( 

253 self._name, self._unit, self._description 

254 ) 

255 

256 

257class ObservableCounter(Asynchronous): 

258 """An ObservableCounter is an asynchronous `Instrument` which reports monotonically 

259 increasing value(s) when the instrument is being observed. 

260 """ 

261 

262 

263class NoOpObservableCounter(ObservableCounter): 

264 """No-op implementation of `ObservableCounter`.""" 

265 

266 def __init__( 

267 self, 

268 name: str, 

269 callbacks: Optional[Sequence[CallbackT]] = None, 

270 unit: str = "", 

271 description: str = "", 

272 ) -> None: 

273 super().__init__(name, callbacks, unit=unit, description=description) 

274 

275 

276class _ProxyObservableCounter( 

277 _ProxyAsynchronousInstrument[ObservableCounter], ObservableCounter 

278): 

279 def _create_real_instrument( 

280 self, meter: "metrics.Meter" 

281 ) -> ObservableCounter: 

282 return meter.create_observable_counter( 

283 self._name, self._callbacks, self._unit, self._description 

284 ) 

285 

286 

287class ObservableUpDownCounter(Asynchronous): 

288 """An ObservableUpDownCounter is an asynchronous `Instrument` which reports additive value(s) (e.g. 

289 the process heap size - it makes sense to report the heap size from multiple processes and sum them 

290 up, so we get the total heap usage) when the instrument is being observed. 

291 """ 

292 

293 

294class NoOpObservableUpDownCounter(ObservableUpDownCounter): 

295 """No-op implementation of `ObservableUpDownCounter`.""" 

296 

297 def __init__( 

298 self, 

299 name: str, 

300 callbacks: Optional[Sequence[CallbackT]] = None, 

301 unit: str = "", 

302 description: str = "", 

303 ) -> None: 

304 super().__init__(name, callbacks, unit=unit, description=description) 

305 

306 

307class _ProxyObservableUpDownCounter( 

308 _ProxyAsynchronousInstrument[ObservableUpDownCounter], 

309 ObservableUpDownCounter, 

310): 

311 def _create_real_instrument( 

312 self, meter: "metrics.Meter" 

313 ) -> ObservableUpDownCounter: 

314 return meter.create_observable_up_down_counter( 

315 self._name, self._callbacks, self._unit, self._description 

316 ) 

317 

318 

319class Histogram(Synchronous): 

320 """Histogram is a synchronous `Instrument` which can be used to report arbitrary values 

321 that are likely to be statistically meaningful. It is intended for statistics such as 

322 histograms, summaries, and percentile. 

323 """ 

324 

325 @abstractmethod 

326 def record( 

327 self, 

328 amount: Union[int, float], 

329 attributes: Optional[Attributes] = None, 

330 ) -> None: 

331 pass 

332 

333 

334class NoOpHistogram(Histogram): 

335 """No-op implementation of `Histogram`.""" 

336 

337 def __init__( 

338 self, 

339 name: str, 

340 unit: str = "", 

341 description: str = "", 

342 ) -> None: 

343 super().__init__(name, unit=unit, description=description) 

344 

345 def record( 

346 self, 

347 amount: Union[int, float], 

348 attributes: Optional[Attributes] = None, 

349 ) -> None: 

350 return super().record(amount, attributes=attributes) 

351 

352 

353class _ProxyHistogram(_ProxyInstrument[Histogram], Histogram): 

354 def record( 

355 self, 

356 amount: Union[int, float], 

357 attributes: Optional[Attributes] = None, 

358 ) -> None: 

359 if self._real_instrument: 

360 self._real_instrument.record(amount, attributes) 

361 

362 def _create_real_instrument(self, meter: "metrics.Meter") -> Histogram: 

363 return meter.create_histogram( 

364 self._name, self._unit, self._description 

365 ) 

366 

367 

368class ObservableGauge(Asynchronous): 

369 """Asynchronous Gauge is an asynchronous `Instrument` which reports non-additive value(s) (e.g. 

370 the room temperature - it makes no sense to report the temperature value from multiple rooms 

371 and sum them up) when the instrument is being observed. 

372 """ 

373 

374 

375class NoOpObservableGauge(ObservableGauge): 

376 """No-op implementation of `ObservableGauge`.""" 

377 

378 def __init__( 

379 self, 

380 name: str, 

381 callbacks: Optional[Sequence[CallbackT]] = None, 

382 unit: str = "", 

383 description: str = "", 

384 ) -> None: 

385 super().__init__(name, callbacks, unit=unit, description=description) 

386 

387 

388class _ProxyObservableGauge( 

389 _ProxyAsynchronousInstrument[ObservableGauge], 

390 ObservableGauge, 

391): 

392 def _create_real_instrument( 

393 self, meter: "metrics.Meter" 

394 ) -> ObservableGauge: 

395 return meter.create_observable_gauge( 

396 self._name, self._callbacks, self._unit, self._description 

397 )