Initial commit
This commit is contained in:
14
metric/alert.go
Normal file
14
metric/alert.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var singletonCounter prometheus.Counter
|
||||
var once sync.Once
|
||||
|
||||
func SingletonCounter() {
|
||||
|
||||
}
|
||||
25
metric/examples/exporter_demo.go
Normal file
25
metric/examples/exporter_demo.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"globalfintech/golib/metric"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := metric.ExporterCfg{
|
||||
Listener: nil,
|
||||
RegisterConsul: false,
|
||||
ProductName: "ProductName_001",
|
||||
ServiceRegion: "ServiceRegion_001",
|
||||
AppName: "AppName_001",
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := metric.InitExporter(cfg); err != nil {
|
||||
fmt.Println("prometheus exporter stopped,err: ", err)
|
||||
}
|
||||
}()
|
||||
fmt.Println("prometheus exporter started")
|
||||
|
||||
select {}
|
||||
}
|
||||
46
metric/examples/main.go
Normal file
46
metric/examples/main.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"globalfintech/golib/metric"
|
||||
"time"
|
||||
)
|
||||
|
||||
var callCounter = metric.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "crm",
|
||||
Name: "call_total_count2",
|
||||
Help: "Total Request",
|
||||
},
|
||||
[]string{"privilege", "uri"},
|
||||
)
|
||||
|
||||
func test2() {
|
||||
pusher := metric.InitPusher("http://ops-dev.a.mobimagic.com:9091", "MetricsExample3", prometheus.Labels{})
|
||||
|
||||
pusher.Collector(callCounter).Add()
|
||||
pusher.Push()
|
||||
//pusher.Push(callCounter)
|
||||
}
|
||||
|
||||
func main() {
|
||||
//test2()
|
||||
|
||||
var labels = prometheus.Labels{"app": "app2",
|
||||
"country": "INDIA",
|
||||
"service": "test2",
|
||||
}
|
||||
|
||||
var pushGateway, job = "http://ops-dev.a.mobimagic.com:9091", "app2"
|
||||
|
||||
//fmt.Println("labels:", labels)
|
||||
metric.InitDefaultPusher(pushGateway, job, labels)
|
||||
|
||||
for {
|
||||
callCounter.With(prometheus.Labels{"privilege": "test", "uri": "xxxxx/sswwe"}).Inc()
|
||||
time.Sleep(time.Second * 1)
|
||||
fmt.Println("Inc 1")
|
||||
|
||||
}
|
||||
}
|
||||
113
metric/exporter.go
Normal file
113
metric/exporter.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
consulapi "github.com/hashicorp/consul/api"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"globalfintech/golib/tlog"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultListenerAddr = "0.0.0.0:9166"
|
||||
)
|
||||
|
||||
type ExporterCfg struct {
|
||||
Listener net.Listener
|
||||
RegisterConsul bool
|
||||
ProductName string
|
||||
ServiceRegion string
|
||||
AppName string
|
||||
}
|
||||
|
||||
// Exporter init function must be executed asynchronously
|
||||
func InitExporter(cfg ExporterCfg) (err error) {
|
||||
lis := cfg.Listener
|
||||
if cfg.Listener == nil {
|
||||
if lis, err = net.Listen("tcp", DefaultListenerAddr); err != nil {
|
||||
tlog.Error("fail to create listener with default address")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(lis.Addr().String())
|
||||
if err != nil {
|
||||
tlog.Error("fail to get port from listener, err: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
h := http.NewServeMux()
|
||||
h.Handle("/metrics", promhttp.Handler())
|
||||
h.HandleFunc("/health_checker", healthChecker)
|
||||
server := &http.Server{
|
||||
Handler: h,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
ReadHeaderTimeout: 30 * time.Second,
|
||||
IdleTimeout: 30 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
if cfg.RegisterConsul {
|
||||
var tags []string
|
||||
tags = append(tags, fmt.Sprintf("app=%s", cfg.ProductName))
|
||||
tags = append(tags, fmt.Sprintf("country=%s", cfg.ServiceRegion))
|
||||
err = consulRegister(port, "", tags, cfg.AppName)
|
||||
if err != nil {
|
||||
tlog.Error("consul register failed, err: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = server.Serve(lis); err != nil {
|
||||
tlog.Error("fail to init prometheus exporter, err: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func consulRegister(checkPort string, regAddr string, tags []string, name string) (err error) {
|
||||
config := consulapi.DefaultConfig()
|
||||
if regAddr != "" {
|
||||
config.Address = regAddr
|
||||
}
|
||||
|
||||
client, err := consulapi.NewClient(config)
|
||||
if err != nil {
|
||||
tlog.Error("fail to new consul client, err: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
intPort, _ := strconv.Atoi(checkPort)
|
||||
hostName, _ := os.Hostname()
|
||||
|
||||
registration := &consulapi.AgentServiceRegistration{
|
||||
Port: intPort,
|
||||
Name: name,
|
||||
ID: name + ":" + hostName,
|
||||
Tags: append(tags, "lang=go"),
|
||||
Check: &consulapi.AgentServiceCheck{
|
||||
HTTP: fmt.Sprintf("http://%s:%s%s", "127.0.0.1", checkPort, "/health_checker"),
|
||||
Interval: "10s",
|
||||
Timeout: "10s",
|
||||
},
|
||||
}
|
||||
|
||||
return client.Agent().ServiceRegister(registration)
|
||||
}
|
||||
|
||||
func 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)
|
||||
}
|
||||
160
metric/metric.go
Normal file
160
metric/metric.go
Normal file
@@ -0,0 +1,160 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const missVal = "MissLabel"
|
||||
|
||||
// CounterVecDecorator Add safe decorator
|
||||
type CounterVecDecorator struct {
|
||||
*prometheus.CounterVec
|
||||
secureLabels []string
|
||||
once *sync.Once
|
||||
}
|
||||
|
||||
func NewCounterVec(opts prometheus.CounterOpts, labelNames []string) *CounterVecDecorator {
|
||||
v := &CounterVecDecorator{prometheus.NewCounterVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// With is secure wrap for with of prometheus.CounterVec
|
||||
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||
// It's secure
|
||||
func (v *CounterVecDecorator) With(labels prometheus.Labels) prometheus.Counter {
|
||||
// To avoid initialization sequence, so must delay register to with func
|
||||
v.once.Do(func() {
|
||||
Register(v)
|
||||
})
|
||||
|
||||
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||
|
||||
//
|
||||
c, err := v.GetMetricWith(safeLabels)
|
||||
|
||||
if err != nil {
|
||||
// should never happen , but should still add handler to record alert
|
||||
// TODO singleton alert Gauge and return an alert counter
|
||||
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// GaugeVecDecorator Add safe decorator
|
||||
type GaugeVecDecorator struct {
|
||||
*prometheus.GaugeVec
|
||||
secureLabels []string
|
||||
once *sync.Once
|
||||
}
|
||||
|
||||
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *GaugeVecDecorator {
|
||||
v := &GaugeVecDecorator{prometheus.NewGaugeVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// With is secure wrap for with of prometheus.GaugeVec
|
||||
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||
// It's secure
|
||||
func (v *GaugeVecDecorator) With(labels prometheus.Labels) prometheus.Gauge {
|
||||
// To avoid initialization sequence, so must delay register to with func
|
||||
v.once.Do(func() {
|
||||
Register(v)
|
||||
})
|
||||
|
||||
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||
//
|
||||
c, err := v.GetMetricWith(safeLabels)
|
||||
|
||||
if err != nil {
|
||||
// should never happen , but should still add handler to record alert
|
||||
// TODO singleton alert Gauge and return
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// HistogramVecDecorator Add safe
|
||||
type HistogramVecDecorator struct {
|
||||
*prometheus.HistogramVec
|
||||
secureLabels []string
|
||||
once *sync.Once
|
||||
}
|
||||
|
||||
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *HistogramVecDecorator {
|
||||
v := &HistogramVecDecorator{prometheus.NewHistogramVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// With is secure wrap for with of prometheus.HistogramVec
|
||||
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||
// It's secure
|
||||
func (v *HistogramVecDecorator) With(labels prometheus.Labels) prometheus.Observer {
|
||||
// To avoid initialization sequence, so must delay register to with func
|
||||
v.once.Do(func() {
|
||||
Register(v)
|
||||
})
|
||||
|
||||
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||
//
|
||||
c, err := v.GetMetricWith(safeLabels)
|
||||
|
||||
if err != nil {
|
||||
// should never happen , but should still add handler to record alert
|
||||
// TODO singleton alert Histogram and return
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// SummaryVecDecorator Add safe
|
||||
type SummaryVecDecorator struct {
|
||||
*prometheus.SummaryVec
|
||||
secureLabels []string
|
||||
once *sync.Once
|
||||
}
|
||||
|
||||
func NewSummaryVec(opts prometheus.SummaryOpts, labelNames []string) *SummaryVecDecorator {
|
||||
v := &SummaryVecDecorator{prometheus.NewSummaryVec(opts, labelNames), labelNames, &sync.Once{}}
|
||||
//if len(autoReg) == 0 || autoReg[0] == true {
|
||||
// Register(v)
|
||||
//}
|
||||
return v
|
||||
}
|
||||
|
||||
// With is secure wrap for with of prometheus.SummaryVec
|
||||
// there is no panic, and always calculate to metrics , if miss values, will calculate to MissLabel
|
||||
// It's secure
|
||||
func (v *SummaryVecDecorator) With(labels prometheus.Labels) prometheus.Observer {
|
||||
// To avoid initialization sequence, so must delay register to with func
|
||||
v.once.Do(func() {
|
||||
Register(v)
|
||||
})
|
||||
|
||||
safeLabels := labelAlignment(v.secureLabels, labels)
|
||||
//
|
||||
c, err := v.GetMetricWith(safeLabels)
|
||||
|
||||
if err != nil {
|
||||
// should never happen , but should still add handler to record alert
|
||||
// TODO singleton alert Gauge and return
|
||||
}
|
||||
//prometheus.NewCounter(v.SummaryVec.)
|
||||
return c
|
||||
}
|
||||
|
||||
// 标签对齐
|
||||
func labelAlignment(labelNames []string, labels prometheus.Labels) prometheus.Labels {
|
||||
|
||||
newLabels := make(prometheus.Labels, len(labelNames))
|
||||
|
||||
for _, val := range labelNames {
|
||||
if v, ok := labels[val]; ok {
|
||||
newLabels[val] = v
|
||||
} else {
|
||||
newLabels[val] = missVal
|
||||
}
|
||||
}
|
||||
return newLabels
|
||||
}
|
||||
48
metric/metric_test.go
Normal file
48
metric/metric_test.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_labelAlignment(t *testing.T) {
|
||||
tds := []struct {
|
||||
in1 []string
|
||||
in2 prometheus.Labels
|
||||
in2New prometheus.Labels
|
||||
}{
|
||||
{
|
||||
[]string{"label1", "label2"},
|
||||
map[string]string{"label1": "1"},
|
||||
map[string]string{"label1": "1", "label2": missVal},
|
||||
},
|
||||
{
|
||||
[]string{"label1", "label3"},
|
||||
map[string]string{"label1": "1", "label2": "2"},
|
||||
map[string]string{"label1": "1", "label3": missVal},
|
||||
},
|
||||
{
|
||||
[]string{},
|
||||
map[string]string{"label1": "1", "label2": "2"},
|
||||
map[string]string{},
|
||||
},
|
||||
{
|
||||
[]string{"label1", "label3", "label4"},
|
||||
map[string]string{"label1": "1", "label2": "2"},
|
||||
map[string]string{"label1": "1", "label3": missVal, "label4": missVal},
|
||||
},
|
||||
}
|
||||
for _, d := range tds {
|
||||
//var org map[string]string
|
||||
//copy(org, d.in2)
|
||||
result := labelAlignment(d.in1, d.in2)
|
||||
t.Log(result, d.in2New)
|
||||
|
||||
if !reflect.DeepEqual(result, d.in2New) {
|
||||
|
||||
t.Errorf("Input: %#v, Expected is not same as , get ", d)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
69
metric/push.go
Normal file
69
metric/push.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/push"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultPusher *push.Pusher
|
||||
|
||||
const (
|
||||
defaultPushIntervalSecond = 5
|
||||
)
|
||||
|
||||
func InitDefaultPusher(gateway string, job string, labels prometheus.Labels, pushIntervalSecond ...int) {
|
||||
defaultPusher = InitPusher(gateway, job, labels)
|
||||
|
||||
// for default pusher , we should add process collector and go collector
|
||||
// then we can monitor all base monitor on our job
|
||||
defaultPusher.
|
||||
Collector(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})).
|
||||
Collector(prometheus.NewGoCollector()).
|
||||
Add()
|
||||
|
||||
// TODO implement init
|
||||
// and push
|
||||
//defaultPusher = InitPusher("")
|
||||
go func() {
|
||||
var pushInterval = defaultPushIntervalSecond * time.Second
|
||||
if len(pushIntervalSecond) > 0 {
|
||||
pushInterval = time.Second * time.Duration(pushIntervalSecond[0])
|
||||
}
|
||||
for {
|
||||
defaultPusher.Push()
|
||||
time.Sleep(pushInterval)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// ExitTriggerLastPush
|
||||
// TODO
|
||||
func ExitTriggerLastPush() {
|
||||
|
||||
defaultPusher.Push()
|
||||
|
||||
}
|
||||
|
||||
//var MustLabel prometheus.Labels
|
||||
//
|
||||
//type RequireLabelConf struct {
|
||||
// Service string `json:"service";yaml:`
|
||||
// App string ``
|
||||
// Country string
|
||||
//}
|
||||
|
||||
// Init Pusher
|
||||
// return prometheus pusher
|
||||
// TODO add labels
|
||||
// need lan ,app,country, service labels to implement
|
||||
func InitPusher(gateway string, job string, labels prometheus.Labels) *push.Pusher {
|
||||
host, _ := os.Hostname()
|
||||
|
||||
p := push.New(gateway, job).Grouping("instance", host).Grouping("lang","go")
|
||||
for name, value := range labels {
|
||||
p.Grouping(name, value)
|
||||
}
|
||||
return p
|
||||
}
|
||||
27
metric/register.go
Normal file
27
metric/register.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package metric
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// Register Here, we must decide pusher/exporter which we use
|
||||
// it's very important, note: var/init/func init sort
|
||||
// keep, when a git rep, not only a service or job
|
||||
// collector will mixed with Push And Registered for pull
|
||||
// So register with pull and add for push collector must control in one place
|
||||
// then we can use conf to switch
|
||||
// avoid collector duplicate
|
||||
// we don't use must register, to avoid panic
|
||||
func Register(cs ...prometheus.Collector) {
|
||||
if defaultPusher == nil {
|
||||
// register by exporter
|
||||
for _, mv := range cs {
|
||||
prometheus.Register(mv)
|
||||
}
|
||||
} else {
|
||||
// register by pusher
|
||||
for _, mv := range cs {
|
||||
defaultPusher.Collector(mv).Add()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user