metricsgen: port metrics code generation tool (#9156)

This is a port of #8470 and #8501 from the former mainline branch, in
support of #2600 and #9076. 

No additional changes other than checking out the code have been
implemented in this PR. A subsequent PR will incorporate this code.
This commit is contained in:
Sam Kleinman
2022-08-03 10:02:18 -04:00
committed by GitHub
parent ea271c534a
commit 4f30c90e62
10 changed files with 1014 additions and 0 deletions

View File

@@ -0,0 +1,197 @@
// metricsdiff is a tool for generating a diff between two different files containing
// prometheus metrics. metricsdiff outputs which metrics have been added, removed,
// or have different sets of labels between the two files.
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"path/filepath"
"sort"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
)
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: %[1]s <path1> <path2>
Generate the diff between the two files of Prometheus metrics.
The input should have the format output by a Prometheus HTTP endpoint.
The tool indicates which metrics have been added, removed, or use different
label sets from path1 to path2.
`, filepath.Base(os.Args[0]))
flag.PrintDefaults()
}
}
// Diff contains the set of metrics that were modified between two files
// containing prometheus metrics output.
type Diff struct {
Adds []string
Removes []string
Changes []LabelDiff
}
// LabelDiff describes the label changes between two versions of the same metric.
type LabelDiff struct {
Metric string
Adds []string
Removes []string
}
type parsedMetric struct {
name string
labels []string
}
type metricsList []parsedMetric
func main() {
flag.Parse()
if flag.NArg() != 2 {
log.Fatalf("Usage is '%s <path1> <path2>', got %d arguments",
filepath.Base(os.Args[0]), flag.NArg())
}
fa, err := os.Open(flag.Arg(0))
if err != nil {
log.Fatalf("Open: %v", err)
}
defer fa.Close()
fb, err := os.Open(flag.Arg(1))
if err != nil {
log.Fatalf("Open: %v", err)
}
defer fb.Close()
md, err := DiffFromReaders(fa, fb)
if err != nil {
log.Fatalf("Generating diff: %v", err)
}
fmt.Print(md)
}
// DiffFromReaders parses the metrics present in the readers a and b and
// determines which metrics were added and removed in b.
func DiffFromReaders(a, b io.Reader) (Diff, error) {
var parser expfmt.TextParser
amf, err := parser.TextToMetricFamilies(a)
if err != nil {
return Diff{}, err
}
bmf, err := parser.TextToMetricFamilies(b)
if err != nil {
return Diff{}, err
}
md := Diff{}
aList := toList(amf)
bList := toList(bmf)
i, j := 0, 0
for i < len(aList) || j < len(bList) {
for j < len(bList) && (i >= len(aList) || bList[j].name < aList[i].name) {
md.Adds = append(md.Adds, bList[j].name)
j++
}
for i < len(aList) && j < len(bList) && aList[i].name == bList[j].name {
adds, removes := listDiff(aList[i].labels, bList[j].labels)
if len(adds) > 0 || len(removes) > 0 {
md.Changes = append(md.Changes, LabelDiff{
Metric: aList[i].name,
Adds: adds,
Removes: removes,
})
}
i++
j++
}
for i < len(aList) && (j >= len(bList) || aList[i].name < bList[j].name) {
md.Removes = append(md.Removes, aList[i].name)
i++
}
}
return md, nil
}
func toList(l map[string]*dto.MetricFamily) metricsList {
r := make([]parsedMetric, len(l))
var idx int
for name, family := range l {
r[idx] = parsedMetric{
name: name,
labels: labelsToStringList(family.Metric[0].Label),
}
idx++
}
sort.Sort(metricsList(r))
return r
}
func labelsToStringList(ls []*dto.LabelPair) []string {
r := make([]string, len(ls))
for i, l := range ls {
r[i] = l.GetName()
}
return sort.StringSlice(r)
}
func listDiff(a, b []string) ([]string, []string) {
adds, removes := []string{}, []string{}
i, j := 0, 0
for i < len(a) || j < len(b) {
for j < len(b) && (i >= len(a) || b[j] < a[i]) {
adds = append(adds, b[j])
j++
}
for i < len(a) && j < len(b) && a[i] == b[j] {
i++
j++
}
for i < len(a) && (j >= len(b) || a[i] < b[j]) {
removes = append(removes, a[i])
i++
}
}
return adds, removes
}
func (m metricsList) Len() int { return len(m) }
func (m metricsList) Less(i, j int) bool { return m[i].name < m[j].name }
func (m metricsList) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
func (m Diff) String() string {
var s strings.Builder
if len(m.Adds) > 0 || len(m.Removes) > 0 {
fmt.Fprintln(&s, "Metric changes:")
}
if len(m.Adds) > 0 {
for _, add := range m.Adds {
fmt.Fprintf(&s, "+++ %s\n", add)
}
}
if len(m.Removes) > 0 {
for _, rem := range m.Removes {
fmt.Fprintf(&s, "--- %s\n", rem)
}
}
if len(m.Changes) > 0 {
fmt.Fprintln(&s, "Label changes:")
for _, ld := range m.Changes {
fmt.Fprintf(&s, "Metric: %s\n", ld.Metric)
for _, add := range ld.Adds {
fmt.Fprintf(&s, "+++ %s\n", add)
}
for _, rem := range ld.Removes {
fmt.Fprintf(&s, "--- %s\n", rem)
}
}
}
return s.String()
}

View File

@@ -0,0 +1,62 @@
package main_test
import (
"bytes"
"io"
"testing"
"github.com/stretchr/testify/require"
metricsdiff "github.com/tendermint/tendermint/scripts/metricsgen/metricsdiff"
)
func TestDiff(t *testing.T) {
for _, tc := range []struct {
name string
aContents string
bContents string
want string
}{
{
name: "labels",
aContents: `
metric_one{label_one="content", label_two="content"} 0
`,
bContents: `
metric_one{label_three="content", label_four="content"} 0
`,
want: `Label changes:
Metric: metric_one
+++ label_three
+++ label_four
--- label_one
--- label_two
`,
},
{
name: "metrics",
aContents: `
metric_one{label_one="content"} 0
`,
bContents: `
metric_two{label_two="content"} 0
`,
want: `Metric changes:
+++ metric_two
--- metric_one
`,
},
} {
t.Run(tc.name, func(t *testing.T) {
bufA := bytes.NewBuffer([]byte{})
bufB := bytes.NewBuffer([]byte{})
_, err := io.WriteString(bufA, tc.aContents)
require.NoError(t, err)
_, err = io.WriteString(bufB, tc.bContents)
require.NoError(t, err)
md, err := metricsdiff.DiffFromReaders(bufA, bufB)
require.NoError(t, err)
require.Equal(t, tc.want, md.String())
})
}
}

View File

@@ -0,0 +1,347 @@
// metricsgen is a code generation tool for creating constructors for Tendermint
// metrics types.
package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"go/types"
"io"
"io/fs"
"log"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"text/template"
)
func init() {
flag.Usage = func() {
fmt.Fprintf(os.Stderr, `Usage: %[1]s -struct <struct>
Generate constructors for the metrics type specified by -struct contained in
the current directory. The tool creates a new file in the current directory
containing the generated code.
Options:
`, filepath.Base(os.Args[0]))
flag.PrintDefaults()
}
}
const metricsPackageName = "github.com/go-kit/kit/metrics"
const (
metricNameTag = "metrics_name"
labelsTag = "metrics_labels"
bucketTypeTag = "metrics_buckettype"
bucketSizeTag = "metrics_bucketsizes"
)
var (
dir = flag.String("dir", ".", "Path to the directory containing the target package")
strct = flag.String("struct", "Metrics", "Struct to parse for metrics")
)
var bucketType = map[string]string{
"exprange": "stdprometheus.ExponentialBucketsRange",
"exp": "stdprometheus.ExponentialBuckets",
"lin": "stdprometheus.LinearBuckets",
}
var tmpl = template.Must(template.New("tmpl").Parse(`// Code generated by metricsgen. DO NOT EDIT.
package {{ .Package }}
import (
"github.com/go-kit/kit/metrics/discard"
prometheus "github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
func PrometheusMetrics(namespace string, labelsAndValues...string) *Metrics {
labels := []string{}
for i := 0; i < len(labelsAndValues); i += 2 {
labels = append(labels, labelsAndValues[i])
}
return &Metrics{
{{ range $metric := .ParsedMetrics }}
{{- $metric.FieldName }}: prometheus.New{{ $metric.TypeName }}From(stdprometheus.{{$metric.TypeName }}Opts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "{{$metric.MetricName }}",
Help: "{{ $metric.Description }}",
{{ if ne $metric.HistogramOptions.BucketType "" }}
Buckets: {{ $metric.HistogramOptions.BucketType }}({{ $metric.HistogramOptions.BucketSizes }}),
{{ else if ne $metric.HistogramOptions.BucketSizes "" }}
Buckets: []float64{ {{ $metric.HistogramOptions.BucketSizes }} },
{{ end }}
{{- if eq (len $metric.Labels) 0 }}
}, labels).With(labelsAndValues...),
{{ else }}
}, append(labels, {{$metric.Labels}})).With(labelsAndValues...),
{{ end }}
{{- end }}
}
}
func NopMetrics() *Metrics {
return &Metrics{
{{- range $metric := .ParsedMetrics }}
{{ $metric.FieldName }}: discard.New{{ $metric.TypeName }}(),
{{- end }}
}
}
`))
// ParsedMetricField is the data parsed for a single field of a metric struct.
type ParsedMetricField struct {
TypeName string
FieldName string
MetricName string
Description string
Labels string
HistogramOptions HistogramOpts
}
type HistogramOpts struct {
BucketType string
BucketSizes string
}
// TemplateData is all of the data required for rendering a metric file template.
type TemplateData struct {
Package string
ParsedMetrics []ParsedMetricField
}
func main() {
flag.Parse()
if *strct == "" {
log.Fatal("You must specify a non-empty -struct")
}
td, err := ParseMetricsDir(".", *strct)
if err != nil {
log.Fatalf("Parsing file: %v", err)
}
out := filepath.Join(*dir, "metrics.gen.go")
f, err := os.Create(out)
if err != nil {
log.Fatalf("Opening file: %v", err)
}
err = GenerateMetricsFile(f, td)
if err != nil {
log.Fatalf("Generating code: %v", err)
}
}
func ignoreTestFiles(f fs.FileInfo) bool {
return !strings.Contains(f.Name(), "_test.go")
}
// ParseMetricsDir parses the dir and scans for a struct matching structName,
// ignoring all test files. ParseMetricsDir iterates the fields of the metrics
// struct and builds a TemplateData using the data obtained from the abstract syntax tree.
func ParseMetricsDir(dir string, structName string) (TemplateData, error) {
fs := token.NewFileSet()
d, err := parser.ParseDir(fs, dir, ignoreTestFiles, parser.ParseComments)
if err != nil {
return TemplateData{}, err
}
if len(d) > 1 {
return TemplateData{}, fmt.Errorf("multiple packages found in %s", dir)
}
if len(d) == 0 {
return TemplateData{}, fmt.Errorf("no go pacakges found in %s", dir)
}
// Grab the package name.
var pkgName string
var pkg *ast.Package
for pkgName, pkg = range d {
}
td := TemplateData{
Package: pkgName,
}
// Grab the metrics struct
m, mPkgName, err := findMetricsStruct(pkg.Files, structName)
if err != nil {
return TemplateData{}, err
}
for _, f := range m.Fields.List {
if !isMetric(f.Type, mPkgName) {
continue
}
pmf := parseMetricField(f)
td.ParsedMetrics = append(td.ParsedMetrics, pmf)
}
return td, err
}
// GenerateMetricsFile executes the metrics file template, writing the result
// into the io.Writer.
func GenerateMetricsFile(w io.Writer, td TemplateData) error {
b := []byte{}
buf := bytes.NewBuffer(b)
err := tmpl.Execute(buf, td)
if err != nil {
return err
}
b, err = format.Source(buf.Bytes())
if err != nil {
return err
}
_, err = io.Copy(w, bytes.NewBuffer(b))
if err != nil {
return err
}
return nil
}
func findMetricsStruct(files map[string]*ast.File, structName string) (*ast.StructType, string, error) {
var (
st *ast.StructType
)
for _, file := range files {
mPkgName, err := extractMetricsPackageName(file.Imports)
if err != nil {
return nil, "", fmt.Errorf("unable to determine metrics package name: %v", err)
}
if !ast.FilterFile(file, func(name string) bool {
return name == structName
}) {
continue
}
ast.Inspect(file, func(n ast.Node) bool {
switch f := n.(type) {
case *ast.TypeSpec:
if f.Name.Name == structName {
var ok bool
st, ok = f.Type.(*ast.StructType)
if !ok {
err = fmt.Errorf("found identifier for %q of wrong type", structName)
}
}
return false
default:
return true
}
})
if err != nil {
return nil, "", err
}
if st != nil {
return st, mPkgName, nil
}
}
return nil, "", fmt.Errorf("target struct %q not found in dir", structName)
}
func parseMetricField(f *ast.Field) ParsedMetricField {
pmf := ParsedMetricField{
Description: extractHelpMessage(f.Doc),
MetricName: extractFieldName(f.Names[0].String(), f.Tag),
FieldName: f.Names[0].String(),
TypeName: extractTypeName(f.Type),
Labels: extractLabels(f.Tag),
}
if pmf.TypeName == "Histogram" {
pmf.HistogramOptions = extractHistogramOptions(f.Tag)
}
return pmf
}
func extractTypeName(e ast.Expr) string {
return strings.TrimPrefix(path.Ext(types.ExprString(e)), ".")
}
func extractHelpMessage(cg *ast.CommentGroup) string {
if cg == nil {
return ""
}
var help []string //nolint: prealloc
for _, c := range cg.List {
mt := strings.TrimPrefix(c.Text, "//metrics:")
if mt != c.Text {
return strings.TrimSpace(mt)
}
help = append(help, strings.TrimSpace(strings.TrimPrefix(c.Text, "//")))
}
return strings.Join(help, " ")
}
func isMetric(e ast.Expr, mPkgName string) bool {
return strings.Contains(types.ExprString(e), fmt.Sprintf("%s.", mPkgName))
}
func extractLabels(bl *ast.BasicLit) string {
if bl != nil {
t := reflect.StructTag(strings.Trim(bl.Value, "`"))
if v := t.Get(labelsTag); v != "" {
var res []string
for _, s := range strings.Split(v, ",") {
res = append(res, strconv.Quote(strings.TrimSpace(s)))
}
return strings.Join(res, ",")
}
}
return ""
}
func extractFieldName(name string, tag *ast.BasicLit) string {
if tag != nil {
t := reflect.StructTag(strings.Trim(tag.Value, "`"))
if v := t.Get(metricNameTag); v != "" {
return v
}
}
return toSnakeCase(name)
}
func extractHistogramOptions(tag *ast.BasicLit) HistogramOpts {
h := HistogramOpts{}
if tag != nil {
t := reflect.StructTag(strings.Trim(tag.Value, "`"))
if v := t.Get(bucketTypeTag); v != "" {
h.BucketType = bucketType[v]
}
if v := t.Get(bucketSizeTag); v != "" {
h.BucketSizes = v
}
}
return h
}
func extractMetricsPackageName(imports []*ast.ImportSpec) (string, error) {
for _, i := range imports {
u, err := strconv.Unquote(i.Path.Value)
if err != nil {
return "", err
}
if u == metricsPackageName {
if i.Name != nil {
return i.Name.Name, nil
}
return path.Base(u), nil
}
}
return "", nil
}
var capitalChange = regexp.MustCompile("([a-z0-9])([A-Z])")
func toSnakeCase(str string) string {
snake := capitalChange.ReplaceAllString(str, "${1}_${2}")
return strings.ToLower(snake)
}

View File

@@ -0,0 +1,259 @@
package main_test
import (
"bytes"
"fmt"
"go/parser"
"go/token"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
metricsgen "github.com/tendermint/tendermint/scripts/metricsgen"
)
const testDataDir = "./testdata"
func TestSimpleTemplate(t *testing.T) {
m := metricsgen.ParsedMetricField{
TypeName: "Histogram",
FieldName: "MyMetric",
MetricName: "request_count",
Description: "how many requests were made since the start of the process",
Labels: "first, second, third",
}
td := metricsgen.TemplateData{
Package: "mypack",
ParsedMetrics: []metricsgen.ParsedMetricField{m},
}
b := bytes.NewBuffer([]byte{})
err := metricsgen.GenerateMetricsFile(b, td)
if err != nil {
t.Fatalf("unable to parse template %v", err)
}
}
func TestFromData(t *testing.T) {
infos, err := ioutil.ReadDir(testDataDir)
if err != nil {
t.Fatalf("unable to open file %v", err)
}
for _, dir := range infos {
t.Run(dir.Name(), func(t *testing.T) {
if !dir.IsDir() {
t.Fatalf("expected file %s to be directory", dir.Name())
}
dirName := path.Join(testDataDir, dir.Name())
pt, err := metricsgen.ParseMetricsDir(dirName, "Metrics")
if err != nil {
t.Fatalf("unable to parse from dir %q: %v", dir, err)
}
outFile := path.Join(dirName, "out.go")
if err != nil {
t.Fatalf("unable to open file %s: %v", outFile, err)
}
of, err := os.Create(outFile)
if err != nil {
t.Fatalf("unable to open file %s: %v", outFile, err)
}
defer os.Remove(outFile)
if err := metricsgen.GenerateMetricsFile(of, pt); err != nil {
t.Fatalf("unable to generate metrics file %s: %v", outFile, err)
}
if _, err := parser.ParseFile(token.NewFileSet(), outFile, nil, parser.AllErrors); err != nil {
t.Fatalf("unable to parse generated file %s: %v", outFile, err)
}
bNew, err := ioutil.ReadFile(outFile)
if err != nil {
t.Fatalf("unable to read generated file %s: %v", outFile, err)
}
goldenFile := path.Join(dirName, "metrics.gen.go")
bOld, err := ioutil.ReadFile(goldenFile)
if err != nil {
t.Fatalf("unable to read file %s: %v", goldenFile, err)
}
if !bytes.Equal(bNew, bOld) {
t.Fatalf("newly generated code in file %s does not match golden file %s\n"+
"if the output of the metricsgen tool is expected to change run the following make target: \n"+
"\tmake metrics", outFile, goldenFile)
}
})
}
}
func TestParseMetricsStruct(t *testing.T) {
const pkgName = "mypkg"
metricsTests := []struct {
name string
shouldError bool
metricsStruct string
expected metricsgen.TemplateData
}{
{
name: "basic",
metricsStruct: `type Metrics struct {
myGauge metrics.Gauge
}`,
expected: metricsgen.TemplateData{
Package: pkgName,
ParsedMetrics: []metricsgen.ParsedMetricField{
{
TypeName: "Gauge",
FieldName: "myGauge",
MetricName: "my_gauge",
},
},
},
},
{
name: "histogram",
metricsStruct: "type Metrics struct {\n" +
"myHistogram metrics.Histogram `metrics_buckettype:\"exp\" metrics_bucketsizes:\"1, 100, .8\"`\n" +
"}",
expected: metricsgen.TemplateData{
Package: pkgName,
ParsedMetrics: []metricsgen.ParsedMetricField{
{
TypeName: "Histogram",
FieldName: "myHistogram",
MetricName: "my_histogram",
HistogramOptions: metricsgen.HistogramOpts{
BucketType: "stdprometheus.ExponentialBuckets",
BucketSizes: "1, 100, .8",
},
},
},
},
},
{
name: "labeled name",
metricsStruct: "type Metrics struct {\n" +
"myCounter metrics.Counter `metrics_name:\"new_name\"`\n" +
"}",
expected: metricsgen.TemplateData{
Package: pkgName,
ParsedMetrics: []metricsgen.ParsedMetricField{
{
TypeName: "Counter",
FieldName: "myCounter",
MetricName: "new_name",
},
},
},
},
{
name: "metric labels",
metricsStruct: "type Metrics struct {\n" +
"myCounter metrics.Counter `metrics_labels:\"label1,label2\"`\n" +
"}",
expected: metricsgen.TemplateData{
Package: pkgName,
ParsedMetrics: []metricsgen.ParsedMetricField{
{
TypeName: "Counter",
FieldName: "myCounter",
MetricName: "my_counter",
Labels: "\"label1\",\"label2\"",
},
},
},
},
{
name: "ignore non-metric field",
metricsStruct: `type Metrics struct {
myCounter metrics.Counter
nonMetric string
}`,
expected: metricsgen.TemplateData{
Package: pkgName,
ParsedMetrics: []metricsgen.ParsedMetricField{
{
TypeName: "Counter",
FieldName: "myCounter",
MetricName: "my_counter",
},
},
},
},
}
for _, testCase := range metricsTests {
t.Run(testCase.name, func(t *testing.T) {
dir, err := os.MkdirTemp(os.TempDir(), "metricsdir")
if err != nil {
t.Fatalf("unable to create directory: %v", err)
}
defer os.Remove(dir)
f, err := os.Create(filepath.Join(dir, "metrics.go"))
if err != nil {
t.Fatalf("unable to open file: %v", err)
}
pkgLine := fmt.Sprintf("package %s\n", pkgName)
importClause := `
import(
"github.com/go-kit/kit/metrics"
)
`
_, err = io.WriteString(f, pkgLine)
require.NoError(t, err)
_, err = io.WriteString(f, importClause)
require.NoError(t, err)
_, err = io.WriteString(f, testCase.metricsStruct)
require.NoError(t, err)
td, err := metricsgen.ParseMetricsDir(dir, "Metrics")
if testCase.shouldError {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, testCase.expected, td)
}
})
}
}
func TestParseAliasedMetric(t *testing.T) {
aliasedData := `
package mypkg
import(
mymetrics "github.com/go-kit/kit/metrics"
)
type Metrics struct {
m mymetrics.Gauge
}
`
dir, err := os.MkdirTemp(os.TempDir(), "metricsdir")
if err != nil {
t.Fatalf("unable to create directory: %v", err)
}
defer os.Remove(dir)
f, err := os.Create(filepath.Join(dir, "metrics.go"))
if err != nil {
t.Fatalf("unable to open file: %v", err)
}
_, err = io.WriteString(f, aliasedData)
if err != nil {
t.Fatalf("unable to write to file: %v", err)
}
td, err := metricsgen.ParseMetricsDir(dir, "Metrics")
require.NoError(t, err)
expected :=
metricsgen.TemplateData{
Package: "mypkg",
ParsedMetrics: []metricsgen.ParsedMetricField{
{
TypeName: "Gauge",
FieldName: "m",
MetricName: "m",
},
},
}
require.Equal(t, expected, td)
}

View File

@@ -0,0 +1,30 @@
// Code generated by metricsgen. DO NOT EDIT.
package basic
import (
"github.com/go-kit/kit/metrics/discard"
prometheus "github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics {
labels := []string{}
for i := 0; i < len(labelsAndValues); i += 2 {
labels = append(labels, labelsAndValues[i])
}
return &Metrics{
Height: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "height",
Help: "simple metric that tracks the height of the chain.",
}, labels).With(labelsAndValues...),
}
}
func NopMetrics() *Metrics {
return &Metrics{
Height: discard.NewGauge(),
}
}

View File

@@ -0,0 +1,11 @@
package basic
import "github.com/go-kit/kit/metrics"
//go:generate go run ../../../../scripts/metricsgen -struct=Metrics
// Metrics contains metrics exposed by this package.
type Metrics struct {
// simple metric that tracks the height of the chain.
Height metrics.Gauge
}

View File

@@ -0,0 +1,30 @@
// Code generated by metricsgen. DO NOT EDIT.
package commented
import (
"github.com/go-kit/kit/metrics/discard"
prometheus "github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics {
labels := []string{}
for i := 0; i < len(labelsAndValues); i += 2 {
labels = append(labels, labelsAndValues[i])
}
return &Metrics{
Field: prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "field",
Help: "Height of the chain. We expect multi-line comments to parse correctly.",
}, labels).With(labelsAndValues...),
}
}
func NopMetrics() *Metrics {
return &Metrics{
Field: discard.NewGauge(),
}
}

View File

@@ -0,0 +1,11 @@
package commented
import "github.com/go-kit/kit/metrics"
//go:generate go run ../../../../scripts/metricsgen -struct=Metrics
type Metrics struct {
// Height of the chain.
// We expect multi-line comments to parse correctly.
Field metrics.Gauge
}

View File

@@ -0,0 +1,55 @@
// Code generated by metricsgen. DO NOT EDIT.
package tags
import (
"github.com/go-kit/kit/metrics/discard"
prometheus "github.com/go-kit/kit/metrics/prometheus"
stdprometheus "github.com/prometheus/client_golang/prometheus"
)
func PrometheusMetrics(namespace string, labelsAndValues ...string) *Metrics {
labels := []string{}
for i := 0; i < len(labelsAndValues); i += 2 {
labels = append(labels, labelsAndValues[i])
}
return &Metrics{
WithLabels: prometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "with_labels",
Help: "",
}, append(labels, "step", "time")).With(labelsAndValues...),
WithExpBuckets: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "with_exp_buckets",
Help: "",
Buckets: stdprometheus.ExponentialBuckets(.1, 100, 8),
}, labels).With(labelsAndValues...),
WithBuckets: prometheus.NewHistogramFrom(stdprometheus.HistogramOpts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "with_buckets",
Help: "",
Buckets: []float64{1, 2, 3, 4, 5},
}, labels).With(labelsAndValues...),
Named: prometheus.NewCounterFrom(stdprometheus.CounterOpts{
Namespace: namespace,
Subsystem: MetricsSubsystem,
Name: "metric_with_name",
Help: "",
}, labels).With(labelsAndValues...),
}
}
func NopMetrics() *Metrics {
return &Metrics{
WithLabels: discard.NewCounter(),
WithExpBuckets: discard.NewHistogram(),
WithBuckets: discard.NewHistogram(),
Named: discard.NewCounter(),
}
}

View File

@@ -0,0 +1,12 @@
package tags
import "github.com/go-kit/kit/metrics"
//go:generate go run ../../../../scripts/metricsgen -struct=Metrics
type Metrics struct {
WithLabels metrics.Counter `metrics_labels:"step,time"`
WithExpBuckets metrics.Histogram `metrics_buckettype:"exp" metrics_bucketsizes:".1,100,8"`
WithBuckets metrics.Histogram `metrics_bucketsizes:"1, 2, 3, 4, 5"`
Named metrics.Counter `metrics_name:"metric_with_name"`
}