api prommetrics

pull/2398/head
withchao 1 year ago
parent 97636c4c7a
commit 1336b83142

@ -14,7 +14,7 @@ require (
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/openimsdk/protocol v0.0.69-alpha.24 github.com/openimsdk/protocol v0.0.69-alpha.24
github.com/openimsdk/tools v0.0.49-alpha.39 github.com/openimsdk/tools v0.0.49-alpha.41
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
@ -176,3 +176,7 @@ require (
golang.org/x/crypto v0.21.0 // indirect golang.org/x/crypto v0.21.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
) )
//replace (
// github.com/openimsdk/tools => /Users/chao/Desktop/withchao/tools
//)

@ -272,8 +272,8 @@ github.com/openimsdk/gomake v0.0.14-alpha.5 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCF
github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
github.com/openimsdk/protocol v0.0.69-alpha.24 h1:TYcNJeWOTuE40UQ54eNPdDdy0KTOh9rAOgax8lCyhDc= github.com/openimsdk/protocol v0.0.69-alpha.24 h1:TYcNJeWOTuE40UQ54eNPdDdy0KTOh9rAOgax8lCyhDc=
github.com/openimsdk/protocol v0.0.69-alpha.24/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= github.com/openimsdk/protocol v0.0.69-alpha.24/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
github.com/openimsdk/tools v0.0.49-alpha.39 h1:bl5+q7xHsc/j1NnkN8/gYmn23RsNNbRizDY58d2EY1w= github.com/openimsdk/tools v0.0.49-alpha.41 h1:rLnwW/yYqtuonDB61U8KB/HXj0BMP4chzF0GLgJL+Uo=
github.com/openimsdk/tools v0.0.49-alpha.39/go.mod h1:zc0maZ2ohXlHd0ylY5JnCE8uqq/hslhcfcKa6iO5PCU= github.com/openimsdk/tools v0.0.49-alpha.41/go.mod h1:zc0maZ2ohXlHd0ylY5JnCE8uqq/hslhcfcKa6iO5PCU=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=

@ -29,7 +29,6 @@ import (
"time" "time"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
ginprom "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
@ -72,9 +71,9 @@ func Start(ctx context.Context, index int, config *Config) error {
netDone <- struct{}{} netDone <- struct{}{}
return return
} }
p := ginprom.NewPrometheus("app", prommetrics.GetGinCusMetrics("Api")) srv := http.NewServeMux()
p.SetListenAddress(fmt.Sprintf(":%d", prometheusPort)) srv.Handle(prommetrics.ApiPath, prommetrics.ApiHandler())
if err = p.Use(router); err != nil && err != http.ErrServerClosed { if err := http.ListenAndServe(fmt.Sprintf(":%d", prometheusPort), srv); err != nil && err != http.ErrServerClosed {
netErr = errs.WrapMsg(err, fmt.Sprintf("prometheus start err: %d", prometheusPort)) netErr = errs.WrapMsg(err, fmt.Sprintf("prometheus start err: %d", prometheusPort))
netDone <- struct{}{} netDone <- struct{}{}
} }

@ -2,8 +2,10 @@ package api
import ( import (
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"net/http" "net/http"
"strings" "strings"
"time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
@ -19,6 +21,22 @@ import (
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
func prommetricsGin() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
path := c.FullPath()
prommetrics.HttpCall(path, c.Request.Method, c.Writer.Status(), time.Since(start))
if c.Request.Method == http.MethodPost {
if resp := apiresp.GetGinApiResponse(c); resp == nil {
prommetrics.APICall(path, -1, "NO_GIN_RESPONSE_FOUND")
} else {
prommetrics.APICall(path, resp.ErrCode, resp.ErrMsg)
}
}
}
}
func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.Engine { func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.Engine {
disCov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), disCov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
@ -37,7 +55,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
authRpc := rpcclient.NewAuth(disCov, config.Share.RpcRegisterName.Auth) authRpc := rpcclient.NewAuth(disCov, config.Share.RpcRegisterName.Auth)
thirdRpc := rpcclient.NewThird(disCov, config.Share.RpcRegisterName.Third, config.API.Prometheus.GrafanaURL) thirdRpc := rpcclient.NewThird(disCov, config.Share.RpcRegisterName.Third, config.API.Prometheus.GrafanaURL)
r.Use(gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc)) r.Use(prommetricsGin(), gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc))
u := NewUserApi(*userRpc) u := NewUserApi(*userRpc)
m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID) m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
userRouterGroup := r.Group("/user") userRouterGroup := r.Group("/user")

@ -14,430 +14,431 @@
package ginprometheus package ginprometheus
import ( //
"bytes" //import (
"fmt" // "bytes"
"io" // "fmt"
"net/http" // "io"
"os" // "net/http"
"strconv" // "os"
"time" // "strconv"
// "time"
"github.com/gin-gonic/gin" //
"github.com/prometheus/client_golang/prometheus" // "github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp" // "github.com/prometheus/client_golang/prometheus"
) // "github.com/prometheus/client_golang/prometheus/promhttp"
//)
var defaultMetricPath = "/metrics" //
//var defaultMetricPath = "/metrics"
// counter, counter_vec, gauge, gauge_vec, //
// histogram, histogram_vec, summary, summary_vec. //// counter, counter_vec, gauge, gauge_vec,
var ( //// histogram, histogram_vec, summary, summary_vec.
reqCounter = &Metric{ //var (
ID: "reqCnt", // reqCounter = &Metric{
Name: "requests_total", // ID: "reqCnt",
Description: "How many HTTP requests processed, partitioned by status code and HTTP method.", // Name: "requests_total",
Type: "counter_vec", // Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
Args: []string{"code", "method", "handler", "host", "url"}} // Type: "counter_vec",
// Args: []string{"code", "method", "handler", "host", "url"}}
reqDuration = &Metric{ //
ID: "reqDur", // reqDuration = &Metric{
Name: "request_duration_seconds", // ID: "reqDur",
Description: "The HTTP request latencies in seconds.", // Name: "request_duration_seconds",
Type: "histogram_vec", // Description: "The HTTP request latencies in seconds.",
Args: []string{"code", "method", "url"}, // Type: "histogram_vec",
} // Args: []string{"code", "method", "url"},
// }
resSize = &Metric{ //
ID: "resSz", // resSize = &Metric{
Name: "response_size_bytes", // ID: "resSz",
Description: "The HTTP response sizes in bytes.", // Name: "response_size_bytes",
Type: "summary"} // Description: "The HTTP response sizes in bytes.",
// Type: "summary"}
reqSize = &Metric{ //
ID: "reqSz", // reqSize = &Metric{
Name: "request_size_bytes", // ID: "reqSz",
Description: "The HTTP request sizes in bytes.", // Name: "request_size_bytes",
Type: "summary"} // Description: "The HTTP request sizes in bytes.",
// Type: "summary"}
standardMetrics = []*Metric{ //
reqCounter, // standardMetrics = []*Metric{
reqDuration, // reqCounter,
resSize, // reqDuration,
reqSize, // resSize,
} // reqSize,
) // }
//)
/* //
RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control ///*
the cardinality of the request counter's "url" label, which might be required in some contexts. //RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control
For instance, if for a "/customer/:name" route you don't want to generate a time series for every //the cardinality of the request counter's "url" label, which might be required in some contexts.
possible customer name, you could use this function: //For instance, if for a "/customer/:name" route you don't want to generate a time series for every
//possible customer name, you could use this function:
func(c *gin.Context) string { //
url := c.Request.URL.Path // func(c *gin.Context) string {
for _, p := range c.Params { // url := c.Request.URL.Path
if p.Key == "name" { // for _, p := range c.Params {
url = strings.Replace(url, p.Value, ":name", 1) // if p.Key == "name" {
break // url = strings.Replace(url, p.Value, ":name", 1)
} // break
} // }
return url // }
} // return url
// }
which would map "/customer/alice" and "/customer/bob" to their template "/customer/:name". //
*/ //which would map "/customer/alice" and "/customer/bob" to their template "/customer/:name".
type RequestCounterURLLabelMappingFn func(c *gin.Context) string //*/
//type RequestCounterURLLabelMappingFn func(c *gin.Context) string
// Metric is a definition for the name, description, type, ID, and //
// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric. //// Metric is a definition for the name, description, type, ID, and
type Metric struct { //// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric.
MetricCollector prometheus.Collector //type Metric struct {
ID string // MetricCollector prometheus.Collector
Name string // ID string
Description string // Name string
Type string // Description string
Args []string // Type string
} // Args []string
//}
// Prometheus contains the metrics gathered by the instance and its path. //
type Prometheus struct { //// Prometheus contains the metrics gathered by the instance and its path.
reqCnt *prometheus.CounterVec //type Prometheus struct {
reqDur *prometheus.HistogramVec // reqCnt *prometheus.CounterVec
reqSz, resSz prometheus.Summary // reqDur *prometheus.HistogramVec
router *gin.Engine // reqSz, resSz prometheus.Summary
listenAddress string // router *gin.Engine
Ppg PrometheusPushGateway // listenAddress string
// Ppg PrometheusPushGateway
MetricsList []*Metric //
MetricsPath string // MetricsList []*Metric
// MetricsPath string
ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn //
// ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn
// gin.Context string to use as a prometheus URL label //
URLLabelFromContext string // // gin.Context string to use as a prometheus URL label
} // URLLabelFromContext string
//}
// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional). //
type PrometheusPushGateway struct { //// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional).
//type PrometheusPushGateway struct {
// Push interval in seconds //
PushIntervalSeconds time.Duration // // Push interval in seconds
// PushIntervalSeconds time.Duration
// Push Gateway URL in format http://domain:port //
// where JOBNAME can be any string of your choice // // Push Gateway URL in format http://domain:port
PushGatewayURL string // // where JOBNAME can be any string of your choice
// PushGatewayURL string
// Local metrics URL where metrics are fetched from, this could be omitted in the future //
// if implemented using prometheus common/expfmt instead // // Local metrics URL where metrics are fetched from, this could be omitted in the future
MetricsURL string // // if implemented using prometheus common/expfmt instead
// MetricsURL string
// pushgateway job name, defaults to "gin" //
Job string // // pushgateway job name, defaults to "gin"
} // Job string
//}
// NewPrometheus generates a new set of metrics with a certain subsystem name. //
func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus { //// NewPrometheus generates a new set of metrics with a certain subsystem name.
if subsystem == "" { //func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus {
subsystem = "app" // if subsystem == "" {
} // subsystem = "app"
// }
var metricsList []*Metric //
// var metricsList []*Metric
if len(customMetricsList) > 1 { //
panic("Too many args. NewPrometheus( string, <optional []*Metric> ).") // if len(customMetricsList) > 1 {
} else if len(customMetricsList) == 1 { // panic("Too many args. NewPrometheus( string, <optional []*Metric> ).")
metricsList = customMetricsList[0] // } else if len(customMetricsList) == 1 {
} // metricsList = customMetricsList[0]
metricsList = append(metricsList, standardMetrics...) // }
// metricsList = append(metricsList, standardMetrics...)
p := &Prometheus{ //
MetricsList: metricsList, // p := &Prometheus{
MetricsPath: defaultMetricPath, // MetricsList: metricsList,
ReqCntURLLabelMappingFn: func(c *gin.Context) string { // MetricsPath: defaultMetricPath,
return c.FullPath() // e.g. /user/:id , /user/:id/info // ReqCntURLLabelMappingFn: func(c *gin.Context) string {
}, // return c.FullPath() // e.g. /user/:id , /user/:id/info
} // },
// }
p.registerMetrics(subsystem) //
// p.registerMetrics(subsystem)
return p //
} // return p
//}
// SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL //
// every pushIntervalSeconds. Metrics are fetched from metricsURL. //// SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL
func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushIntervalSeconds time.Duration) { //// every pushIntervalSeconds. Metrics are fetched from metricsURL.
p.Ppg.PushGatewayURL = pushGatewayURL //func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushIntervalSeconds time.Duration) {
p.Ppg.MetricsURL = metricsURL // p.Ppg.PushGatewayURL = pushGatewayURL
p.Ppg.PushIntervalSeconds = pushIntervalSeconds // p.Ppg.MetricsURL = metricsURL
p.startPushTicker() // p.Ppg.PushIntervalSeconds = pushIntervalSeconds
} // p.startPushTicker()
//}
// SetPushGatewayJob job name, defaults to "gin". //
func (p *Prometheus) SetPushGatewayJob(j string) { //// SetPushGatewayJob job name, defaults to "gin".
p.Ppg.Job = j //func (p *Prometheus) SetPushGatewayJob(j string) {
} // p.Ppg.Job = j
//}
// SetListenAddress for exposing metrics on address. If not set, it will be exposed at the //
// same address of the gin engine that is being used. //// SetListenAddress for exposing metrics on address. If not set, it will be exposed at the
func (p *Prometheus) SetListenAddress(address string) { //// same address of the gin engine that is being used.
p.listenAddress = address //func (p *Prometheus) SetListenAddress(address string) {
if p.listenAddress != "" { // p.listenAddress = address
p.router = gin.Default() // if p.listenAddress != "" {
} // p.router = gin.Default()
} // }
//}
// SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of //
// your content's access log). //// SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of
func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Engine) { //// your content's access log).
p.listenAddress = listenAddress //func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Engine) {
if len(p.listenAddress) > 0 { // p.listenAddress = listenAddress
p.router = r // if len(p.listenAddress) > 0 {
} // p.router = r
} // }
//}
// SetMetricsPath set metrics paths. //
func (p *Prometheus) SetMetricsPath(e *gin.Engine) error { //// SetMetricsPath set metrics paths.
//func (p *Prometheus) SetMetricsPath(e *gin.Engine) error {
if p.listenAddress != "" { //
p.router.GET(p.MetricsPath, prometheusHandler()) // if p.listenAddress != "" {
return p.runServer() // p.router.GET(p.MetricsPath, prometheusHandler())
} else { // return p.runServer()
e.GET(p.MetricsPath, prometheusHandler()) // } else {
return nil // e.GET(p.MetricsPath, prometheusHandler())
} // return nil
} // }
//}
// SetMetricsPathWithAuth set metrics paths with authentication. //
func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) error { //// SetMetricsPathWithAuth set metrics paths with authentication.
//func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) error {
if p.listenAddress != "" { //
p.router.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler()) // if p.listenAddress != "" {
return p.runServer() // p.router.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler())
} else { // return p.runServer()
e.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler()) // } else {
return nil // e.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler())
} // return nil
// }
} //
//}
func (p *Prometheus) runServer() error { //
return p.router.Run(p.listenAddress) //func (p *Prometheus) runServer() error {
} // return p.router.Run(p.listenAddress)
//}
func (p *Prometheus) getMetrics() []byte { //
response, err := http.Get(p.Ppg.MetricsURL) //func (p *Prometheus) getMetrics() []byte {
if err != nil { // response, err := http.Get(p.Ppg.MetricsURL)
return nil // if err != nil {
} // return nil
// }
defer response.Body.Close() //
// defer response.Body.Close()
body, _ := io.ReadAll(response.Body) //
return body // body, _ := io.ReadAll(response.Body)
} // return body
//}
var hostname, _ = os.Hostname() //
//var hostname, _ = os.Hostname()
func (p *Prometheus) getPushGatewayURL() string { //
if p.Ppg.Job == "" { //func (p *Prometheus) getPushGatewayURL() string {
p.Ppg.Job = "gin" // if p.Ppg.Job == "" {
} // p.Ppg.Job = "gin"
return p.Ppg.PushGatewayURL + "/metrics/job/" + p.Ppg.Job + "/instance/" + hostname // }
} // return p.Ppg.PushGatewayURL + "/metrics/job/" + p.Ppg.Job + "/instance/" + hostname
//}
func (p *Prometheus) sendMetricsToPushGateway(metrics []byte) { //
req, err := http.NewRequest("POST", p.getPushGatewayURL(), bytes.NewBuffer(metrics)) //func (p *Prometheus) sendMetricsToPushGateway(metrics []byte) {
if err != nil { // req, err := http.NewRequest("POST", p.getPushGatewayURL(), bytes.NewBuffer(metrics))
return // if err != nil {
} // return
// }
client := &http.Client{} //
resp, err := client.Do(req) // client := &http.Client{}
if err != nil { // resp, err := client.Do(req)
fmt.Println("Error sending to push gateway error:", err.Error()) // if err != nil {
} // fmt.Println("Error sending to push gateway error:", err.Error())
// }
resp.Body.Close() //
} // resp.Body.Close()
//}
func (p *Prometheus) startPushTicker() { //
ticker := time.NewTicker(time.Second * p.Ppg.PushIntervalSeconds) //func (p *Prometheus) startPushTicker() {
go func() { // ticker := time.NewTicker(time.Second * p.Ppg.PushIntervalSeconds)
for range ticker.C { // go func() {
p.sendMetricsToPushGateway(p.getMetrics()) // for range ticker.C {
} // p.sendMetricsToPushGateway(p.getMetrics())
}() // }
} // }()
//}
// NewMetric associates prometheus.Collector based on Metric.Type. //
func NewMetric(m *Metric, subsystem string) prometheus.Collector { //// NewMetric associates prometheus.Collector based on Metric.Type.
var metric prometheus.Collector //func NewMetric(m *Metric, subsystem string) prometheus.Collector {
switch m.Type { // var metric prometheus.Collector
case "counter_vec": // switch m.Type {
metric = prometheus.NewCounterVec( // case "counter_vec":
prometheus.CounterOpts{ // metric = prometheus.NewCounterVec(
Subsystem: subsystem, // prometheus.CounterOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "counter": // )
metric = prometheus.NewCounter( // case "counter":
prometheus.CounterOpts{ // metric = prometheus.NewCounter(
Subsystem: subsystem, // prometheus.CounterOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
case "gauge_vec": // )
metric = prometheus.NewGaugeVec( // case "gauge_vec":
prometheus.GaugeOpts{ // metric = prometheus.NewGaugeVec(
Subsystem: subsystem, // prometheus.GaugeOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "gauge": // )
metric = prometheus.NewGauge( // case "gauge":
prometheus.GaugeOpts{ // metric = prometheus.NewGauge(
Subsystem: subsystem, // prometheus.GaugeOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
case "histogram_vec": // )
metric = prometheus.NewHistogramVec( // case "histogram_vec":
prometheus.HistogramOpts{ // metric = prometheus.NewHistogramVec(
Subsystem: subsystem, // prometheus.HistogramOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "histogram": // )
metric = prometheus.NewHistogram( // case "histogram":
prometheus.HistogramOpts{ // metric = prometheus.NewHistogram(
Subsystem: subsystem, // prometheus.HistogramOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
case "summary_vec": // )
metric = prometheus.NewSummaryVec( // case "summary_vec":
prometheus.SummaryOpts{ // metric = prometheus.NewSummaryVec(
Subsystem: subsystem, // prometheus.SummaryOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "summary": // )
metric = prometheus.NewSummary( // case "summary":
prometheus.SummaryOpts{ // metric = prometheus.NewSummary(
Subsystem: subsystem, // prometheus.SummaryOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
} // )
return metric // }
} // return metric
//}
func (p *Prometheus) registerMetrics(subsystem string) { //
for _, metricDef := range p.MetricsList { //func (p *Prometheus) registerMetrics(subsystem string) {
metric := NewMetric(metricDef, subsystem) // for _, metricDef := range p.MetricsList {
if err := prometheus.Register(metric); err != nil { // metric := NewMetric(metricDef, subsystem)
fmt.Println("could not be registered in Prometheus,metricDef.Name:", metricDef.Name, " error:", err.Error()) // if err := prometheus.Register(metric); err != nil {
} // fmt.Println("could not be registered in Prometheus,metricDef.Name:", metricDef.Name, " error:", err.Error())
// }
switch metricDef { //
case reqCounter: // switch metricDef {
p.reqCnt = metric.(*prometheus.CounterVec) // case reqCounter:
case reqDuration: // p.reqCnt = metric.(*prometheus.CounterVec)
p.reqDur = metric.(*prometheus.HistogramVec) // case reqDuration:
case resSize: // p.reqDur = metric.(*prometheus.HistogramVec)
p.resSz = metric.(prometheus.Summary) // case resSize:
case reqSize: // p.resSz = metric.(prometheus.Summary)
p.reqSz = metric.(prometheus.Summary) // case reqSize:
} // p.reqSz = metric.(prometheus.Summary)
metricDef.MetricCollector = metric // }
} // metricDef.MetricCollector = metric
} // }
//}
// Use adds the middleware to a gin engine. //
func (p *Prometheus) Use(e *gin.Engine) error { //// Use adds the middleware to a gin engine.
e.Use(p.HandlerFunc()) //func (p *Prometheus) Use(e *gin.Engine) error {
return p.SetMetricsPath(e) // e.Use(p.HandlerFunc())
} // return p.SetMetricsPath(e)
//}
// UseWithAuth adds the middleware to a gin engine with BasicAuth. //
func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts) error { //// UseWithAuth adds the middleware to a gin engine with BasicAuth.
e.Use(p.HandlerFunc()) //func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts) error {
return p.SetMetricsPathWithAuth(e, accounts) // e.Use(p.HandlerFunc())
} // return p.SetMetricsPathWithAuth(e, accounts)
//}
// HandlerFunc defines handler function for middleware. //
func (p *Prometheus) HandlerFunc() gin.HandlerFunc { //// HandlerFunc defines handler function for middleware.
return func(c *gin.Context) { //func (p *Prometheus) HandlerFunc() gin.HandlerFunc {
if c.Request.URL.Path == p.MetricsPath { // return func(c *gin.Context) {
c.Next() // if c.Request.URL.Path == p.MetricsPath {
return // c.Next()
} // return
// }
start := time.Now() //
reqSz := computeApproximateRequestSize(c.Request) // start := time.Now()
// reqSz := computeApproximateRequestSize(c.Request)
c.Next() //
// c.Next()
status := strconv.Itoa(c.Writer.Status()) //
elapsed := float64(time.Since(start)) / float64(time.Second) // status := strconv.Itoa(c.Writer.Status())
resSz := float64(c.Writer.Size()) // elapsed := float64(time.Since(start)) / float64(time.Second)
// resSz := float64(c.Writer.Size())
url := p.ReqCntURLLabelMappingFn(c) //
if len(p.URLLabelFromContext) > 0 { // url := p.ReqCntURLLabelMappingFn(c)
u, found := c.Get(p.URLLabelFromContext) // if len(p.URLLabelFromContext) > 0 {
if !found { // u, found := c.Get(p.URLLabelFromContext)
u = "unknown" // if !found {
} // u = "unknown"
url = u.(string) // }
} // url = u.(string)
p.reqDur.WithLabelValues(status, c.Request.Method, url).Observe(elapsed) // }
p.reqCnt.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, url).Inc() // p.reqDur.WithLabelValues(status, c.Request.Method, url).Observe(elapsed)
p.reqSz.Observe(float64(reqSz)) // p.reqCnt.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, url).Inc()
p.resSz.Observe(resSz) // p.reqSz.Observe(float64(reqSz))
} // p.resSz.Observe(resSz)
} // }
//}
func prometheusHandler() gin.HandlerFunc { //
h := promhttp.Handler() //func prometheusHandler() gin.HandlerFunc {
return func(c *gin.Context) { // h := promhttp.Handler()
h.ServeHTTP(c.Writer, c.Request) // return func(c *gin.Context) {
} // h.ServeHTTP(c.Writer, c.Request)
} // }
//}
func computeApproximateRequestSize(r *http.Request) int { //
var s int //func computeApproximateRequestSize(r *http.Request) int {
if r.URL != nil { // var s int
s = len(r.URL.Path) // if r.URL != nil {
} // s = len(r.URL.Path)
// }
s += len(r.Method) //
s += len(r.Proto) // s += len(r.Method)
for name, values := range r.Header { // s += len(r.Proto)
s += len(name) // for name, values := range r.Header {
for _, value := range values { // s += len(name)
s += len(value) // for _, value := range values {
} // s += len(value)
} // }
s += len(r.Host) // }
// s += len(r.Host)
// r.FormData and r.MultipartForm are assumed to be included in r.URL. //
// // r.FormData and r.MultipartForm are assumed to be included in r.URL.
if r.ContentLength != -1 { //
s += int(r.ContentLength) // if r.ContentLength != -1 {
} // s += int(r.ContentLength)
return s // }
} // return s
//}

@ -0,0 +1,106 @@
package prommetrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"strconv"
"time"
)
const ApiPath = "/metrics"
var (
apiRegistry = prometheus.NewRegistry()
apiCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_count",
Help: "Total number of API calls",
},
[]string{"path", "code", "type"},
)
httpCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_count",
Help: "Total number of HTTP calls",
},
[]string{"path", "method", "status", "duration"},
)
)
func init() {
apiRegistry.MustRegister(apiCounter, httpCounter)
}
func APICall(path string, apiCode int, apiType string) {
apiCounter.With(prometheus.Labels{"path": path, "code": strconv.Itoa(apiCode), "type": apiType}).Inc()
}
func HttpCall(path string, method string, status int, duration time.Duration) {
httpCounter.With(prometheus.Labels{"path": path, "method": method, "status": strconv.Itoa(status), "duration": durationRange(duration)}).Inc()
}
var (
durations = [...]time.Duration{
time.Millisecond * 1,
time.Millisecond * 2,
time.Millisecond * 3,
time.Millisecond * 4,
time.Millisecond * 5,
time.Millisecond * 6,
time.Millisecond * 7,
time.Millisecond * 8,
time.Millisecond * 9,
time.Millisecond * 10,
time.Millisecond * 20,
time.Millisecond * 30,
time.Millisecond * 40,
time.Millisecond * 50,
time.Millisecond * 60,
time.Millisecond * 70,
time.Millisecond * 80,
time.Millisecond * 90,
time.Millisecond * 100,
time.Millisecond * 200,
time.Millisecond * 300,
time.Millisecond * 400,
time.Millisecond * 500,
time.Millisecond * 600,
time.Millisecond * 700,
time.Millisecond * 800,
time.Millisecond * 900,
time.Second * 1,
time.Second * 2,
time.Second * 3,
time.Second * 4,
time.Second * 5,
time.Second * 6,
time.Second * 7,
time.Second * 8,
time.Second * 9,
time.Second * 10,
time.Second * 20,
time.Second * 30,
time.Second * 40,
time.Second * 50,
time.Second * 60,
time.Second * 70,
time.Second * 80,
time.Second * 90,
time.Second * 100,
}
maxDuration = durations[len(durations)-1]
)
func durationRange(duration time.Duration) string {
for _, d := range durations {
if duration <= d {
return d.String()
}
}
return ">" + maxDuration.String()
}
func ApiHandler() http.Handler {
return promhttp.HandlerFor(apiRegistry, promhttp.HandlerOpts{})
}

@ -0,0 +1,56 @@
package prommetrics
import (
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net/http"
"testing"
"time"
)
var (
apiCallCounter1 = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_calls_total",
Help: "Total number of API calls",
},
[]string{"endpoint", "status", "code", "error"},
)
registerer *prometheus.Registry
)
func init() {
registerer = prometheus.NewRegistry()
registerer.MustRegister(apiCallCounter1)
}
func recordAPICall(endpoint string, status string) {
apiCallCounter1.With(prometheus.Labels{"endpoint": endpoint, "status": status, "code": "200", "error": "ArgsError"}).Inc()
}
func TestName(t *testing.T) {
go func() {
for i := 0; ; i++ {
recordAPICall("/api/test", "success")
time.Sleep(time.Second)
}
}()
go func() {
for i := 0; ; i++ {
recordAPICall("/api/test", "failed")
time.Sleep(time.Second * 3)
}
}()
http.Handle("/metrics", promhttp.HandlerFor(registerer, promhttp.HandlerOpts{}))
if err := http.ListenAndServe(":2112", nil); err != nil {
panic(err)
}
}
func TestName2(t *testing.T) {
var d time.Duration
d = time.Second / 900
fmt.Println(durationRange(d))
}

@ -14,17 +14,17 @@
package prommetrics package prommetrics
import ginprom "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus" //import ginprom "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
/* /*
labels := prometheus.Labels{"label_one": "any", "label_two": "value"} labels := prometheus.Labels{"label_one": "any", "label_two": "value"}
ApiCustomCnt.MetricCollector.(*prometheus.CounterVec).With(labels).Inc(). ApiCustomCnt.MetricCollector.(*prometheus.CounterVec).With(labels).Inc().
*/ */
var ( //var (
ApiCustomCnt = &ginprom.Metric{ // ApiCustomCnt = &ginprom.Metric{
Name: "custom_total", // Name: "custom_total",
Description: "Custom counter events.", // Description: "Custom counter events.",
Type: "counter_vec", // Type: "counter_vec",
Args: []string{"label_one", "label_two"}, // Args: []string{"label_one", "label_two"},
} // }
) //)

@ -17,7 +17,6 @@ package prommetrics
import ( import (
gp "github.com/grpc-ecosystem/go-grpc-prometheus" gp "github.com/grpc-ecosystem/go-grpc-prometheus"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/collectors"
) )
@ -48,11 +47,11 @@ func GetGrpcCusMetrics(registerName string, share *config2.Share) []prometheus.C
} }
} }
func GetGinCusMetrics(name string) []*ginprometheus.Metric { //func GetGinCusMetrics(name string) []*ginprometheus.Metric {
switch name { // switch name {
case "Api": // case "Api":
return []*ginprometheus.Metric{ApiCustomCnt} // return []*ginprometheus.Metric{ApiCustomCnt}
default: // default:
return []*ginprometheus.Metric{ApiCustomCnt} // return []*ginprometheus.Metric{ApiCustomCnt}
} // }
} //}

Loading…
Cancel
Save