Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

133 righe
3.8 KiB

  1. package redisprom
  2. import (
  3. "context"
  4. "time"
  5. "github.com/go-redis/redis/v8"
  6. "github.com/prometheus/client_golang/prometheus"
  7. )
  8. type (
  9. // Hook represents a go-redis hook that exports metrics of commands and pipelines.
  10. //
  11. // The following metrics are exported:
  12. //
  13. // - Single commands (not-pipelined)
  14. // - Histogram of duration
  15. // - Counter of errors
  16. //
  17. // - Pipelined commands
  18. // - Counter of commands
  19. // - Counter of errors
  20. //
  21. // The duration of individual pipelined commands won't be collected, but the overall duration of the
  22. // pipeline will, with a pseudo-command called "pipeline".
  23. Hook struct {
  24. options *Options
  25. singleCommands *prometheus.HistogramVec
  26. pipelinedCommands *prometheus.CounterVec
  27. singleErrors *prometheus.CounterVec
  28. pipelinedErrors *prometheus.CounterVec
  29. }
  30. startKey struct{}
  31. )
  32. var (
  33. labelNames = []string{"instance", "command"}
  34. )
  35. // NewHook creates a new go-redis hook instance and registers Prometheus collectors.
  36. func NewHook(opts ...Option) *Hook {
  37. options := DefaultOptions()
  38. options.Merge(opts...)
  39. singleCommands := register(prometheus.NewHistogramVec(prometheus.HistogramOpts{
  40. Namespace: options.Namespace,
  41. Name: "redis_single_commands",
  42. Help: "Histogram of single Redis commands",
  43. Buckets: options.DurationBuckets,
  44. }, labelNames)).(*prometheus.HistogramVec)
  45. pipelinedCommands := register(prometheus.NewCounterVec(prometheus.CounterOpts{
  46. Namespace: options.Namespace,
  47. Name: "redis_pipelined_commands",
  48. Help: "Number of pipelined Redis commands",
  49. }, labelNames)).(*prometheus.CounterVec)
  50. singleErrors := register(prometheus.NewCounterVec(prometheus.CounterOpts{
  51. Namespace: options.Namespace,
  52. Name: "redis_single_errors",
  53. Help: "Number of single Redis commands that have failed",
  54. }, labelNames)).(*prometheus.CounterVec)
  55. pipelinedErrors := register(prometheus.NewCounterVec(prometheus.CounterOpts{
  56. Namespace: options.Namespace,
  57. Name: "redis_pipelined_errors",
  58. Help: "Number of pipelined Redis commands that have failed",
  59. }, labelNames)).(*prometheus.CounterVec)
  60. return &Hook{
  61. options: options,
  62. singleCommands: singleCommands,
  63. pipelinedCommands: pipelinedCommands,
  64. singleErrors: singleErrors,
  65. pipelinedErrors: pipelinedErrors,
  66. }
  67. }
  68. func (hook *Hook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
  69. return context.WithValue(ctx, startKey{}, time.Now()), nil
  70. }
  71. func (hook *Hook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
  72. if start, ok := ctx.Value(startKey{}).(time.Time); ok {
  73. duration := time.Since(start).Seconds()
  74. hook.singleCommands.WithLabelValues(hook.options.InstanceName, cmd.Name()).Observe(duration)
  75. }
  76. if isActualErr(cmd.Err()) {
  77. hook.singleErrors.WithLabelValues(hook.options.InstanceName, cmd.Name()).Inc()
  78. }
  79. return nil
  80. }
  81. func (hook *Hook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
  82. return context.WithValue(ctx, startKey{}, time.Now()), nil
  83. }
  84. func (hook *Hook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
  85. if err := hook.AfterProcess(ctx, redis.NewCmd(ctx, "pipeline")); err != nil {
  86. return err
  87. }
  88. for _, cmd := range cmds {
  89. hook.pipelinedCommands.WithLabelValues(hook.options.InstanceName, cmd.Name()).Inc()
  90. if isActualErr(cmd.Err()) {
  91. hook.pipelinedErrors.WithLabelValues(hook.options.InstanceName, cmd.Name()).Inc()
  92. }
  93. }
  94. return nil
  95. }
  96. func register(collector prometheus.Collector) prometheus.Collector {
  97. err := prometheus.DefaultRegisterer.Register(collector)
  98. if err == nil {
  99. return collector
  100. }
  101. if arErr, ok := err.(prometheus.AlreadyRegisteredError); ok {
  102. return arErr.ExistingCollector
  103. }
  104. panic(err)
  105. }
  106. func isActualErr(err error) bool {
  107. return err != nil && err != redis.Nil
  108. }