test_transport.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import io
  2. from typing import Any
  3. from unittest.mock import patch
  4. from minimetrics.core import CounterMetric, DistributionMetric, GaugeMetric, SetMetric
  5. from minimetrics.transport import MetricEnvelopeTransport, RelayStatsdEncoder
  6. from minimetrics.types import BucketKey
  7. def encode_metric(value):
  8. encoder = RelayStatsdEncoder()
  9. out = io.BytesIO()
  10. encoder._encode(value, out)
  11. return out.getvalue().decode("utf-8")
  12. def test_relay_encoder_with_counter():
  13. bucket_key: BucketKey = (
  14. "c",
  15. "button_click",
  16. "none",
  17. (
  18. ("browser", "Chrome"),
  19. ("browser.version", "1.0"),
  20. ),
  21. )
  22. metric = CounterMetric(first=2)
  23. flushed_metric = (1693994400, bucket_key, metric)
  24. result = encode_metric(flushed_metric)
  25. assert result == "button_click@none:2|c|#browser:Chrome,browser.version:1.0|T1693994400"
  26. def test_relay_encoder_with_distribution():
  27. bucket_key: BucketKey = (
  28. "d",
  29. "execution_time",
  30. "second",
  31. (
  32. ("browser", "Chrome"),
  33. ("browser.version", "1.0"),
  34. ),
  35. )
  36. metric = DistributionMetric(first=1.0)
  37. metric.add(0.5)
  38. metric.add(3.0)
  39. flushed_metric = (1693994400, bucket_key, metric)
  40. result = encode_metric(flushed_metric)
  41. assert (
  42. result
  43. == "execution_time@second:1.0:0.5:3.0|d|#browser:Chrome,browser.version:1.0|T1693994400"
  44. )
  45. def test_relay_encoder_with_set():
  46. bucket_key: BucketKey = (
  47. "s",
  48. "users",
  49. "none",
  50. (
  51. ("browser", "Chrome"),
  52. ("browser.version", "1.0"),
  53. ),
  54. )
  55. metric = SetMetric(first=123)
  56. metric.add(456)
  57. metric.add("riccardo")
  58. flushed_metric = (1693994400, bucket_key, metric)
  59. result = encode_metric(flushed_metric)
  60. pieces = result.split("|")
  61. m = pieces[0].split(":")
  62. assert m[0] == "users@none"
  63. assert sorted(m[1:]) == sorted(["123", "456", "3455635177"])
  64. assert pieces[1] == "s"
  65. assert pieces[2] == "#browser:Chrome,browser.version:1.0"
  66. assert pieces[3] == "T1693994400"
  67. def test_relay_encoder_with_gauge():
  68. bucket_key: BucketKey = (
  69. "g",
  70. "startup_time",
  71. "second",
  72. (
  73. ("browser", "Chrome"),
  74. ("browser.version", "1.0"),
  75. ),
  76. )
  77. metric = GaugeMetric(first=10.0)
  78. metric.add(5.0)
  79. metric.add(7.0)
  80. flushed_metric = (1693994400, bucket_key, metric)
  81. result = encode_metric(flushed_metric)
  82. assert (
  83. result
  84. == "startup_time@second:7.0:5.0:10.0:22.0:3|g|#browser:Chrome,browser.version:1.0|T1693994400"
  85. )
  86. def test_relay_encoder_with_invalid_chars():
  87. bucket_key: BucketKey = (
  88. "c",
  89. "büttòn_click",
  90. "second",
  91. (
  92. # Invalid tag key.
  93. ("browser\nname", "Chrome"),
  94. # Invalid tag value.
  95. ("browser.version", "\t1.\n0ô"),
  96. # Valid tag key and value.
  97. ("platform", "Android"),
  98. # Totally invalid tag key.
  99. ("\nöś", "Windows"),
  100. # Totally invalid tag value.
  101. ("version", "\n\t"),
  102. ),
  103. )
  104. metric = CounterMetric(first=1)
  105. flushed_metric = (1693994400, bucket_key, metric)
  106. result = encode_metric(flushed_metric)
  107. assert (
  108. result
  109. == "bttn_click@second:1|c|#browsername:Chrome,browser.version:1.0,platform:Android,version:|T1693994400"
  110. )
  111. bucket_key = (
  112. "c",
  113. "üòë",
  114. "second",
  115. (),
  116. )
  117. metric = CounterMetric(first=1)
  118. flushed_metric = (1693994400, bucket_key, metric)
  119. assert encode_metric(flushed_metric) == "invalid-metric-name@second:1|c|T1693994400"
  120. def test_relay_encoder_with_multiple_metrics():
  121. encoder = RelayStatsdEncoder()
  122. flushed_metric_1 = (
  123. 1693994400,
  124. (
  125. "g",
  126. "startup_time",
  127. "second",
  128. (
  129. ("browser", "Chrome"),
  130. ("browser.version", "1.0"),
  131. ),
  132. ),
  133. GaugeMetric(first=10.0),
  134. )
  135. flushed_metric_2 = (
  136. 1693994400,
  137. (
  138. "c",
  139. "button_click",
  140. "none",
  141. (
  142. ("browser", "Chrome"),
  143. ("browser.version", "1.0"),
  144. ),
  145. ),
  146. CounterMetric(first=1),
  147. )
  148. flushed_metric_3 = (
  149. 1693994400,
  150. (
  151. "c",
  152. # This name will be completely scraped, resulting in an invalid metric.
  153. "öüâ",
  154. "none",
  155. (
  156. ("browser", "Chrome"),
  157. ("browser.version", "1.0"),
  158. ),
  159. ),
  160. CounterMetric(first=1),
  161. )
  162. metrics: Any = [flushed_metric_1, flushed_metric_2, flushed_metric_3]
  163. result = encoder.encode_multiple(metrics).decode("utf-8")
  164. assert result == (
  165. "startup_time@second:10.0:10.0:10.0:10.0:1|g|#browser:Chrome,browser.version:1.0|T1693994400\n"
  166. "button_click@none:1|c|#browser:Chrome,browser.version:1.0|T1693994400\n"
  167. "invalid-metric-name@none:1|c|#browser:Chrome,browser.version:1.0|T1693994400\n"
  168. )
  169. @patch("minimetrics.transport.sentry_sdk")
  170. def test_send(sentry_sdk):
  171. flushed_metric = (
  172. 1693994400,
  173. {
  174. (
  175. "c",
  176. "button_click",
  177. "none",
  178. (
  179. ("browser", "Chrome"),
  180. ("browser.version", "1.0"),
  181. ),
  182. ): CounterMetric(first=1),
  183. },
  184. )
  185. transport = MetricEnvelopeTransport(RelayStatsdEncoder())
  186. metrics: Any = [flushed_metric]
  187. transport.send(metrics)
  188. args = sentry_sdk.Hub.current.client.transport.capture_envelope.call_args.args
  189. assert len(args) == 1
  190. arg = args[0]
  191. assert arg.items[0].type == "statsd"
  192. assert arg.items[0].data_category == "statsd"