metrics.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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. QueuedWorkerRequest(name string)
  38. DequeuedWorkerRequest(name string)
  39. QueuedRequest()
  40. DequeuedRequest()
  41. }
  42. type nullMetrics struct{}
  43. func (n nullMetrics) StartWorker(string) {
  44. }
  45. func (n nullMetrics) ReadyWorker(string) {
  46. }
  47. func (n nullMetrics) StopWorker(string, StopReason) {
  48. }
  49. func (n nullMetrics) TotalWorkers(string, int) {
  50. }
  51. func (n nullMetrics) TotalThreads(int) {
  52. }
  53. func (n nullMetrics) StartRequest() {
  54. }
  55. func (n nullMetrics) StopRequest() {
  56. }
  57. func (n nullMetrics) StopWorkerRequest(string, time.Duration) {
  58. }
  59. func (n nullMetrics) StartWorkerRequest(string) {
  60. }
  61. func (n nullMetrics) Shutdown() {
  62. }
  63. func (n nullMetrics) QueuedWorkerRequest(name string) {}
  64. func (n nullMetrics) DequeuedWorkerRequest(name string) {}
  65. func (n nullMetrics) QueuedRequest() {}
  66. func (n nullMetrics) DequeuedRequest() {}
  67. type PrometheusMetrics struct {
  68. registry prometheus.Registerer
  69. totalThreads prometheus.Counter
  70. busyThreads prometheus.Gauge
  71. totalWorkers map[string]prometheus.Gauge
  72. busyWorkers map[string]prometheus.Gauge
  73. readyWorkers map[string]prometheus.Gauge
  74. workerCrashes map[string]prometheus.Counter
  75. workerRestarts map[string]prometheus.Counter
  76. workerRequestTime map[string]prometheus.Counter
  77. workerRequestCount map[string]prometheus.Counter
  78. workerQueueDepth map[string]prometheus.Gauge
  79. queueDepth prometheus.Gauge
  80. mu sync.Mutex
  81. }
  82. func (m *PrometheusMetrics) StartWorker(name string) {
  83. m.busyThreads.Inc()
  84. // tests do not register workers before starting them
  85. if _, ok := m.totalWorkers[name]; !ok {
  86. return
  87. }
  88. m.totalWorkers[name].Inc()
  89. }
  90. func (m *PrometheusMetrics) ReadyWorker(name string) {
  91. if _, ok := m.totalWorkers[name]; !ok {
  92. return
  93. }
  94. m.readyWorkers[name].Inc()
  95. }
  96. func (m *PrometheusMetrics) StopWorker(name string, reason StopReason) {
  97. m.busyThreads.Dec()
  98. // tests do not register workers before starting them
  99. if _, ok := m.totalWorkers[name]; !ok {
  100. return
  101. }
  102. m.totalWorkers[name].Dec()
  103. m.readyWorkers[name].Dec()
  104. if reason == StopReasonCrash {
  105. m.workerCrashes[name].Inc()
  106. } else if reason == StopReasonRestart {
  107. m.workerRestarts[name].Inc()
  108. } else if reason == StopReasonShutdown {
  109. m.totalWorkers[name].Dec()
  110. }
  111. }
  112. func (m *PrometheusMetrics) getIdentity(name string) (string, error) {
  113. actualName, err := fastabs.FastAbs(name)
  114. if err != nil {
  115. return name, err
  116. }
  117. return actualName, nil
  118. }
  119. func (m *PrometheusMetrics) TotalWorkers(name string, _ int) {
  120. m.mu.Lock()
  121. defer m.mu.Unlock()
  122. identity, err := m.getIdentity(name)
  123. if err != nil {
  124. // do not create metrics, let error propagate when worker is started
  125. return
  126. }
  127. subsystem := getWorkerNameForMetrics(name)
  128. if _, ok := m.totalWorkers[identity]; !ok {
  129. m.totalWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  130. Namespace: "frankenphp",
  131. Subsystem: subsystem,
  132. Name: "total_workers",
  133. Help: "Total number of PHP workers for this worker",
  134. })
  135. m.registry.MustRegister(m.totalWorkers[identity])
  136. }
  137. if _, ok := m.workerCrashes[identity]; !ok {
  138. m.workerCrashes[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  139. Namespace: "frankenphp",
  140. Subsystem: subsystem,
  141. Name: "worker_crashes",
  142. Help: "Number of PHP worker crashes for this worker",
  143. })
  144. m.registry.MustRegister(m.workerCrashes[identity])
  145. }
  146. if _, ok := m.workerRestarts[identity]; !ok {
  147. m.workerRestarts[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  148. Namespace: "frankenphp",
  149. Subsystem: subsystem,
  150. Name: "worker_restarts",
  151. Help: "Number of PHP worker restarts for this worker",
  152. })
  153. m.registry.MustRegister(m.workerRestarts[identity])
  154. }
  155. if _, ok := m.readyWorkers[identity]; !ok {
  156. m.readyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  157. Namespace: "frankenphp",
  158. Subsystem: subsystem,
  159. Name: "ready_workers",
  160. Help: "Running workers that have successfully called frankenphp_handle_request at least once",
  161. })
  162. m.registry.MustRegister(m.readyWorkers[identity])
  163. }
  164. if _, ok := m.busyWorkers[identity]; !ok {
  165. m.busyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  166. Namespace: "frankenphp",
  167. Subsystem: subsystem,
  168. Name: "busy_workers",
  169. Help: "Number of busy PHP workers for this worker",
  170. })
  171. m.registry.MustRegister(m.busyWorkers[identity])
  172. }
  173. if _, ok := m.workerRequestTime[identity]; !ok {
  174. m.workerRequestTime[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  175. Namespace: "frankenphp",
  176. Subsystem: subsystem,
  177. Name: "worker_request_time",
  178. })
  179. m.registry.MustRegister(m.workerRequestTime[identity])
  180. }
  181. if _, ok := m.workerRequestCount[identity]; !ok {
  182. m.workerRequestCount[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  183. Namespace: "frankenphp",
  184. Subsystem: subsystem,
  185. Name: "worker_request_count",
  186. })
  187. m.registry.MustRegister(m.workerRequestCount[identity])
  188. }
  189. if _, ok := m.workerQueueDepth[identity]; !ok {
  190. m.workerQueueDepth[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  191. Namespace: "frankenphp",
  192. Subsystem: subsystem,
  193. Name: "worker_queue_depth",
  194. })
  195. m.registry.MustRegister(m.workerQueueDepth[identity])
  196. }
  197. }
  198. func (m *PrometheusMetrics) TotalThreads(num int) {
  199. m.totalThreads.Add(float64(num))
  200. }
  201. func (m *PrometheusMetrics) StartRequest() {
  202. m.busyThreads.Inc()
  203. }
  204. func (m *PrometheusMetrics) StopRequest() {
  205. m.busyThreads.Dec()
  206. }
  207. func (m *PrometheusMetrics) StopWorkerRequest(name string, duration time.Duration) {
  208. if _, ok := m.workerRequestTime[name]; !ok {
  209. return
  210. }
  211. m.workerRequestCount[name].Inc()
  212. m.busyWorkers[name].Dec()
  213. m.workerRequestTime[name].Add(duration.Seconds())
  214. }
  215. func (m *PrometheusMetrics) StartWorkerRequest(name string) {
  216. if _, ok := m.busyWorkers[name]; !ok {
  217. return
  218. }
  219. m.busyWorkers[name].Inc()
  220. }
  221. func (m *PrometheusMetrics) QueuedWorkerRequest(name string) {
  222. if _, ok := m.workerQueueDepth[name]; !ok {
  223. return
  224. }
  225. m.workerQueueDepth[name].Inc()
  226. }
  227. func (m *PrometheusMetrics) DequeuedWorkerRequest(name string) {
  228. if _, ok := m.workerQueueDepth[name]; !ok {
  229. return
  230. }
  231. m.workerQueueDepth[name].Dec()
  232. }
  233. func (m *PrometheusMetrics) QueuedRequest() {
  234. m.queueDepth.Inc()
  235. }
  236. func (m *PrometheusMetrics) DequeuedRequest() {
  237. m.queueDepth.Dec()
  238. }
  239. func (m *PrometheusMetrics) Shutdown() {
  240. m.registry.Unregister(m.totalThreads)
  241. m.registry.Unregister(m.busyThreads)
  242. m.registry.Unregister(m.queueDepth)
  243. for _, g := range m.totalWorkers {
  244. m.registry.Unregister(g)
  245. }
  246. for _, g := range m.busyWorkers {
  247. m.registry.Unregister(g)
  248. }
  249. for _, c := range m.workerRequestTime {
  250. m.registry.Unregister(c)
  251. }
  252. for _, c := range m.workerRequestCount {
  253. m.registry.Unregister(c)
  254. }
  255. for _, c := range m.workerCrashes {
  256. m.registry.Unregister(c)
  257. }
  258. for _, c := range m.workerRestarts {
  259. m.registry.Unregister(c)
  260. }
  261. for _, g := range m.readyWorkers {
  262. m.registry.Unregister(g)
  263. }
  264. for _, g := range m.workerQueueDepth {
  265. m.registry.Unregister(g)
  266. }
  267. m.totalThreads = prometheus.NewCounter(prometheus.CounterOpts{
  268. Name: "frankenphp_total_threads",
  269. Help: "Total number of PHP threads",
  270. })
  271. m.busyThreads = prometheus.NewGauge(prometheus.GaugeOpts{
  272. Name: "frankenphp_busy_threads",
  273. Help: "Number of busy PHP threads",
  274. })
  275. m.totalWorkers = map[string]prometheus.Gauge{}
  276. m.busyWorkers = map[string]prometheus.Gauge{}
  277. m.workerRequestTime = map[string]prometheus.Counter{}
  278. m.workerRequestCount = map[string]prometheus.Counter{}
  279. m.workerRestarts = map[string]prometheus.Counter{}
  280. m.workerCrashes = map[string]prometheus.Counter{}
  281. m.readyWorkers = map[string]prometheus.Gauge{}
  282. m.workerQueueDepth = map[string]prometheus.Gauge{}
  283. m.queueDepth = prometheus.NewGauge(prometheus.GaugeOpts{
  284. Name: "frankenphp_queue_depth",
  285. Help: "Number of regular queued requests",
  286. })
  287. m.registry.MustRegister(m.totalThreads)
  288. m.registry.MustRegister(m.busyThreads)
  289. m.registry.MustRegister(m.queueDepth)
  290. }
  291. func getWorkerNameForMetrics(name string) string {
  292. name = metricsNameRegex.ReplaceAllString(name, "_")
  293. name = metricsNameFixRegex.ReplaceAllString(name, "")
  294. return name
  295. }
  296. func NewPrometheusMetrics(registry prometheus.Registerer) *PrometheusMetrics {
  297. if registry == nil {
  298. registry = prometheus.NewRegistry()
  299. }
  300. m := &PrometheusMetrics{
  301. registry: registry,
  302. totalThreads: prometheus.NewCounter(prometheus.CounterOpts{
  303. Name: "frankenphp_total_threads",
  304. Help: "Total number of PHP threads",
  305. }),
  306. busyThreads: prometheus.NewGauge(prometheus.GaugeOpts{
  307. Name: "frankenphp_busy_threads",
  308. Help: "Number of busy PHP threads",
  309. }),
  310. totalWorkers: map[string]prometheus.Gauge{},
  311. busyWorkers: map[string]prometheus.Gauge{},
  312. workerRequestTime: map[string]prometheus.Counter{},
  313. workerRequestCount: map[string]prometheus.Counter{},
  314. workerRestarts: map[string]prometheus.Counter{},
  315. workerCrashes: map[string]prometheus.Counter{},
  316. readyWorkers: map[string]prometheus.Gauge{},
  317. workerQueueDepth: map[string]prometheus.Gauge{},
  318. queueDepth: prometheus.NewGauge(prometheus.GaugeOpts{
  319. Name: "frankenphp_queue_depth",
  320. Help: "Number of regular queued requests",
  321. }),
  322. }
  323. m.registry.MustRegister(m.totalThreads)
  324. m.registry.MustRegister(m.busyThreads)
  325. m.registry.MustRegister(m.queueDepth)
  326. return m
  327. }