github.com/Jeffail/benthos/v3@v3.65.0/lib/metrics/http.go (about)

     1  package metrics
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"runtime"
     8  	"time"
     9  
    10  	"github.com/Jeffail/benthos/v3/internal/docs"
    11  	"github.com/Jeffail/benthos/v3/lib/log"
    12  	"github.com/Jeffail/gabs/v2"
    13  )
    14  
    15  func init() {
    16  	Constructors[TypeHTTPServer] = TypeSpec{
    17  		constructor: NewHTTP,
    18  		Summary: `
    19  Serves metrics as [JSON object](#object-format) with the service wide HTTP
    20  service at the endpoints ` + "`/stats` and `/metrics`" + `.`,
    21  		Description: `
    22  This metrics type is useful for debugging as it provides a human readable format
    23  that you can parse with tools such as ` + "`jq`" + ``,
    24  		Footnotes: `
    25  ## Object Format
    26  
    27  The metrics object takes the form of a hierarchical representation of the dot
    28  paths for each metric combined. So, for example, if Benthos exposed two metric
    29  counters ` + "`foo.bar` and `bar.baz`" + ` then the resulting object might look
    30  like this:
    31  
    32  ` + "``` json" + `
    33  {
    34  	"foo": {
    35  		"bar": 9
    36  	},
    37  	"bar": {
    38  		"baz": 3
    39  	}
    40  }
    41  ` + "```" + ``,
    42  		FieldSpecs: docs.FieldSpecs{
    43  			docs.FieldCommon("prefix", "A string prefix to add to all metrics."),
    44  			pathMappingDocs(false, false),
    45  		},
    46  	}
    47  }
    48  
    49  //------------------------------------------------------------------------------
    50  
    51  // Errors for the HTTP type.
    52  var (
    53  	ErrTimedOut = errors.New("timed out")
    54  )
    55  
    56  //------------------------------------------------------------------------------
    57  
    58  // HTTPConfig contains configuration parameters for the HTTP metrics aggregator.
    59  type HTTPConfig struct {
    60  	Prefix      string `json:"prefix" yaml:"prefix"`
    61  	PathMapping string `json:"path_mapping" yaml:"path_mapping"`
    62  }
    63  
    64  // NewHTTPConfig returns a new HTTPConfig with default values.
    65  func NewHTTPConfig() HTTPConfig {
    66  	return HTTPConfig{
    67  		Prefix:      "benthos",
    68  		PathMapping: "",
    69  	}
    70  }
    71  
    72  //------------------------------------------------------------------------------
    73  
    74  // HTTP is an object with capability to hold internal stats as a JSON endpoint.
    75  type HTTP struct {
    76  	local       *Local
    77  	log         log.Modular
    78  	timestamp   time.Time
    79  	pathPrefix  string
    80  	pathMapping *pathMapping
    81  }
    82  
    83  // NewHTTP creates and returns a new HTTP object.
    84  func NewHTTP(config Config, opts ...func(Type)) (Type, error) {
    85  	t := &HTTP{
    86  		local:      NewLocal(),
    87  		timestamp:  time.Now(),
    88  		pathPrefix: config.HTTP.Prefix,
    89  	}
    90  	for _, opt := range opts {
    91  		opt(t)
    92  	}
    93  	var err error
    94  	if t.pathMapping, err = newPathMapping(config.HTTP.PathMapping, t.log); err != nil {
    95  		return nil, fmt.Errorf("failed to init path mapping: %v", err)
    96  	}
    97  	return t, nil
    98  }
    99  
   100  //------------------------------------------------------------------------------
   101  
   102  func (h *HTTP) getPath(path string) string {
   103  	path = h.pathMapping.mapPathNoTags(path)
   104  	if len(h.pathPrefix) > 0 && len(path) > 0 {
   105  		path = h.pathPrefix + "." + path
   106  	}
   107  	return path
   108  }
   109  
   110  // HandlerFunc returns an http.HandlerFunc for accessing metrics as a JSON blob.
   111  func (h *HTTP) HandlerFunc() http.HandlerFunc {
   112  	return func(w http.ResponseWriter, r *http.Request) {
   113  		uptime := time.Since(h.timestamp).String()
   114  		goroutines := runtime.NumGoroutine()
   115  
   116  		counters := h.local.GetCounters()
   117  		timings := h.local.GetTimings()
   118  
   119  		obj := gabs.New()
   120  		for k, v := range counters {
   121  			obj.SetP(v, k)
   122  		}
   123  		for k, v := range timings {
   124  			obj.SetP(v, k)
   125  			obj.SetP(time.Duration(v).String(), k+"_readable")
   126  		}
   127  		obj.SetP(uptime, "uptime")
   128  		obj.SetP(goroutines, "goroutines")
   129  
   130  		w.Header().Set("Content-Type", "application/json")
   131  		w.Write(obj.Bytes())
   132  	}
   133  }
   134  
   135  // GetCounter returns a stat counter object for a path.
   136  func (h *HTTP) GetCounter(path string) StatCounter {
   137  	if path = h.getPath(path); path == "" {
   138  		return DudStat{}
   139  	}
   140  	return h.local.GetCounter(path)
   141  }
   142  
   143  // GetCounterVec returns a stat counter object for a path with the labels
   144  // discarded.
   145  func (h *HTTP) GetCounterVec(path string, n []string) StatCounterVec {
   146  	if path = h.getPath(path); path == "" {
   147  		return fakeCounterVec(func([]string) StatCounter {
   148  			return DudStat{}
   149  		})
   150  	}
   151  	return fakeCounterVec(func([]string) StatCounter {
   152  		return h.local.GetCounter(path)
   153  	})
   154  }
   155  
   156  // GetTimer returns a stat timer object for a path.
   157  func (h *HTTP) GetTimer(path string) StatTimer {
   158  	if path = h.getPath(path); path == "" {
   159  		return DudStat{}
   160  	}
   161  	return h.local.GetTimer(path)
   162  }
   163  
   164  // GetTimerVec returns a stat timer object for a path with the labels
   165  // discarded.
   166  func (h *HTTP) GetTimerVec(path string, n []string) StatTimerVec {
   167  	if path = h.getPath(path); path == "" {
   168  		return fakeTimerVec(func([]string) StatTimer {
   169  			return DudStat{}
   170  		})
   171  	}
   172  	return fakeTimerVec(func([]string) StatTimer {
   173  		return h.local.GetTimer(path)
   174  	})
   175  }
   176  
   177  // GetGauge returns a stat gauge object for a path.
   178  func (h *HTTP) GetGauge(path string) StatGauge {
   179  	if path = h.getPath(path); path == "" {
   180  		return DudStat{}
   181  	}
   182  	return h.local.GetGauge(path)
   183  }
   184  
   185  // GetGaugeVec returns a stat timer object for a path with the labels
   186  // discarded.
   187  func (h *HTTP) GetGaugeVec(path string, n []string) StatGaugeVec {
   188  	if path = h.getPath(path); path == "" {
   189  		return fakeGaugeVec(func([]string) StatGauge {
   190  			return DudStat{}
   191  		})
   192  	}
   193  	return fakeGaugeVec(func([]string) StatGauge {
   194  		return h.local.GetGauge(path)
   195  	})
   196  }
   197  
   198  // SetLogger does nothing.
   199  func (h *HTTP) SetLogger(log log.Modular) {
   200  	h.log = log
   201  }
   202  
   203  // Close stops the HTTP object from aggregating metrics and cleans up resources.
   204  func (h *HTTP) Close() error {
   205  	return nil
   206  }
   207  
   208  //------------------------------------------------------------------------------