Initial commit

This commit is contained in:
Cruise Zhao
2020-04-06 19:57:19 +08:00
commit 2fc86fd0a1
42 changed files with 4056 additions and 0 deletions

14
metric/alert.go Normal file
View 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() {
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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()
}
}
}