阅读 463

聊聊gorm的prometheus

本文主要研究一下gorm的prometheus

Plugin

gorm.io/gorm@v1.20.10/interfaces.go

// Plugin GORM plugin interface
type Plugin interface {
	Name() string
	Initialize(*DB) error
}
复制代码

Plugin接口定义了Name、Initialize方法

Prometheus

gorm.io/plugin/prometheus@v0.0.0-20201023060415-b0e68fc269af/prometheus.go

type Prometheus struct {
	*gorm.DB
	*DBStats
	*Config
	refreshOnce, pushOnce sync.Once
	Labels                map[string]string
	collectors            []prometheus.Collector
}

func (p *Prometheus) Name() string {
	return "gorm:prometheus"
}

func (p *Prometheus) Initialize(db *gorm.DB) error { //can be called repeatedly
	p.DB = db

	if p.Config.DBName != "" {
		p.Labels["db_name"] = p.Config.DBName
	}

	p.DBStats = newStats(p.Labels)

	p.refreshOnce.Do(func() {
		for _, mc := range p.MetricsCollector {
			p.collectors = append(p.collectors, mc.Metrics(p)...)
		}

		go func() {
			for range time.Tick(time.Duration(p.Config.RefreshInterval) * time.Second) {
				p.refresh()
			}
		}()
	})

	if p.Config.StartServer {
		go p.startServer()
	}

	if p.PushAddr != "" {
		go p.startPush()
	}

	return nil
}
复制代码

Prometheus实现了Plugin接口定义了Name、Initialize方法;Initialize方法这里执行了p.startServer()、p.startPush()以及定时任务执行p.refresh()

startServer

gorm.io/plugin/prometheus@v0.0.0-20201023060415-b0e68fc269af/prometheus.go

func (p *Prometheus) startServer() {
	httpServerOnce.Do(func() { //only start once
		mux := http.NewServeMux()
		mux.Handle("/metrics", promhttp.Handler())
		err := http.ListenAndServe(fmt.Sprintf(":%d", p.Config.HTTPServerPort), mux)
		if err != nil {
			p.DB.Logger.Error(context.Background(), "gorm:prometheus listen and serve err: ", err)
		}
	})
}
复制代码

startServer启动了http server提供了/metrics接口

startPush

gorm.io/plugin/prometheus@v0.0.0-20201023060415-b0e68fc269af/prometheus.go

func (p *Prometheus) startPush() {
	p.pushOnce.Do(func() {
		pusher := push.New(p.PushAddr, p.DBName)

		for _, collector := range p.DBStats.Collectors() {
			pusher = pusher.Collector(collector)
		}

		for _, c := range p.collectors {
			pusher = pusher.Collector(c)
		}

		for range time.Tick(time.Duration(p.Config.RefreshInterval) * time.Second) {
			err := pusher.Push()
			if err != nil {
				p.DB.Logger.Error(context.Background(), "gorm:prometheus push err: ", err)
			}
		}
	})
}
复制代码

startPush方法启动定时任务去执行pusher.Push()

refresh

gorm.io/plugin/prometheus@v0.0.0-20201023060415-b0e68fc269af/prometheus.go

func (p *Prometheus) refresh() {
	if db, err := p.DB.DB(); err == nil {
		p.DBStats.Set(db.Stats())
	} else {
		p.DB.Logger.Error(context.Background(), "gorm:prometheus failed to collect db status, got error: %v", err)
	}
}
复制代码

refresh方法主要是更新p.DBStats

Use

gorm.io/gorm@v1.20.10/gorm.go

func (db *DB) Use(plugin Plugin) (err error) {
	name := plugin.Name()
	if _, ok := db.Plugins[name]; !ok {
		if err = plugin.Initialize(db); err == nil {
			db.Plugins[name] = plugin
		}
	} else {
		return ErrRegistered
	}

	return err
}
复制代码

gorm的Use方法用于启动一个plugin,它会执行plugin.Initialize(db)

实例

	db.Use(prometheus.New(prometheus.Config{
		DBName:          "db1", // 使用 `DBName` 作为指标 label
		RefreshInterval: 15,    // 指标刷新频率(默认为 15 秒)
		PushAddr:        "prometheus pusher address", // 如果配置了 `PushAddr`,则推送指标
		StartServer:     true,  // 启用一个 http 服务来暴露指标
		HTTPServerPort:  8080,  // 配置 http 服务监听端口,默认端口为 8080 (如果您配置了多个,只有第一个 `HTTPServerPort` 会被使用)
		MetricsCollector: []prometheus.MetricsCollector {
			&prometheus.MySQL{
				VariableNames: []string{"Threads_running"},
			},
		},  // 用户自定义指标
	}))
复制代码

小结

gorm的Plugin接口定义了Name、Initialize方法;gorm的Use方法用于启动一个plugin,它会执行plugin.Initialize(db);Prometheus实现了Plugin接口定义了Name、Initialize方法;Initialize方法这里执行了p.startServer()、p.startPush()以及定时任务执行p.refresh()。

doc