-
安装 - go get
OpenCensus: go get -u -v go.opencensus.io/... Prometheus exporter: go get -u -v contrib.go.opencensus.io/exporter/prometheus
-
概览
- 创建我们将要记录的量化 Metrics(numerical)
- 创建我们将要和Metrics关联的tags
- 组织metrics到一个View
- 将views出口到后端:(Prometheus)
-
代码开始
- 写一个REPL程序,然后通过这个程序收集metrics观察以下动作
- 循环中每次处理的延迟时间
- 命令行中读取的行数
- 错误次数
- 每次命令的长度
- 写一个REPL程序,然后通过这个程序收集metrics观察以下动作
-
让Metrics可用
-
import Packages
import ( "bufio" "bytes" "context" "fmt" "io" "log" "os" "time" "go.opencensus.io/stats" "go.opencensus.io/tag" )
-
创建Metrics
实质为变量:待会将要记录的metrics
var ( // The latency in milliseconds MLatencyMs = stats.Float64("repl/latency", "The latency in milliseconds per REPL loop", "ms") // Counts/groups the lengths of lines read in. MLineLengths = stats.Int64("repl/line_lengths", "The distribution of line lengths", "By") )
-
创建Tags
同样也是变量,稍后将用这些变量记录那些方法被调用
var ( KeyMethod, _ = tag.NewKey("method") KeyStatus, _ = tag.NewKey("status") KeyError, _ = tag.NewKey("error") )
-
Inserting Tags - 嵌入的Tags
插入一个特殊的tag,它会返回一个ctx.Context 当我们记录metrics的时候需要用到ctx,这个ctx有所有预先插入的tags,允许在上下文中传播
ctx, _ := tag.New(context.Background(), tag.Insert(KeyMethod, "repl"), tag.Insert(KeyStatus, "OK"))
当记录metrics的时候,我们需要tags.New()返回的ctx。
-
记录Metrics
现在我们将要记录期望的Metrics,为了实现这个,我们需要用到stats.Record方法和ctx与预先实例化Metrics变量
func processLine(ctx context.Context, in []byte) (out []byte, err error) { defer func() { stats.Record(ctx, MLatencyMs.M(sinceInMilliseconds(startTime)), MLineLengths.M(int64(len(in)))) }() }
-
-
使Views可用 - Enable Views
import "go.opencensus.io/stats/view"
-
创建 Views
现在创建让metrics以何种方式组织的Views
var ( LatencyView = &view.View{ Name: "demo/latency", Measure: MLatencyMs, Description: "The distribution of the latencies", // Latency in buckets: // [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s] Aggregation: view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000), TagKeys: []tag.Key{KeyMethod}} LineCountView = &view.View{ Name: "demo/lines_in", Measure: MLineLengths, Description: "The number of lines from standard input", Aggregation: view.Count(), } LineLengthView = &view.View{ Name: "demo/line_lengths", Description: "Groups the lengths of keys in buckets", Measure: MLineLengths, // Lengths: [>=0B, >=5B, >=10B, >=15B, >=20B, >=40B, >=60B, >=80, >=100B, >=200B, >=400, >=600, >=800, >=1000] Aggregation: view.Distribution(0, 5, 10, 15, 20, 40, 60, 80, 100, 200, 400, 600, 800, 1000), } )
-
注册Views
if err := view.Register(LatencyView, LineCountView, LineLengthView); err != nil { log.Fatalf("Failed to register views: %v", err) }
-
导出stats - Exporting stats
-
导包:import Packages
在import中加入Prometheus GO exporter 包
import "contrib.go.opencensus.io/exporter/prometheus"
-
创建出口者 - Create the exporter
为了保证metrics出口到Prometheus,应用需要暴露一个端口,OpenCensus Go Prometheus exporter是一个http.Handler,并且监听必须通过HTTP路径/metrics
go func() { mux := http.NewServeMux() mux.Handle("/metrics", pe) if err := http.ListenAndServe(":8888", mux); err != nil { log.Fatalf("Failed to run Prometheus scrape endpoint: %v", err) } }()
-
Running the App
go run repl.go
-
Prometheus configuration file
scrape_configs: - job_name: 'ocmetricstutorial' scrape_interval: 10s static_configs: - targets: ['localhost:8888']
-
Running Prometheus
prometheus --config.file=promconfig.yaml
-
-
代码汇总
package main
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
"contrib.go.opencensus.io/exporter/prometheus"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
)
var (
// The latency in milliseconds
MLatencyMs = stats.Float64("repl/latency", "The latency in milliseconds per REPL loop", "ms")
// Counts/groups the lengths of lines read in.
MLineLengths = stats.Int64("repl/line_lengths", "The distribution of line lengths", "By")
)
var (
KeyMethod, _ = tag.NewKey("method")
KeyStatus, _ = tag.NewKey("status")
KeyError, _ = tag.NewKey("error")
)
var (
LatencyView = &view.View{
Name: "demo/latency",
Measure: MLatencyMs,
Description: "The distribution of the latencies",
// Latency in buckets:
// [>=0ms, >=25ms, >=50ms, >=75ms, >=100ms, >=200ms, >=400ms, >=600ms, >=800ms, >=1s, >=2s, >=4s, >=6s]
Aggregation: view.Distribution(0, 25, 50, 75, 100, 200, 400, 600, 800, 1000, 2000, 4000, 6000),
TagKeys: []tag.Key{KeyMethod}}
LineCountView = &view.View{
Name: "demo/lines_in",
Measure: MLineLengths,
Description: "The number of lines from standard input",
Aggregation: view.Count(),
}
LineLengthView = &view.View{
Name: "demo/line_lengths",
Description: "Groups the lengths of keys in buckets",
Measure: MLineLengths,
// Lengths: [>=0B, >=5B, >=10B, >=15B, >=20B, >=40B, >=60B, >=80, >=100B, >=200B, >=400, >=600, >=800, >=1000]
Aggregation: view.Distribution(0, 5, 10, 15, 20, 40, 60, 80, 100, 200, 400, 600, 800, 1000),
}
)
func main() {
// Register the views, it is imperative that this step exists
// lest recorded metrics will be dropped and never exported.
if err := view.Register(LatencyView, LineCountView, LineLengthView); err != nil {
log.Fatalf("Failed to register the views: %v", err)
}
// Create the Prometheus exporter.
pe, err := prometheus.NewExporter(prometheus.Options{
Namespace: "ocmetricstutorial",
})
if err != nil {
log.Fatalf("Failed to create the Prometheus stats exporter: %v", err)
}
// Now finally run the Prometheus exporter as a scrape endpoint.
// We'll run the server on port 8888.
go func() {
mux := http.NewServeMux()
mux.Handle("/metrics", pe)
if err := http.ListenAndServe(":8888", mux); err != nil {
log.Fatalf("Failed to run Prometheus scrape endpoint: %v", err)
}
}()
// In a REPL:
// 1. Read input
// 2. process input
br := bufio.NewReader(os.Stdin)
// Register the views
if err := view.Register(LatencyView, LineCountView, LineLengthView); err != nil {
log.Fatalf("Failed to register views: %v", err)
}
// repl is the read, evaluate, print, loop
for {
if err := readEvaluateProcess(br); err != nil {
if err == io.EOF {
return
}
log.Fatal(err)
}
}
}
// readEvaluateProcess reads a line from the input reader and
// then processes it. It returns an error if any was encountered.
func readEvaluateProcess(br *bufio.Reader) (terr error) {
//注意这里有一行,在官网上边没有,如果复制官网代码执行会报错
//加上这一行代码即可 startTime := time.New()
startTime := time.New()
ctx, err := tag.New(context.Background(), tag.Insert(KeyMethod, "repl"), tag.Insert(KeyStatus, "OK"))
if err != nil {
return err
}
defer func() {
if terr != nil {
ctx, _ = tag.New(ctx, tag.Upsert(KeyStatus, "ERROR"),
tag.Upsert(KeyError, terr.Error()))
}
stats.Record(ctx, MLatencyMs.M(sinceInMilliseconds(startTime)))
}()
fmt.Printf("> ")
line, _, err := br.ReadLine()
if err != nil {
if err != io.EOF {
return err
}
log.Fatal(err)
}
out, err := processLine(ctx, line)
if err != nil {
return err
}
fmt.Printf("< %s\n\n", out)
return nil
}
// processLine takes in a line of text and
// transforms it. Currently it just capitalizes it.
func processLine(ctx context.Context, in []byte) (out []byte, err error) {
startTime := time.Now()
defer func() {
stats.Record(ctx, MLatencyMs.M(sinceInMilliseconds(startTime)),
MLineLengths.M(int64(len(in))))
}()
return bytes.ToUpper(in), nil
}
func sinceInMilliseconds(startTime time.Time) float64 {
return float64(time.Since(startTime).Nanoseconds()) / 1e6
}
opencensus参考网址:https://opencensus.io/quickstart/go/metrics/