metrics.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. package frankenphp
  2. import (
  3. "github.com/prometheus/client_golang/prometheus"
  4. "path/filepath"
  5. "regexp"
  6. "sync"
  7. "time"
  8. )
  9. var metricsNameRegex = regexp.MustCompile(`\W+`)
  10. var metricsNameFixRegex = regexp.MustCompile(`^_+|_+$`)
  11. type Metrics interface {
  12. // StartWorker Collects started workers
  13. StartWorker(name string)
  14. // StopWorker Collects stopped workers
  15. StopWorker(name string)
  16. // TotalWorkers Collects expected workers
  17. TotalWorkers(name string, num int)
  18. // TotalThreads Collects total threads
  19. TotalThreads(num int)
  20. // StartRequest Collects started requests
  21. StartRequest()
  22. // StopRequest Collects stopped requests
  23. StopRequest()
  24. // StopWorkerRequest Collects stopped worker requests
  25. StopWorkerRequest(name string, duration time.Duration)
  26. StartWorkerRequest(name string)
  27. Shutdown()
  28. }
  29. type nullMetrics struct{}
  30. func (n nullMetrics) StartWorker(name string) {
  31. }
  32. func (n nullMetrics) StopWorker(name string) {
  33. }
  34. func (n nullMetrics) TotalWorkers(name string, num int) {
  35. }
  36. func (n nullMetrics) TotalThreads(num int) {
  37. }
  38. func (n nullMetrics) StartRequest() {
  39. }
  40. func (n nullMetrics) StopRequest() {
  41. }
  42. func (n nullMetrics) StopWorkerRequest(name string, duration time.Duration) {
  43. }
  44. func (n nullMetrics) StartWorkerRequest(name string) {
  45. }
  46. func (n nullMetrics) Shutdown() {
  47. }
  48. type PrometheusMetrics struct {
  49. registry prometheus.Registerer
  50. totalThreads prometheus.Counter
  51. busyThreads prometheus.Gauge
  52. totalWorkers map[string]prometheus.Gauge
  53. busyWorkers map[string]prometheus.Gauge
  54. workerRequestTime map[string]prometheus.Counter
  55. workerRequestCount map[string]prometheus.Counter
  56. mu sync.Mutex
  57. }
  58. func (m *PrometheusMetrics) StartWorker(name string) {
  59. m.busyThreads.Inc()
  60. // tests do not register workers before starting them
  61. if _, ok := m.totalWorkers[name]; !ok {
  62. return
  63. }
  64. m.totalWorkers[name].Inc()
  65. }
  66. func (m *PrometheusMetrics) StopWorker(name string) {
  67. m.busyThreads.Dec()
  68. // tests do not register workers before starting them
  69. if _, ok := m.totalWorkers[name]; !ok {
  70. return
  71. }
  72. m.totalWorkers[name].Dec()
  73. }
  74. func (m *PrometheusMetrics) getIdentity(name string) (string, error) {
  75. actualName, err := filepath.Abs(name)
  76. if err != nil {
  77. return name, err
  78. }
  79. return actualName, nil
  80. }
  81. func (m *PrometheusMetrics) TotalWorkers(name string, num int) {
  82. m.mu.Lock()
  83. defer m.mu.Unlock()
  84. identity, err := m.getIdentity(name)
  85. if err != nil {
  86. // do not create metrics, let error propagate when worker is started
  87. return
  88. }
  89. subsystem := getWorkerNameForMetrics(name)
  90. if _, ok := m.totalWorkers[identity]; !ok {
  91. m.totalWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  92. Namespace: "frankenphp",
  93. Subsystem: subsystem,
  94. Name: "total_workers",
  95. Help: "Total number of PHP workers for this worker",
  96. })
  97. m.registry.MustRegister(m.totalWorkers[identity])
  98. }
  99. if _, ok := m.busyWorkers[identity]; !ok {
  100. m.busyWorkers[identity] = prometheus.NewGauge(prometheus.GaugeOpts{
  101. Namespace: "frankenphp",
  102. Subsystem: subsystem,
  103. Name: "busy_workers",
  104. Help: "Number of busy PHP workers for this worker",
  105. })
  106. m.registry.MustRegister(m.busyWorkers[identity])
  107. }
  108. if _, ok := m.workerRequestTime[identity]; !ok {
  109. m.workerRequestTime[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  110. Namespace: "frankenphp",
  111. Subsystem: subsystem,
  112. Name: "worker_request_time",
  113. })
  114. m.registry.MustRegister(m.workerRequestTime[identity])
  115. }
  116. if _, ok := m.workerRequestCount[identity]; !ok {
  117. m.workerRequestCount[identity] = prometheus.NewCounter(prometheus.CounterOpts{
  118. Namespace: "frankenphp",
  119. Subsystem: subsystem,
  120. Name: "worker_request_count",
  121. })
  122. m.registry.MustRegister(m.workerRequestCount[identity])
  123. }
  124. }
  125. func (m *PrometheusMetrics) TotalThreads(num int) {
  126. m.totalThreads.Add(float64(num))
  127. }
  128. func (m *PrometheusMetrics) StartRequest() {
  129. m.busyThreads.Inc()
  130. }
  131. func (m *PrometheusMetrics) StopRequest() {
  132. m.busyThreads.Dec()
  133. }
  134. func (m *PrometheusMetrics) StopWorkerRequest(name string, duration time.Duration) {
  135. if _, ok := m.workerRequestTime[name]; !ok {
  136. return
  137. }
  138. m.workerRequestCount[name].Inc()
  139. m.busyWorkers[name].Dec()
  140. m.workerRequestTime[name].Add(duration.Seconds())
  141. }
  142. func (m *PrometheusMetrics) StartWorkerRequest(name string) {
  143. if _, ok := m.busyWorkers[name]; !ok {
  144. return
  145. }
  146. m.busyWorkers[name].Inc()
  147. }
  148. func (m *PrometheusMetrics) Shutdown() {
  149. m.registry.Unregister(m.totalThreads)
  150. m.registry.Unregister(m.busyThreads)
  151. for _, g := range m.totalWorkers {
  152. m.registry.Unregister(g)
  153. }
  154. for _, g := range m.busyWorkers {
  155. m.registry.Unregister(g)
  156. }
  157. for _, c := range m.workerRequestTime {
  158. m.registry.Unregister(c)
  159. }
  160. for _, c := range m.workerRequestCount {
  161. m.registry.Unregister(c)
  162. }
  163. m.totalThreads = prometheus.NewCounter(prometheus.CounterOpts{
  164. Name: "frankenphp_total_threads",
  165. Help: "Total number of PHP threads",
  166. })
  167. m.busyThreads = prometheus.NewGauge(prometheus.GaugeOpts{
  168. Name: "frankenphp_busy_threads",
  169. Help: "Number of busy PHP threads",
  170. })
  171. m.totalWorkers = map[string]prometheus.Gauge{}
  172. m.busyWorkers = map[string]prometheus.Gauge{}
  173. m.workerRequestTime = map[string]prometheus.Counter{}
  174. m.workerRequestCount = map[string]prometheus.Counter{}
  175. m.registry.MustRegister(m.totalThreads)
  176. m.registry.MustRegister(m.busyThreads)
  177. }
  178. func getWorkerNameForMetrics(name string) string {
  179. name = metricsNameRegex.ReplaceAllString(name, "_")
  180. name = metricsNameFixRegex.ReplaceAllString(name, "")
  181. return name
  182. }
  183. func NewPrometheusMetrics(registry prometheus.Registerer) *PrometheusMetrics {
  184. if registry == nil {
  185. registry = prometheus.NewRegistry()
  186. }
  187. m := &PrometheusMetrics{
  188. registry: registry,
  189. totalThreads: prometheus.NewCounter(prometheus.CounterOpts{
  190. Name: "frankenphp_total_threads",
  191. Help: "Total number of PHP threads",
  192. }),
  193. busyThreads: prometheus.NewGauge(prometheus.GaugeOpts{
  194. Name: "frankenphp_busy_threads",
  195. Help: "Number of busy PHP threads",
  196. }),
  197. totalWorkers: map[string]prometheus.Gauge{},
  198. busyWorkers: map[string]prometheus.Gauge{},
  199. workerRequestTime: map[string]prometheus.Counter{},
  200. workerRequestCount: map[string]prometheus.Counter{},
  201. }
  202. m.registry.MustRegister(m.totalThreads)
  203. m.registry.MustRegister(m.busyThreads)
  204. return m
  205. }