package metrics import ( "encoding/json" "errors" "fmt" "github.com/MAN00K/unitech_golib/tlog" "net/http" "os" "strconv" "time" consulapi "github.com/hashicorp/consul/api" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/push" ) const ( defaultAddr string = "9166" ) // Counter describes a metric that accumulates values monotonically. // An example of a counter is the number of received HTTP requests. type Counter interface { With(labelValues ...string) Counter Add(delta float64) Inc() } // Gauge describes a metric that takes specific values over time. // An example of a gauge is the current depth of a job queue. type Gauge interface { With(labelValues ...string) Gauge Set(value float64) Add(delta float64) Sub(delta float64) } // Histogram describes a metric that takes repeated observations of the same // kind of thing, and produces a statistical summary of those observations, // typically expressed as quantiles or buckets. An example of a histogram is // HTTP request latencies. type Histogram interface { With(labelValues ...string) Histogram Observe(value float64) } // LabelValues is a type alias that provides validation on its With method. // Metrics may include it as a member to help them satisfy With semantics and // save some code duplication. type LabelValues []string // With validates the input, and returns a new aggregate labelValues. func (lvs LabelValues) With(labelValues ...string) LabelValues { if len(labelValues)%2 != 0 { labelValues = append(labelValues, "unknown") } return append(lvs, labelValues...) } // new a *ProcessMetrics // productName,serviceRegion,appName all those params must be passed // port if not be pass,will use defaultAddr 9166 func NewProcessMetrics(productName, serviceRegion, appName string, port ...string) (*ProcessMetrics, error) { if len(productName) == 0 || len(serviceRegion) == 0 || len(appName) == 0 { err := errors.New("productName,serviceRegion,appName all those params must be passed") tlog.Error(err) return nil, err } addr := defaultAddr if len(port) > 0 { addr = port[0] } var processMetrics ProcessMetrics processMetrics.Port = addr processMetrics.ProductName = productName processMetrics.ServiceRegion = serviceRegion processMetrics.Appname = appName return &processMetrics, nil } type ProcessMetrics struct { Port string // port default 9166 ProductName string ServiceRegion string Appname string } // get real work port func (s *ProcessMetrics) GetMetricsPort() string { if len(s.Port) > 0 { return s.Port } return defaultAddr } // Init Export on GF default port 9166 // If you want custom listen port, just pass your port to MetricsParams struct // return exporter http server func (s *ProcessMetrics) InitExporter() *http.Server { h := http.NewServeMux() h.Handle("/metrics", promhttp.Handler()) h.HandleFunc("/health_checker", s.healthChecker) addr := defaultAddr if len(s.Port) > 0 { addr = s.Port } else { s.Port = defaultAddr } server := &http.Server{ Addr: ":" + addr, Handler: h, ReadTimeout: 30 * time.Second, ReadHeaderTimeout: 30 * time.Second, IdleTimeout: 30 * time.Second, WriteTimeout: 10 * time.Second, } { var tags []string tags = append(tags, fmt.Sprintf("app=%s", s.ProductName)) tags = append(tags, fmt.Sprintf("country=%s", s.ServiceRegion)) s.initExporterOnExistHttpServer(addr, "/health_checker", tags, s.Appname) } go func() { if err := server.ListenAndServe(); err != nil { tlog.Fatal(err) } }() return server } func (s *ProcessMetrics) healthChecker(resp http.ResponseWriter, req *http.Request) { data := map[string]interface{}{ "code": 0, "message": "ok", "server_time": time.Now().Unix(), } res, _ := json.Marshal(data) resp.Write(res) } // Init Pusher // return prometheus pusher func InitPusher(gateway string, job string) *push.Pusher { host, _ := os.Hostname() return push.New(gateway, job).Grouping("instance", host) } // InitExporterOnExistHttpServer Init http exporter // Why we init exporter on exist http server: // to avoid new custom port // which should add healthCheckRouter - like /healthChecker func // must export a route: /metrics router for prometheus // promhttp.Handler().ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request) func (s *ProcessMetrics) initExporterOnExistHttpServer(port string, healthCheckRouter string, tags []string, name string, id ...string) { config := consulapi.DefaultConfig() client, err := consulapi.NewClient(config) if err != nil { tlog.Error("consul client error : ", err) return } registration := new(consulapi.AgentServiceRegistration) registration.Name = name intPort, _ := strconv.Atoi(port) registration.Port = intPort hostName, _ := os.Hostname() if len(id) > 0 { registration.ID = registration.Name + ":" + id[0] } else { registration.ID = registration.Name + ":" + hostName } registration.Tags = append(tags, "lang=go") registration.Check = new(consulapi.AgentServiceCheck) registration.Check.HTTP = fmt.Sprintf("http://%s:%s%s", "127.0.0.1", port, healthCheckRouter) registration.Check.Interval = "5s" registration.Check.Timeout = "3s" // 为兼容现有的 平滑逻辑 //err = client.Agent().ServiceDeregister(registration.ID) err = client.Agent().ServiceRegister(registration) if err != nil { tlog.Error(err) } }