191 lines
5.3 KiB
Go
191 lines
5.3 KiB
Go
package metrics
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"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)
|
|
}
|
|
}
|