Skip to content

Commit f6c820d

Browse files
authored
RFE - Add automated documentation for metrics (#6114)
Jira-Ticket: https://issues.redhat.com/browse/CNV-21334 Signed-off-by: Aviv Litman <alitman@redhat.com> apply the automated documentation for metrics Signed-off-by: Aviv Litman <alitman@redhat.com> Fix according to Joao's review Signed-off-by: Aviv Litman <alitman@redhat.com> Update testdata Signed-off-by: João Vilaça <jvilaca@redhat.com> Update generator code Signed-off-by: João Vilaça <jvilaca@redhat.com> Rename to memcached-with-customization and remove docs bin Signed-off-by: João Vilaça <jvilaca@redhat.com> Fix comments Signed-off-by: Aviv Litman <alitman@redhat.com> Signed-off-by: Aviv Litman <alitman@redhat.com>
1 parent a95c9f2 commit f6c820d

File tree

19 files changed

+556
-41
lines changed

19 files changed

+556
-41
lines changed

hack/generate/samples/internal/go/generate.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ package golang
1717
import (
1818
"path/filepath"
1919

20-
withwebhooks "github.com/operator-framework/operator-sdk/hack/generate/samples/internal/go/memcached-with-webhooks"
20+
withcustomization "github.com/operator-framework/operator-sdk/hack/generate/samples/internal/go/memcached-with-customization"
2121
)
2222

2323
func GenerateMemcachedSamples(binaryPath, rootPath string) {
@@ -26,8 +26,8 @@ func GenerateMemcachedSamples(binaryPath, rootPath string) {
2626
// to use the deploy.image/v1-alpha plugin to do the scaffold instead
2727
// to create an empty scaffold add add all code. So that, we can also
2828
// ensure that the tutorial follows the good practices
29-
withwebhooks.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v3"))
30-
withwebhooks.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v3", "monitoring"))
31-
withwebhooks.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v4-alpha"))
32-
withwebhooks.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v4-alpha", "monitoring"))
29+
withcustomization.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v3"))
30+
withcustomization.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v3", "monitoring"))
31+
withcustomization.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v4-alpha"))
32+
withcustomization.GenerateSample(binaryPath, filepath.Join(rootPath, "go", "v4-alpha", "monitoring"))
3333
}

hack/generate/samples/internal/go/memcached-with-webhooks/memcached_with_webhooks.go renamed to hack/generate/samples/internal/go/memcached-with-customization/memcached_with_customization.go

Lines changed: 205 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
"github.com/operator-framework/operator-sdk/hack/generate/samples/internal/pkg"
2828
)
2929

30-
// Memcached defines the Memcached Sample in GO using webhooks
30+
// Memcached defines the Memcached Sample in GO using webhooks and monitoring code
3131
type Memcached struct {
3232
ctx *pkg.SampleContext
3333
}
@@ -39,9 +39,9 @@ var prometheusAPIVersion = "v0.59.0"
3939
// GenerateSample will call all actions to create the directory and generate the sample
4040
// Note that it should NOT be called in the e2e tests.
4141
func GenerateSample(binaryPath, samplesPath string) {
42-
log.Infof("starting to generate Go memcached sample with webhooks")
42+
log.Infof("starting to generate Go memcached sample with webhooks and metrics documentation")
4343
ctx, err := pkg.NewSampleContext(binaryPath, filepath.Join(samplesPath, "memcached-operator"), "GO111MODULE=on")
44-
pkg.CheckError("generating Go memcached with webhooks context", err)
44+
pkg.CheckError("generating Go memcached with webhooks and metrics documentation context", err)
4545

4646
generateWithMonitoring = false
4747
if strings.HasSuffix(samplesPath, "monitoring") {
@@ -53,11 +53,11 @@ func GenerateSample(binaryPath, samplesPath string) {
5353
memcached.Run()
5454
}
5555

56-
// Prepare the Context for the Memcached with WebHooks Go Sample
56+
// Prepare the Context for the Memcached with webhooks and metrics documentation Go Sample
5757
// Note that sample directory will be re-created and the context data for the sample
5858
// will be set such as the domain and GVK.
5959
func (mh *Memcached) Prepare() {
60-
log.Infof("destroying directory for Memcached with Webhooks Go samples")
60+
log.Infof("destroying directory for Memcached with webhooks and metrics documentation Go samples")
6161
mh.ctx.Destroy()
6262

6363
log.Infof("creating directory")
@@ -71,7 +71,7 @@ func (mh *Memcached) Prepare() {
7171
mh.ctx.Kind = "Memcached"
7272
}
7373

74-
// Run the steps to create the Memcached with Webhooks Go Sample
74+
// Run the steps to create the Memcached with metrics and webhooks Go Sample
7575
func (mh *Memcached) Run() {
7676

7777
if strings.Contains(mh.ctx.Dir, "v4-alpha") {
@@ -151,6 +151,13 @@ func (mh *Memcached) Run() {
151151
_, err = mh.ctx.Run(cmd)
152152
pkg.CheckError("Running go mod tidy", err)
153153

154+
if generateWithMonitoring {
155+
cmd := exec.Command("make", "generate-metricsdocs")
156+
cmd.Dir = mh.ctx.Dir
157+
_, err = mh.ctx.Run(cmd)
158+
pkg.CheckError("Running make generate-metricsdocs", err)
159+
}
160+
154161
log.Infof("creating the bundle")
155162
err = mh.ctx.GenerateBundle()
156163
pkg.CheckError("creating the bundle", err)
@@ -548,6 +555,35 @@ func (mh *Memcached) implementingMetrics() {
548555
goFilesHeader,
549556
metricsFragment)
550557
pkg.CheckError("adding metrics content", err)
558+
559+
// Add metricsdocs directory
560+
err = os.Mkdir(filepath.Join(mh.ctx.Dir, "monitoring/metricsdocs"), os.ModePerm)
561+
pkg.CheckError("creating metricsdocs directory", err)
562+
563+
// Create metricsdocs file
564+
metricsdocsPath := filepath.Join(mh.ctx.Dir, "monitoring/metricsdocs/metricsdocs.go")
565+
_, err = os.Create(metricsdocsPath)
566+
pkg.CheckError("creating metricsdocs file", err)
567+
568+
// Add go files header
569+
err = kbutil.InsertCode(metricsdocsPath,
570+
"",
571+
goFilesHeader)
572+
pkg.CheckError("adding go files header", err)
573+
574+
// Create metricsdocs generator tool
575+
err = kbutil.InsertCode(metricsdocsPath,
576+
goFilesHeader,
577+
metricsdocsFragment)
578+
pkg.CheckError("creating metricsdocs generator tool", err)
579+
580+
// Create docs directory
581+
err = os.Mkdir(filepath.Join(mh.ctx.Dir, "docs"), os.ModePerm)
582+
pkg.CheckError("creating docs directory", err)
583+
584+
// Create monitoring directory
585+
err = os.Mkdir(filepath.Join(mh.ctx.Dir, "docs/monitoring"), os.ModePerm)
586+
pkg.CheckError("creating monitoring directory", err)
551587
}
552588

553589
func (mh *Memcached) implementingAlerts() {
@@ -618,16 +654,14 @@ func (mh *Memcached) implementingPromRuleCi() {
618654
}
619655

620656
func (mh *Memcached) implementingRunbooks() {
621-
// Create docs directory
622-
err := os.Mkdir(filepath.Join(mh.ctx.Dir, "docs"), os.ModePerm)
623-
pkg.CheckError("creating docs directory", err)
657+
runbooksPath := "docs/monitoring/runbooks/"
624658

625659
// Create runbooks directory
626-
err = os.Mkdir(filepath.Join(mh.ctx.Dir, "docs/runbooks"), os.ModePerm)
660+
err := os.Mkdir(filepath.Join(mh.ctx.Dir, runbooksPath), os.ModePerm)
627661
pkg.CheckError("creating runbooks directory", err)
628662

629663
// Create MemcachedDeploymentSizeUndesired runbook file
630-
memcachedDeploymentSizeUndesiredRunbookPath := filepath.Join(mh.ctx.Dir, "docs/runbooks/memcachedDeploymentSizeUndesired.md")
664+
memcachedDeploymentSizeUndesiredRunbookPath := filepath.Join(mh.ctx.Dir, runbooksPath, "memcachedDeploymentSizeUndesired.md")
631665
_, err = os.Create(memcachedDeploymentSizeUndesiredRunbookPath)
632666
pkg.CheckError("creating MemcachedDeploymentSizeUndesired runbook file", err)
633667

@@ -638,7 +672,7 @@ func (mh *Memcached) implementingRunbooks() {
638672
pkg.CheckError("adding MemcachedDeploymentSizeUndesired runbook content", err)
639673

640674
// Create MemcachedOperatorDown runbook file
641-
memcachedOperatorDownRunbookPath := filepath.Join(mh.ctx.Dir, "docs/runbooks/memcachedOperatorDown.md")
675+
memcachedOperatorDownRunbookPath := filepath.Join(mh.ctx.Dir, runbooksPath, "memcachedOperatorDown.md")
642676
_, err = os.Create(memcachedOperatorDownRunbookPath)
643677
pkg.CheckError("creating MemcachedOperatorDown runbook file", err)
644678

@@ -776,6 +810,12 @@ func (mh *Memcached) customizingMakefile() {
776810
`$(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -`,
777811
makefileFragment)
778812
pkg.CheckError("adding prom-rule-ci target to the makefile", err)
813+
814+
// Add metrics documentation
815+
err = kbutil.InsertCode(makefilePath,
816+
`$(MAKE) docker-push IMG=$(CATALOG_IMG)`,
817+
metricsdocsMakefileFragment)
818+
pkg.CheckError("adding metrics documentation", err)
779819
}
780820

781821
const metricsFragment = `
@@ -787,21 +827,157 @@ import (
787827
"sigs.k8s.io/controller-runtime/pkg/metrics"
788828
)
789829
830+
// MetricDescription is an exported struct that defines the metric description (Name, Help)
831+
// as a new type named MetricDescription.
832+
type MetricDescription struct {
833+
Name string
834+
Help string
835+
Type string
836+
}
837+
838+
// metricsDescription is a map of string keys (metrics) to MetricDescription values (Name, Help).
839+
var metricDescription = map[string]MetricDescription{
840+
"MemcachedDeploymentSizeUndesiredCountTotal": {
841+
Name: "memcached_deployment_size_undesired_count_total",
842+
Help: "Total number of times the deployment size was not as desired.",
843+
Type: "Counter",
844+
},
845+
}
846+
790847
var (
791848
// MemcachedDeploymentSizeUndesiredCountTotal will count how many times was required
792849
// to perform the operation to ensure that the number of replicas on the cluster
793850
// is the same as the quantity desired and specified via the custom resource size spec.
794851
MemcachedDeploymentSizeUndesiredCountTotal = prometheus.NewCounter(
795852
prometheus.CounterOpts{
796-
Name: "memcached_deployment_size_undesired_count_total",
797-
Help: "Total number of times the deployment size was not as desired.",
853+
Name: metricDescription["MemcachedDeploymentSizeUndesiredCountTotal"].Name,
854+
Help: metricDescription["MemcachedDeploymentSizeUndesiredCountTotal"].Help,
798855
},
799856
)
800857
)
801-
// Register metrics with the global prometheus registry
858+
859+
// RegisterMetrics will register metrics with the global prometheus registry
802860
func RegisterMetrics() {
803861
metrics.Registry.MustRegister(MemcachedDeploymentSizeUndesiredCountTotal)
804862
}
863+
864+
// ListMetrics will create a slice with the metrics available in metricDescription
865+
func ListMetrics() []MetricDescription {
866+
v := make([]MetricDescription, 0, len(metricDescription))
867+
// Insert value (Name, Help) for each metric
868+
for _, value := range metricDescription {
869+
v = append(v, value)
870+
}
871+
872+
return v
873+
}
874+
`
875+
876+
const metricsdocsFragment = `
877+
878+
package main
879+
880+
import (
881+
"fmt"
882+
"sort"
883+
884+
"github.com/example/memcached-operator/monitoring"
885+
)
886+
887+
// please run "make generate-metricsdocs" to run this tool and update metrics documentation
888+
const (
889+
title = "# Operator Metrics\n"
890+
background = "This document aims to help users that are not familiar with metrics exposed by this operator.\n" +
891+
"The metrics documentation is auto-generated by the utility tool \"monitoring/metricsdocs\" and reflects all of the metrics that are exposed by the operator.\n\n"
892+
893+
KVSpecificMetrics = "## Operator Metrics List\n"
894+
895+
opening = title +
896+
background +
897+
KVSpecificMetrics
898+
899+
// footer
900+
footerHeading = "## Developing new metrics\n"
901+
footerContent = "After developing new metrics or changing old ones, please run \"make generate-metricsdocs\" to regenerate this document.\n\n" +
902+
"If you feel that the new metric doesn't follow these rules, please change \"monitoring/metricsdocs\" according to your needs.\n"
903+
904+
footer = footerHeading + footerContent
905+
)
906+
907+
// TODO: scaffolding these helpers with operator-lib: https://github.com/operator-framework/operator-lib.
908+
909+
// metricList contains the name, description, and type for each metric.
910+
func main() {
911+
metricList := metricDescriptionListToMetricList(monitoring.ListMetrics())
912+
sort.Sort(metricList)
913+
writeToStdOut(metricList)
914+
}
915+
916+
// writeToStdOut receives a list of metrics and prints them to STDOUT.
917+
func writeToStdOut(metricsList metricList) {
918+
fmt.Print(opening)
919+
metricsList.writeOut()
920+
fmt.Print(footer)
921+
}
922+
923+
// Metric is an exported struct that defines the metric
924+
// name, description, and type as a new type named Metric.
925+
type Metric struct {
926+
name string
927+
description string
928+
metricType string
929+
}
930+
931+
func metricDescriptionToMetric(md monitoring.MetricDescription) Metric {
932+
return Metric{
933+
name: md.Name,
934+
description: md.Help,
935+
metricType: md.Type,
936+
}
937+
}
938+
939+
// writeOut receives a metric of type metric and prints
940+
// the metric name, description, and type.
941+
func (m Metric) writeOut() {
942+
fmt.Println("###", m.name)
943+
fmt.Println(m.description, "Type: "+m.metricType+".")
944+
}
945+
946+
// metricList is an array that contain metrics from type metric,
947+
// as a new type named metricList.
948+
type metricList []Metric
949+
950+
// metricDescriptionListToMetricList collects the metrics exposed by the
951+
// operator, and inserts them into the metricList array.
952+
func metricDescriptionListToMetricList(mdl []monitoring.MetricDescription) metricList {
953+
res := make([]Metric, len(mdl))
954+
for i, md := range mdl {
955+
res[i] = metricDescriptionToMetric(md)
956+
}
957+
958+
return res
959+
}
960+
961+
// Len implements sort.Interface.Len
962+
func (m metricList) Len() int {
963+
return len(m)
964+
}
965+
966+
// Less implements sort.Interface.Less
967+
func (m metricList) Less(i, j int) bool {
968+
return m[i].name < m[j].name
969+
}
970+
971+
// Swap implements sort.Interface.Swap
972+
func (m metricList) Swap(i, j int) {
973+
m[i], m[j] = m[j], m[i]
974+
}
975+
976+
func (m metricList) writeOut() {
977+
for _, met := range m {
978+
met.writeOut()
979+
}
980+
}
805981
`
806982

807983
const alertsFragment = `
@@ -820,7 +996,7 @@ const (
820996
deploymentSizeUndesiredAlert = "MemcachedDeploymentSizeUndesired"
821997
operatorDownAlert = "MemcachedOperatorDown"
822998
operatorUpTotalRecordingRule = "memcached_operator_up_total"
823-
runbookURLBasePath = "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/runbooks/"
999+
runbookURLBasePath = "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/monitoring/runbooks/"
8241000
)
8251001
8261002
// NewPrometheusRule creates new PrometheusRule(CR) for the operator to have alerts and recording rules
@@ -930,15 +1106,15 @@ tests:
9301106
description: "Memcached-sample deployment size was not as desired more than 3 times in the last 5 minutes."
9311107
exp_labels:
9321108
severity: "warning"
933-
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/runbooks/MemcachedDeploymentSizeUndesired.md"
1109+
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/monitoring/runbooks/MemcachedDeploymentSizeUndesired.md"
9341110
- eval_time: 5m
9351111
alertname: MemcachedOperatorDown
9361112
exp_alerts:
9371113
- exp_annotations:
9381114
description: "No running memcached-operator pods were detected in the last 5 min."
9391115
exp_labels:
9401116
severity: "critical"
941-
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/runbooks/MemcachedOperatorDown.md"
1117+
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/monitoring/runbooks/MemcachedOperatorDown.md"
9421118
# it must not trigger before 15m
9431119
- eval_time: 14m
9441120
alertname: MemcachedDeploymentSizeUndesired
@@ -954,15 +1130,15 @@ tests:
9541130
description: "Memcached-sample deployment size was not as desired more than 3 times in the last 5 minutes."
9551131
exp_labels:
9561132
severity: "warning"
957-
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/runbooks/MemcachedDeploymentSizeUndesired.md"
1133+
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/monitoring/runbooks/MemcachedDeploymentSizeUndesired.md"
9581134
- eval_time: 15m
9591135
alertname: MemcachedOperatorDown
9601136
exp_alerts:
9611137
- exp_annotations:
9621138
description: "No running memcached-operator pods were detected in the last 5 min."
9631139
exp_labels:
9641140
severity: "critical"
965-
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/runbooks/MemcachedOperatorDown.md"
1141+
runbook_url: "https://github.com/operator-framework/operator-sdk/tree/master/testdata/go/v4-alpha/monitoring/memcached-operator/docs/monitoring/runbooks/MemcachedOperatorDown.md"
9661142
`
9671143

9681144
const ruleSpecDumperFragment = `
@@ -1207,6 +1383,15 @@ prom-rules-verify: build-prom-spec-dumper
12071383
12081384
`
12091385

1386+
const metricsdocsMakefileFragment = `
1387+
1388+
##@ Generate the metrics documentation
1389+
.PHONY: generate-metricsdocs
1390+
generate-metricsdocs:
1391+
mkdir -p $(shell pwd)/docs/monitoring
1392+
go run -ldflags="${LDFLAGS}" ./monitoring/metricsdocs > docs/monitoring/metrics.md
1393+
`
1394+
12101395
const webhooksFragment = `
12111396
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
12121397
//+kubebuilder:webhook:path=/validate-cache-example-com-v1alpha1-memcached,mutating=false,failurePolicy=fail,sideEffects=None,groups=cache.example.com,resources=memcacheds,verbs=create;update,versions=v1alpha1,name=vmemcached.kb.io,admissionReviewVersions=v1

testdata/go/v3/monitoring/memcached-operator/Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,10 @@ catalog-build: opm ## Build a catalog image.
271271
.PHONY: catalog-push
272272
catalog-push: ## Push a catalog image.
273273
$(MAKE) docker-push IMG=$(CATALOG_IMG)
274+
275+
##@ Generate the metrics documentation
276+
.PHONY: generate-metricsdocs
277+
generate-metricsdocs:
278+
mkdir -p $(shell pwd)/docs/monitoring
279+
go run -ldflags="${LDFLAGS}" ./monitoring/metricsdocs > docs/monitoring/metrics.md
280+

0 commit comments

Comments
 (0)