Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

205 wiersze
7.0 KiB

  1. # Copyright 2015-2022 The Matrix.org Foundation C.I.C.
  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. import gc
  15. import logging
  16. import platform
  17. import time
  18. from typing import Iterable
  19. from prometheus_client.core import (
  20. REGISTRY,
  21. CounterMetricFamily,
  22. Gauge,
  23. GaugeMetricFamily,
  24. Histogram,
  25. Metric,
  26. )
  27. from twisted.internet import task
  28. from synapse.metrics._types import Collector
  29. """Prometheus metrics for garbage collection"""
  30. logger = logging.getLogger(__name__)
  31. # The minimum time in seconds between GCs for each generation, regardless of the current GC
  32. # thresholds and counts.
  33. MIN_TIME_BETWEEN_GCS = (1.0, 10.0, 30.0)
  34. running_on_pypy = platform.python_implementation() == "PyPy"
  35. #
  36. # Python GC metrics
  37. #
  38. gc_unreachable = Gauge("python_gc_unreachable_total", "Unreachable GC objects", ["gen"])
  39. gc_time = Histogram(
  40. "python_gc_time",
  41. "Time taken to GC (sec)",
  42. ["gen"],
  43. buckets=[
  44. 0.0025,
  45. 0.005,
  46. 0.01,
  47. 0.025,
  48. 0.05,
  49. 0.10,
  50. 0.25,
  51. 0.50,
  52. 1.00,
  53. 2.50,
  54. 5.00,
  55. 7.50,
  56. 15.00,
  57. 30.00,
  58. 45.00,
  59. 60.00,
  60. ],
  61. )
  62. class GCCounts(Collector):
  63. def collect(self) -> Iterable[Metric]:
  64. cm = GaugeMetricFamily("python_gc_counts", "GC object counts", labels=["gen"])
  65. for n, m in enumerate(gc.get_count()):
  66. cm.add_metric([str(n)], m)
  67. yield cm
  68. def install_gc_manager() -> None:
  69. """Disable automatic GC, and replace it with a task that runs every 100ms
  70. This means that (a) we can limit how often GC runs; (b) we can get some metrics
  71. about GC activity.
  72. It does nothing on PyPy.
  73. """
  74. if running_on_pypy:
  75. return
  76. REGISTRY.register(GCCounts())
  77. gc.disable()
  78. # The time (in seconds since the epoch) of the last time we did a GC for each generation.
  79. _last_gc = [0.0, 0.0, 0.0]
  80. def _maybe_gc() -> None:
  81. # Check if we need to do a manual GC (since its been disabled), and do
  82. # one if necessary. Note we go in reverse order as e.g. a gen 1 GC may
  83. # promote an object into gen 2, and we don't want to handle the same
  84. # object multiple times.
  85. threshold = gc.get_threshold()
  86. counts = gc.get_count()
  87. end = time.time()
  88. for i in (2, 1, 0):
  89. # We check if we need to do one based on a straightforward
  90. # comparison between the threshold and count. We also do an extra
  91. # check to make sure that we don't a GC too often.
  92. if threshold[i] < counts[i] and MIN_TIME_BETWEEN_GCS[i] < end - _last_gc[i]:
  93. if i == 0:
  94. logger.debug("Collecting gc %d", i)
  95. else:
  96. logger.info("Collecting gc %d", i)
  97. start = time.time()
  98. unreachable = gc.collect(i)
  99. end = time.time()
  100. _last_gc[i] = end
  101. gc_time.labels(i).observe(end - start)
  102. gc_unreachable.labels(i).set(unreachable)
  103. gc_task = task.LoopingCall(_maybe_gc)
  104. gc_task.start(0.1)
  105. #
  106. # PyPy GC / memory metrics
  107. #
  108. class PyPyGCStats(Collector):
  109. def collect(self) -> Iterable[Metric]:
  110. # @stats is a pretty-printer object with __str__() returning a nice table,
  111. # plus some fields that contain data from that table.
  112. # unfortunately, fields are pretty-printed themselves (i. e. '4.5MB').
  113. stats = gc.get_stats(memory_pressure=False) # type: ignore
  114. # @s contains same fields as @stats, but as actual integers.
  115. s = stats._s # type: ignore
  116. # also note that field naming is completely braindead
  117. # and only vaguely correlates with the pretty-printed table.
  118. # >>>> gc.get_stats(False)
  119. # Total memory consumed:
  120. # GC used: 8.7MB (peak: 39.0MB) # s.total_gc_memory, s.peak_memory
  121. # in arenas: 3.0MB # s.total_arena_memory
  122. # rawmalloced: 1.7MB # s.total_rawmalloced_memory
  123. # nursery: 4.0MB # s.nursery_size
  124. # raw assembler used: 31.0kB # s.jit_backend_used
  125. # -----------------------------
  126. # Total: 8.8MB # stats.memory_used_sum
  127. #
  128. # Total memory allocated:
  129. # GC allocated: 38.7MB (peak: 41.1MB) # s.total_allocated_memory, s.peak_allocated_memory
  130. # in arenas: 30.9MB # s.peak_arena_memory
  131. # rawmalloced: 4.1MB # s.peak_rawmalloced_memory
  132. # nursery: 4.0MB # s.nursery_size
  133. # raw assembler allocated: 1.0MB # s.jit_backend_allocated
  134. # -----------------------------
  135. # Total: 39.7MB # stats.memory_allocated_sum
  136. #
  137. # Total time spent in GC: 0.073 # s.total_gc_time
  138. pypy_gc_time = CounterMetricFamily(
  139. "pypy_gc_time_seconds_total",
  140. "Total time spent in PyPy GC",
  141. labels=[],
  142. )
  143. pypy_gc_time.add_metric([], s.total_gc_time / 1000)
  144. yield pypy_gc_time
  145. pypy_mem = GaugeMetricFamily(
  146. "pypy_memory_bytes",
  147. "Memory tracked by PyPy allocator",
  148. labels=["state", "class", "kind"],
  149. )
  150. # memory used by JIT assembler
  151. pypy_mem.add_metric(["used", "", "jit"], s.jit_backend_used)
  152. pypy_mem.add_metric(["allocated", "", "jit"], s.jit_backend_allocated)
  153. # memory used by GCed objects
  154. pypy_mem.add_metric(["used", "", "arenas"], s.total_arena_memory)
  155. pypy_mem.add_metric(["allocated", "", "arenas"], s.peak_arena_memory)
  156. pypy_mem.add_metric(["used", "", "rawmalloced"], s.total_rawmalloced_memory)
  157. pypy_mem.add_metric(["allocated", "", "rawmalloced"], s.peak_rawmalloced_memory)
  158. pypy_mem.add_metric(["used", "", "nursery"], s.nursery_size)
  159. pypy_mem.add_metric(["allocated", "", "nursery"], s.nursery_size)
  160. # totals
  161. pypy_mem.add_metric(["used", "totals", "gc"], s.total_gc_memory)
  162. pypy_mem.add_metric(["allocated", "totals", "gc"], s.total_allocated_memory)
  163. pypy_mem.add_metric(["used", "totals", "gc_peak"], s.peak_memory)
  164. pypy_mem.add_metric(["allocated", "totals", "gc_peak"], s.peak_allocated_memory)
  165. yield pypy_mem
  166. if running_on_pypy:
  167. REGISTRY.register(PyPyGCStats())