Files
unitech-golib/metrics/metrics.go

191 lines
5.3 KiB
Go
Raw Permalink Normal View History

2020-04-06 19:57:19 +08:00
package metrics
import (
"encoding/json"
"errors"
"fmt"
2025-07-22 14:08:24 +08:00
"github.com/MAN00K/unitech_golib/tlog"
2020-04-06 19:57:19 +08:00
"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)
}
}