含类型精讲+接口示例+源码节选
prometheus是什么,网上已经有很多文章了,prometheus的部署和启动可以参照这个链接。prometheus为使用者提供了http接口,使用者可以使用PromQl通过get或post来从prometheus进行query。prometheus http api传送
示例:
在https://github.com/prometheus/common/blob/master/model/metric.go定义了prometheus metric的命名规则,即只能为大小写字母,数字,’_’,’:’,并且名字不能为空string,其他的字符均不可出现
func IsValidMetricName(n LabelValue) bool {
if len(n) == 0 {
return false
}
for i, b := range n {
if !((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == ':' || (b >= '0' && b <= '9' && i > 0)) {
return false
}
}
return true
}
计数器,并且只能增长和重置。例如:一个网站的总访问量,机器的运行时长
测量值,或瞬时记录值,可以增加,也可以减少。例如:一个视频的同时观看人数,当前运行的进程数
下面图片可传送:
counter.Inc(), counter.Add(123)
gauge.Set(), gauge.Inc(), gauge.Dec(), gauge.Add(123) , gauge.Sub(321)
histogram.Observer(123)//添加此数据到此histogram实例(使其观察)
type Metric interface {
//获取此metric的描述
Desc() *Desc
//转化为proto格式的Metric,返回
Write(*dto.Metric) error //dto "github.com/prometheus/client_model/go"
}
package main
//不是伪代码,可以直接go run
import (
"net/http"
"time"
"log"
"math"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
MyTestCounter = prometheus.NewCounter(prometheus.CounterOpts{
//因为Name不可以重复,所以建议规则为:"部门名_业务名_模块名_标量名_类型"
Name: "my_test_counter", //唯一id,不可重复Register(),可以Unregister()
Help: "my test counter", //对此Counter的描述
})
MyTestGauge = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "my_test_gauge",
Help: "my test gauge",
})
MyTestHistogram = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "my_test_histogram",
Help: "my test histogram",
Buckets: prometheus.LinearBuckets(20, 5, 5), //第一个桶20起,每个桶间隔5,共5个桶。 所以20, 25, 30, 35, 40
})
MyTestSummary = prometheus.NewSummary(prometheus.SummaryOpts{
Name: "my_test_summary",
Help: "my test summary",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, //返回五分数, 九分数, 九九分数
})
)
func main() {
//不能注册多次Name相同的Metrics
//MustRegister注册失败将直接panic(),如果想捕获error,建议使用Register()
prometheus.MustRegister(MyTestCounter)
prometheus.MustRegister(MyTestGauge)
prometheus.MustRegister(MyTestHistogram)
prometheus.MustRegister(MyTestSummary)
go func(){
var i float64
for {
i++
MyTestCounter.Add(10000) //每次加常量
MyTestGauge.Add(i) //每次加增量
MyTestHistogram.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10) //每次观察一个18 - 42的量
MyTestSummary.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
time.Sleep(time.Second)
}
}()
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil))//多个进程不可监听同一个端口
}
图像存在锯齿是因为prometheus默认每15s同步一次数据
//metricVec实现了具体结构和接口,提供给四种数组类型调用,部分接口不提供给用户
//位于github.com/prometheus/client_golang/prometheus/vec.go
type metricVec struct {
*metricMap
curry []curriedLabelValue
hashAdd func(h uint64, s string) uint64
hashAddByte func(h uint64, b byte) uint64
}
//删除匹配的labels,删除成功返回true,如果未找到则返回false,并不是error
//两者的不同:
// Delete用法: vec.Delete(Labels{"l1": "v1", "l2": "v2"})
// DeleteLabelValues用法: vec.DeleteLabelValues("v1", "v2")
//如果后者参数的顺序有问题,则返回false,而前者不会
//但是与之带来的是前者的开销要比后者大,因为前者要构建Labels映射
func (m *metricVec) DeleteLabelValues(lvs ...string) bool{}
func (m *metricVec) Delete(labels Labels) bool {}
type Observer interface {
Observe(float64)
}
//XXX需要使用Counter,Gauge,Histogram,Summary来代替
//以下接口实现于counter.go, gauge.go, histogram.go, summary.go
type XXXVec struct {
*metricVec
}
//将返回数组实例,如 NewCounterVec,将返回一个 *CounterVec,
//注意,调用时,opts 中, Histogtam的Budket不能有"le", Summary的quantile不能有"quantile",否则painc()
func NewXXXVec(opts XXXOpts, labelNames []string) *XXXVec{}
//如果CounterVec则 TTT为Counter,GaugeVec则TTT为Gauge,Histogram和Summary则TTT为Observer
//获取Counter,Gauge,Histogram或Summary,如果存在则返回,不存在则创建,如果name相同,描述不同,则返回error。
//用法:
// vec.GetMetricWith(Labels{"l1": "v1", "l2": "v2"})
// vec.GetMetricWithLabelValues("v1", "v2")
//很容易因为顺序问题而导致错误或获取不到,所以建议使用前者,但与之带来的是前者会有额外消耗
//如果我们只想获取,如果获取不到不创建新的的话,那么是做不到的,不过我们不保存返回的实例就好了。如果考虑到消耗,也可以使用Delete来移除它
func (v *XXXVec) GetMetricWith(labels Labels) (TTT, error){}
func (v *XXXVec) GetMetricWithLabelValues(lvs ...string)(TTT, error){}
//分别为GetMetricWith和GetMetricWithLabelValues的must形式
//即如果出错则panic(),不会返回error
//不建议使用must形式,因为觉得我们自己处理error的能力还是要有的,即使我们捕捉到error之后和它做相同的事
func (v *XXXVec) WithLabelValues(lvs ...string) TTT{}
func (v *XXXVec) With(labels Labels) TTT{}
//CurryWith将根据传入的labels,进行匹配,返回xxxVec形式,xxxVec并不是数组类型!
//作用为了返回子xxxVec
//注意,不要对返回值进行for range,因为不是数组,并且里面的值和方法并不是公开的。
//可能的使用情况:
// TestHistogramVec := NewHistogramVec(HistogramVecOpts{
// Name : "test_name",
// Help : "test_help",
// Buckets: prometheus.LinearBuckets(20, 5, 5),
// },[]string{"color","size","weight"})
// TestHistogramVecSon := CurryWith("color":"black")
func (v *XXXVec) CurryWith(labels Labels) (TTTVec, error){}
//CurryWith的Must形式,出现异常直接panic()
func (v *XXXVec) MustCurryWith(labels Labels) TTTVec{}
package main
import (
"net/http"
"time"
"log"
"math"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
MyTestHistogramVec = prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: "human_weight_histogram",
Help: "human weight histogram",
Buckets: prometheus.LinearBuckets(1, 10, 15), //第一个桶1起,每个桶间隔10, 共15个桶。 所以1,11,21,31,...,141
},[]string{"sex","age","race"},)
)
func main() {
prometheus.MustRegister(MyTestHistogramVec)
go func(){
var i float64
for i < 20 {
//不要太在意赋的什么值了,随便写的,主要为了了解用法
MyTestHistogramVec.With(prometheus.Labels{"sex":"man","age":"20","race":"black"}).Observe(90 + math.Floor(400*math.Sin(float64(i*127)*0.1))/10)
MyTestHistogramVec.With(prometheus.Labels{"sex":"woman","age":"20","race":"black"}).Observe(70 + math.Floor(400*math.Sin(float64(i*127)*0.1))/10)
MyTestHistogramVec.With(prometheus.Labels{"sex":"man","age":"25","race":"black"}).Observe(95 + math.Floor(400*math.Sin(float64(i*127)*0.1))/10)
MyTestHistogramVec.With(prometheus.Labels{"sex":"woman","age":"25","race":"black"}).Observe(95 + math.Floor(400*math.Sin(float64(i*127)*0.1))/10)
MyTestHistogramVec.With(prometheus.Labels{"sex":"man","age":"20","race":"yellow"}).Observe(90 + math.Floor(400*math.Sin(float64(i*127)*0.1))/10)
time.Sleep(time.Second)
i++
}
}()
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe("0.0.0.0:8080", nil)
}
如果你是一个懒人的话,不想去手动Registor()的话,promauto提供了这种方法。
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
//这时候你就不需要去调用带Registor字样的方法了。Unregistor除外!
//但是因为promauto调用的是MustRegistor(xxx),所以如果注册出现问题会直接panic()
var histogram = promauto.NewHistogram(prometheus.HistogramOpts{
Name: "random_numbers",
Help: "A histogram of normally distributed random numbers.",
Buckets: prometheus.LinearBuckets(-3, .1, 61),
})
//看两个promauto的实现
func NewCounterFunc(opts prometheus.CounterOpts, function func() float64) prometheus.CounterFunc {
g := prometheus.NewCounterFunc(opts, function)
prometheus.MustRegister(g)
return g
}
func NewSummary(opts prometheus.SummaryOpts) prometheus.Summary {
s := prometheus.NewSummary(opts)
prometheus.MustRegister(s)
return s
}
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {
h := prometheus.NewHistogramVec(opts, labelNames)
prometheus.MustRegister(h)
return h
}
原文链接
//原文就这么多
package prometheus
import "time"
// Timer is a helper type to time functions. Use NewTimer to create new
// instances.
type Timer struct {
begin time.Time
observer Observer
}
// 通常使用这种形式来Observe一个函数的运行时间
// 已测试,非常好用
// func TimeMe() {
// timer := NewTimer(myHistogram)
// defer timer.ObserveDuration()
// // Do actual work.
// }
func NewTimer(o Observer) *Timer {
return &Timer{
begin: time.Now(),
observer: o,
}
}
func (t *Timer) ObserveDuration() time.Duration {
d := time.Since(t.begin)
if t.observer != nil {
t.observer.Observe(d.Seconds())
}
return d
}
https://godoc.org/github.com/prometheus/client_golang/prometheus
https://ryanyang.gitbook.io/prometheus/
https://s0prometheus0io.icopy.site/docs/introduction/overview/