Skip to content

Commit

Permalink
feat: generate metrics documentation (#1351)
Browse files Browse the repository at this point in the history
## Which problem is this PR solving?

This PR implements the logic to parse out all metrics defined in the
Refinery code base and generates a markdown table for them.

- part 2 of #1152 

## Short description of the changes

- add two new commands to the `convert` tool to generate the
metricsMeta.yaml file and a metrics.md file
  • Loading branch information
VinozzZ authored and TylerHelmuth committed Oct 16, 2024
1 parent 209cf99 commit eb061f0
Show file tree
Hide file tree
Showing 8 changed files with 608 additions and 10 deletions.
12 changes: 9 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ require (
gopkg.in/yaml.v3 v3.0.1
)

require (
golang.org/x/mod v0.21.0 // indirect
golang.org/x/sync v0.8.0 // indirect
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
Expand All @@ -73,9 +78,10 @@ require (
github.com/tidwall/pretty v1.2.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.25.0
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect
)
Expand Down
18 changes: 12 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,19 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo=
google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E=
Expand Down
77 changes: 77 additions & 0 deletions metrics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Metrics Documentation
# Automatically generated on 2024-09-26 at 18:58:58 UTC

This document contains the description of various metrics used in the system.

| Name | Type | Unit | Description |
|------|------|------|-------------|
| collect_cache_buffer_overrun | Counter | Dimensionless | The number of times the trace overwritten in the circular buffer has not yet been sent |
| collect_cache_capacity | Gauge | Dimensionless | The number of traces that can be stored in the cache |
| collect_cache_entries | Histogram | Dimensionless | The number of traces currently stored in the cache |
| cuckoo_current_capacity | Gauge | Dimensionless | current capacity of the cuckoo filter |
| cuckoo_future_load_factor | Gauge | Percent | the fraction of slots occupied in the future cuckoo filter |
| cuckoo_current_load_factor | Gauge | Percent | the fraction of slots occupied in the current cuckoo filter |
| cache_recent_dropped_traces | Gauge | Dimensionless | the current size of the most recent dropped trace cache |
| collect_sent_reasons_cache_entries | Histogram | Dimensionless | Number of entries in the sent reasons cache |
| is_ready | Gauge | Dimensionless | Whether the system is ready to receive traffic |
| is_alive | Gauge | Dimensionless | Whether the system is alive and reporting in |
| redis_pubsub_published | Counter | Dimensionless | Number of messages published to Redis PubSub |
| redis_pubsub_received | Counter | Dimensionless | Number of messages received from Redis PubSub |
| local_pubsub_published | Counter | Dimensionless | The total number of messages sent via the local pubsub implementation |
| local_pubsub_received | Counter | Dimensionless | The total number of messages received via the local pubsub implementation |
| num_file_peers | Gauge | Dimensionless | Number of peers in the file peer list |
| num_peers | Gauge | Dimensionless | the active number of peers in the cluster |
| peer_hash | Gauge | Dimensionless | the hash of the current list of peers |
| peer_messages | Counter | Dimensionless | the number of messages received by the peers service |
| _num_dropped_by_drop_rule | Counter | Dimensionless | Number of traces dropped by the drop rule |
| _num_dropped | Counter | Dimensionless | Number of traces dropped by configured sampler |
| _num_kept | Counter | Dimensionless | Number of traces kept by configured sampler |
| _sample_rate | Histogram | Dimensionless | Sample rate for traces |
| enqueue_errors | Counter | Dimensionless | The number of errors encountered when enqueueing events |
| response_20x | Counter | Dimensionless | The number of successful responses from Honeycomb |
| response_errors | Counter | Dimensionless | The number of errors encountered when sending events to Honeycomb |
| queued_items | UpDown | Dimensionless | The number of events queued for transmission to Honeycomb |
| queue_time | Histogram | Microseconds | The time spent in the queue before being sent to Honeycomb |
| trace_duration_ms | Histogram | Milliseconds | time taken to process a trace from arrival to send |
| trace_span_count | Histogram | Dimensionless | number of spans in a trace |
| collector_incoming_queue | Histogram | Dimensionless | number of spans currently in the incoming queue |
| collector_peer_queue_length | Gauge | Dimensionless | number of spans in the peer queue |
| collector_incoming_queue_length | Gauge | Dimensionless | number of spans in the incoming queue |
| collector_peer_queue | Histogram | Dimensionless | number of spans currently in the peer queue |
| collector_cache_size | Gauge | Dimensionless | number of traces currently stored in the trace cache |
| memory_heap_allocation | Gauge | Bytes | current heap allocation |
| span_received | Counter | Dimensionless | number of spans received by the collector |
| span_processed | Counter | Dimensionless | number of spans processed by the collector |
| spans_waiting | UpDown | Dimensionless | number of spans waiting to be processed by the collector |
| trace_sent_cache_hit | Counter | Dimensionless | number of late spans received for traces that have already been sent |
| trace_accepted | Counter | Dimensionless | number of new traces received by the collector |
| trace_send_kept | Counter | Dimensionless | number of traces that has been kept |
| trace_send_dropped | Counter | Dimensionless | number of traces that has been dropped |
| trace_send_has_root | Counter | Dimensionless | number of kept traces that have a root span |
| trace_send_no_root | Counter | Dimensionless | number of kept traces that do not have a root span |
| trace_forwarded_on_peer_change | Gauge | Dimensionless | number of traces forwarded due to peer membership change |
| trace_redistribution_count | Gauge | Dimensionless | number of traces redistributed due to peer membership change |
| trace_send_on_shutdown | Counter | Dimensionless | number of traces sent during shutdown |
| trace_forwarded_on_shutdown | Counter | Dimensionless | number of traces forwarded during shutdown |
| trace_send_got_root | Counter | Dimensionless | number of traces that are ready for decision due to root span arrival |
| trace_send_expired | Counter | Dimensionless | number of traces that are ready for decision due to TraceTimeout or SendDelay |
| trace_send_span_limit | Counter | Dimensionless | number of traces that are ready for decision due to span limit |
| trace_send_ejected_full | Counter | Dimensionless | number of traces that are ready for decision due to cache capacity overrun |
| trace_send_ejected_memsize | Counter | Dimensionless | number of traces that are ready for decision due to memory overrun |
| trace_send_late_span | Counter | Dimensionless | number of spans that are sent due to late span arrival |
| dropped_from_stress | Counter | Dimensionless | number of traces dropped due to stress relief |
| cluster_stress_level | Gauge | Dimensionless | The overall stress level of the cluster |
| individual_stress_level | Gauge | Dimensionless | The stress level of the individual node |
| stress_level | Gauge | Dimensionless | The stress level that's being used to determine whether to activate stress relief |
| stress_relief_activated | Gauge | Dimensionless | Whether stress relief is currently activated |
| _router_proxied | Counter | Dimensionless | the number of events proxied to another refinery |
| _router_event | Counter | Dimensionless | the number of events received |
| config_hash | Gauge | Dimensionless | The hash of the current configuration |
| rule_config_hash | Gauge | Dimensionless | The hash of the current rules configuration |
| queue_length | Gauge | Dimensionless | number of events waiting to be sent to destination |
| queue_overflow | Counter | Dimensionless | number of events dropped due to queue overflow |
| send_errors | Counter | Dimensionless | number of errors encountered while sending events to destination |
| send_retries | Counter | Dimensionless | number of times a batch of events was retried |
| batches_sent | Counter | Dimensionless | number of batches of events sent to destination |
| messages_sent | Counter | Dimensionless | number of messages sent to destination |
| response_decode_errors | Counter | Dimensionless | number of errors encountered while decoding responses from destination |
20 changes: 19 additions & 1 deletion tools/convert/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: all
#: build all the things
all: template names sample complete docs validate build
all: template names sample metrics complete docs validate build

.PHONY: build
#: build the binary
Expand Down Expand Up @@ -34,6 +34,24 @@ sample:
@echo
go run . sample --output=minimal_config.yaml

.PHONY: metricsmeta
metricsmeta:
@echo
@echo "+++ generating metrics metadata file"
@echo
go run . metricsmeta

.PHONY: metricsdoc
metrics:
@echo
@echo "+++ generating metrics markdown documentation file"
@echo
go run . metrics --output=../../metrics.md


.PHONY: metrics
metrics: metricsmeta metricsdoc

.PHONY: complete
#: generate the complete config
complete:
Expand Down
44 changes: 44 additions & 0 deletions tools/convert/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func main() {
convert validate rules: validate a rules file against the 2.0 format
convert doc config: generate markdown documentation for the config file
convert doc rules: generate markdown documentation for the rules file
convert metricsMeta: generates the metrics metadata file
convert metrics: generates markdown documentation for all refinery metrics
Examples:
convert config --input config.toml --output config.yaml
Expand Down Expand Up @@ -173,6 +175,20 @@ func main() {
os.Exit(1)
}
os.Exit(0)
case "metricsmeta":
err := GenerateMetricsMetadata()
if err != nil {
fmt.Fprintf(os.Stderr, `error generating metrics metadata: %v\n`, err)
os.Exit(1)
}
os.Exit(0)
case "metrics":
err := GenerateMetricsDoc(output)
if err != nil {
fmt.Fprintf(os.Stderr, `error generating metrics documentation: %v\n`, err)
os.Exit(1)
}
os.Exit(0)
case "config", "rules", "validate", "helm":
// do nothing yet because we need to parse the input file
default:
Expand Down Expand Up @@ -373,6 +389,34 @@ func GenerateTemplate(w io.Writer) {
}
}

func GenerateMetricsDoc(w io.Writer) error {
data, err := os.ReadFile("metricsMeta.yaml")
if err != nil {
return err
}

var metricsUsages []MetricsUsage
err = yaml.Unmarshal(data, &metricsUsages)
if err != nil {
return err
}

tmpl := template.New("metrics.tmpl")
tmpl.Funcs(helpers())
tmpl, err = tmpl.ParseFS(filesystem, "templates/metrics.tmpl")
if err != nil {
return err
}

err = tmpl.Execute(w, metricsUsages)
if err != nil {
return err
}

fmt.Println("Metrics usages have been written to the output file")
return nil
}

// This generates a nested list of the groups and names.
func PrintNames(w io.Writer) {
metadata := loadConfigMetadata()
Expand Down
157 changes: 157 additions & 0 deletions tools/convert/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import (
"fmt"
"go/ast"
"go/types"
"os"
"slices"
"strings"

"golang.org/x/exp/maps"
"golang.org/x/tools/go/packages"
"gopkg.in/yaml.v3"
)

type MetricsUsage struct {
Name string
Type string
Unit string
Description string
}

const metricsImportPath = "github.com/honeycombio/refinery/metrics"

func GenerateMetricsMetadata() error {
output, err := os.Create("metricsMeta.yaml")
if err != nil {
return fmt.Errorf("error creating output file: %v", err)
}
defer output.Close()

// Configuration to load Go packages.
cfg := &packages.Config{
Mode: packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo,
}

// Load the package from the current directory.
pkgs, err := packages.Load(cfg, "github.com/honeycombio/refinery/...")
if err != nil {
return fmt.Errorf("error loading packages: %v", err)
}

usages := make([]MetricsUsage, 0)
// Traverse each package and file.
for _, pkg := range pkgs {
if !slices.Contains(maps.Keys(pkg.Imports), metricsImportPath) {
continue
}

var found bool
// Iterate over the syntax trees (ASTs) of each file in the package.
for _, syntax := range pkg.Syntax {
// Inspect the AST for each file.
ast.Inspect(syntax, func(n ast.Node) bool {
// Look for all slice type declarations
if decl, ok := n.(*ast.CompositeLit); ok {

if arrayType, ok := decl.Type.(*ast.ArrayType); ok {
// Check if the element type of the array is a selector expression
if selector, ok := arrayType.Elt.(*ast.SelectorExpr); ok {
// Check if the package and type name match "metrics.Metadata"
if pkgIdent, ok := selector.X.(*ast.Ident); ok && pkgIdent.Name == "metrics" && selector.Sel.Name == "Metadata" {

// Now extract the fields from the composite literal
for _, elt := range decl.Elts {
if comp, ok := elt.(*ast.CompositeLit); ok {
var usage MetricsUsage
for _, elt := range comp.Elts {
if kvExpr, ok := elt.(*ast.KeyValueExpr); ok {
field := exprToString(kvExpr.Key, pkg)
value := exprToString(kvExpr.Value, pkg)

switch field {
case "Name":
usage.Name = value
case "Type":
usage.Type = value
case "Unit":
usage.Unit = value
case "Description":
usage.Description = value

}
}
}

if usage.Name == "" {
continue
}
usages = append(usages, usage)
found = true
}
}
}
}
}
}
return true
})

}
if !found {
return fmt.Errorf("Missing metrics.Metadata declaration in the package %s", pkg.Name)
}
}

if len(usages) == 0 {
return fmt.Errorf("No metrics.Metadata declarations found in all packages")
}

err = writeMetricsToYAML(usages, output)
if err != nil {
return fmt.Errorf("error writing metrics to YAML: %v", err)
}

fmt.Printf("Metrics usages have been written to %s\n", output.Name())
return nil

}

// exprToString is a helper function to convert ast.Expr to a string representation
func exprToString(expr ast.Expr, pkg *packages.Package) string {
var strVal string
switch v := expr.(type) {
case *ast.Ident:
if obj := pkg.TypesInfo.ObjectOf(v); obj != nil {
// Get the value of the variable (if constant)
if constVal, ok := obj.(*types.Const); ok {
strVal = constVal.Val().String()
break
}

}
strVal = v.Name
case *ast.BasicLit:
strVal = v.Value
case *ast.SelectorExpr:
strVal = v.Sel.Name
default:
strVal = fmt.Sprintf("%T", expr)
}

return strings.Trim(strVal, "\"")
}

func writeMetricsToYAML(metricsUsages []MetricsUsage, output *os.File) error {
// Create a new YAML encoder and write the metrics
encoder := yaml.NewEncoder(output)
defer encoder.Close()

err := encoder.Encode(metricsUsages)
if err != nil {
return fmt.Errorf("error encoding YAML: %v", err)
}

return nil
}
Loading

0 comments on commit eb061f0

Please sign in to comment.