@@ -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
3131type 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.
4141func 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.
5959func (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
7575func (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
553589func (mh * Memcached ) implementingAlerts () {
@@ -618,16 +654,14 @@ func (mh *Memcached) implementingPromRuleCi() {
618654}
619655
620656func (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
781821const 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+
790847var (
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
802860func 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
807983const 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
9681144const 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+
12101395const 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
0 commit comments