metrics.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. package frankenphp
  2. import (
  3. "github.com/dunglas/frankenphp/internal/fastabs"
  4. "regexp"
  5. "sync"
  6. "time"
  7. "github.com/prometheus/client_golang/prometheus"
  8. )
  9. var metricsNameRegex = regexp.MustCompile(`\W+`)
  10. var metricsNameFixRegex = regexp.MustCompile(`^_+|_+$`)
  11. const (
  12. StopReasonCrash = iota
  13. StopReasonRestart
  14. StopReasonShutdown
  15. )
  16. type StopReason int
  17. type Metrics interface {
  18. // StartWorker collects started workers
  19. StartWorker(name string)
  20. // ReadyWorker collects ready workers
  21. ReadyWorker(name string)
  22. // StopWorker collects stopped workers
  23. StopWorker(name string, reason StopReason)
  24. // TotalWorkers collects expected workers
  25. TotalWorkers(name string, num int)
  26. // TotalThreads collects total threads
  27. TotalThreads(num int)
  28. // StartRequest collects started requests
  29. StartRequest()
  30. // StopRequest collects stopped requests
  31. StopRequest()
  32. // StopWorkerRequest collects stopped worker requests
  33. StopWorkerRequest(name string, duration time.Duration)
  34. // StartWorkerRequest collects started worker requests
  35. StartWorkerRequest(name string)
  36. Shutdown()
  37. }
  38. type nullMetrics struct{}
  39. func (n nullMetrics) StartWorker(string) {
  40. }
  41. func (n nullMetrics) ReadyWorker(string) {
  42. }
  43. func (n nullMetrics) StopWorker(string, StopReason) {
  44. }
  45. func (n nullMetrics) TotalWorkers(string, int) {
  46. }
  47. func (n nullMetrics) TotalThreads(int) {
  48. }
  49. func (n nullMetrics) StartRequest() {
  50. }
  51. func (n nullMetrics) StopRequest() {
  52. }
  53. func (n nullMetrics) StopWorkerRequest(string, time.Duration) {
  54. }
  55. func (n nullMetrics) StartWorkerRequest(string) {
  56. }
  57. func (n nullMetrics) Shutdown() {
  58. }
  59. type PrometheusMetrics struct {
  60. registry prometheus.Registerer
  61. totalThreads prometheus.Counter
  62. busyThreads prometheus.Gauge
  63. totalWorkers map[string]prometheus.Gauge
  64. busyWorkers map[string]prometheus.Gauge
  65. readyWorkers map[string]prometheus.Gauge
  66. workerCrashes map[string]prometheus.Counter
  67. workerRestarts map[string]prometheus.Counter
  68. workerRequestTime map[string]prometheus.Counter
  69. workerRequestCount map[string]prometheus.Counter
  70. mu sync.Mutex
  71. }
  72. func (m *PrometheusMetrics) StartWorker(name string) {
  73. m.busyThreads.Inc()
  74. // tests do not register workers before starting them
  75. if _, ok := m.totalWorkers[name]; !ok {
  76. return
  77. }
  78. m.totalWorkers[name].Inc()
  79. }
  80. func (m *PrometheusMetrics) ReadyWorker(name string) {
  81. if _, ok := m.totalWorkers[name]; !ok {
  82. return
  83. }
  84. m.readyWorkers[name].Inc()
  85. }
  86. func (m *PrometheusMetrics) StopWorker(name string, reason StopReason) {
  87. m.busyThreads.Dec()
  88. // tests do not register workers before starting them
  89. if _, ok := m.totalWorkers[name]; !ok {
  90. return
  91. }
  92. m.totalWorkers[name].Dec()
  93. m.readyWorkers[name].Dec()
  94. if reason == StopReasonCrash {
  95. m.workerCrashes[name].Inc()
  96. } else if reason == StopReasonRestart {
  97. m.workerRestarts[name].Inc()
  98. } else if reason == StopReasonShutdown {
  99. m.totalWorkers[name].Dec()
  100. }
  101. }
  102. func (m *PrometheusMetrics) getIdentity(name string) (string, error) {
  103. actualName, err := fastabs.FastAbs(name)
  104. if err != nil {
  105. return name, err
  106. }
  107. return actualName, nil
  108. }
  109. func (m *PrometheusMetrics) TotalWorkers(name string, _ int) {
  110. m.mu.Lock()
  111. defer m.mu.Unlock()
  112. identity, err := m.getIdentity(name)
  113. if err != nil {
  114. // do not create metrics, let error propagate when worker is started
  115. return
  116. }
  117. subsystem := getWorkerNameForMetrics(name)
  118. if _, ok := m.totalWorkers[identity]; !ok {
  119. m.totalWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  120. Namespace: "frankenphp",
  121. Subsystem: subsystem,
  122. Name: "total_workers",
  123. Help: "Total number of PHP workers for this worker",
  124. })
  125. m.registry.MustRegister(m.totalWorkers[identity])
  126. }
  127. if _, ok := m.workerCrashes[identity]; !ok {
  128. m.workerCrashes[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  129. Namespace: "frankenphp",
  130. Subsystem: subsystem,
  131. Name: "worker_crashes",
  132. Help: "Number of PHP worker crashes for this worker",
  133. })
  134. m.registry.MustRegister(m.workerCrashes[identity])
  135. }
  136. if _, ok := m.workerRestarts[identity]; !ok {
  137. m.workerRestarts[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  138. Namespace: "frankenphp",
  139. Subsystem: subsystem,
  140. Name: "worker_restarts",
  141. Help: "Number of PHP worker restarts for this worker",
  142. })
  143. m.registry.MustRegister(m.workerRestarts[identity])
  144. }
  145. if _, ok := m.readyWorkers[identity]; !ok {
  146. m.readyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  147. Namespace: "frankenphp",
  148. Subsystem: subsystem,
  149. Name: "ready_workers",
  150. Help: "Running workers that have successfully called frankenphp_handle_request at least once",
  151. })
  152. m.registry.MustRegister(m.readyWorkers[identity])
  153. }
  154. if _, ok := m.busyWorkers[identity]; !ok {
  155. m.busyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  156. Namespace: "frankenphp",
  157. Subsystem: subsystem,
  158. Name: "busy_workers",
  159. Help: "Number of busy PHP workers for this worker",
  160. })
  161. m.registry.MustRegister(m.busyWorkers[identity])
  162. }
  163. if _, ok := m.workerRequestTime[identity]; !ok {
  164. m.workerRequestTime[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  165. Namespace: "frankenphp",
  166. Subsystem: subsystem,
  167. Name: "worker_request_time",
  168. })
  169. m.registry.MustRegister(m.workerRequestTime[identity])
  170. }
  171. if _, ok := m.workerRequestCount[identity]; !ok {
  172. m.workerRequestCount[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  173. Namespace: "frankenphp",
  174. Subsystem: subsystem,
  175. Name: "worker_request_count",
  176. })
  177. m.registry.MustRegister(m.workerRequestCount[identity])
  178. }
  179. }
  180. func (m *PrometheusMetrics) TotalThreads(num int) {
  181. m.totalThreads.Add(float64(num))
  182. }
  183. func (m *PrometheusMetrics) StartRequest() {
  184. m.busyThreads.Inc()
  185. }
  186. func (m *PrometheusMetrics) StopRequest() {
  187. m.busyThreads.Dec()
  188. }
  189. func (m *PrometheusMetrics) StopWorkerRequest(name string, duration time.Duration) {
  190. if _, ok := m.workerRequestTime[name]; !ok {
  191. return
  192. }
  193. m.workerRequestCount[name].Inc()
  194. m.busyWorkers[name].Dec()
  195. m.workerRequestTime[name].Add(duration.Seconds())
  196. }
  197. func (m *PrometheusMetrics) StartWorkerRequest(name string) {
  198. if _, ok := m.busyWorkers[name]; !ok {
  199. return
  200. }
  201. m.busyWorkers[name].Inc()
  202. }
  203. func (m *PrometheusMetrics) Shutdown() {
  204. m.registry.Unregister(m.totalThreads)
  205. m.registry.Unregister(m.busyThreads)
  206. for _, g := range m.totalWorkers {
  207. m.registry.Unregister(g)
  208. }
  209. for _, g := range m.busyWorkers {
  210. m.registry.Unregister(g)
  211. }
  212. for _, c := range m.workerRequestTime {
  213. m.registry.Unregister(c)
  214. }
  215. for _, c := range m.workerRequestCount {
  216. m.registry.Unregister(c)
  217. }
  218. for _, c := range m.workerCrashes {
  219. m.registry.Unregister(c)
  220. }
  221. for _, c := range m.workerRestarts {
  222. m.registry.Unregister(c)
  223. }
  224. for _, g := range m.readyWorkers {
  225. m.registry.Unregister(g)
  226. }
  227. m.totalThreads = prometheus.NewCounter(prometheus.CounterOpts{
  228. Name: "frankenphp_total_threads",
  229. Help: "Total number of PHP threads",
  230. })
  231. m.busyThreads = prometheus.NewGauge(prometheus.GaugeOpts{
  232. Name: "frankenphp_busy_threads",
  233. Help: "Number of busy PHP threads",
  234. })
  235. m.totalWorkers = map[string]prometheus.Gauge{}
  236. m.busyWorkers = map[string]prometheus.Gauge{}
  237. m.workerRequestTime = map[string]prometheus.Counter{}
  238. m.workerRequestCount = map[string]prometheus.Counter{}
  239. m.workerRestarts = map[string]prometheus.Counter{}
  240. m.workerCrashes = map[string]prometheus.Counter{}
  241. m.readyWorkers = map[string]prometheus.Gauge{}
  242. m.registry.MustRegister(m.totalThreads)
  243. m.registry.MustRegister(m.busyThreads)
  244. }
  245. func getWorkerNameForMetrics(name string) string {
  246. name = metricsNameRegex.ReplaceAllString(name, "_")
  247. name = metricsNameFixRegex.ReplaceAllString(name, "")
  248. return name
  249. }
  250. func NewPrometheusMetrics(registry prometheus.Registerer) *PrometheusMetrics {
  251. if registry == nil {
  252. registry = prometheus.NewRegistry()
  253. }
  254. m := &PrometheusMetrics{
  255. registry: registry,
  256. totalThreads: prometheus.NewCounter(prometheus.CounterOpts{
  257. Name: "frankenphp_total_threads",
  258. Help: "Total number of PHP threads",
  259. }),
  260. busyThreads: prometheus.NewGauge(prometheus.GaugeOpts{
  261. Name: "frankenphp_busy_threads",
  262. Help: "Number of busy PHP threads",
  263. }),
  264. totalWorkers: map[string]prometheus.Gauge{},
  265. busyWorkers: map[string]prometheus.Gauge{},
  266. workerRequestTime: map[string]prometheus.Counter{},
  267. workerRequestCount: map[string]prometheus.Counter{},
  268. workerRestarts: map[string]prometheus.Counter{},
  269. workerCrashes: map[string]prometheus.Counter{},
  270. readyWorkers: map[string]prometheus.Gauge{},
  271. }
  272. m.registry.MustRegister(m.totalThreads)
  273. m.registry.MustRegister(m.busyThreads)
  274. return m
  275. }