@@ -27,7 +27,7 @@ import (
27
27
"github.com/operator-framework/operator-sdk/hack/generate/samples/internal/pkg"
28
28
)
29
29
30
- // Memcached defines the Memcached Sample in GO using webhooks
30
+ // Memcached defines the Memcached Sample in GO using webhooks and monitoring code
31
31
type Memcached struct {
32
32
ctx * pkg.SampleContext
33
33
}
@@ -39,9 +39,9 @@ var prometheusAPIVersion = "v0.59.0"
39
39
// GenerateSample will call all actions to create the directory and generate the sample
40
40
// Note that it should NOT be called in the e2e tests.
41
41
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 " )
43
43
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 )
45
45
46
46
generateWithMonitoring = false
47
47
if strings .HasSuffix (samplesPath , "monitoring" ) {
@@ -53,11 +53,11 @@ func GenerateSample(binaryPath, samplesPath string) {
53
53
memcached .Run ()
54
54
}
55
55
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
57
57
// Note that sample directory will be re-created and the context data for the sample
58
58
// will be set such as the domain and GVK.
59
59
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" )
61
61
mh .ctx .Destroy ()
62
62
63
63
log .Infof ("creating directory" )
@@ -71,7 +71,7 @@ func (mh *Memcached) Prepare() {
71
71
mh .ctx .Kind = "Memcached"
72
72
}
73
73
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
75
75
func (mh * Memcached ) Run () {
76
76
77
77
if strings .Contains (mh .ctx .Dir , "v4-alpha" ) {
@@ -151,6 +151,13 @@ func (mh *Memcached) Run() {
151
151
_ , err = mh .ctx .Run (cmd )
152
152
pkg .CheckError ("Running go mod tidy" , err )
153
153
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
+
154
161
log .Infof ("creating the bundle" )
155
162
err = mh .ctx .GenerateBundle ()
156
163
pkg .CheckError ("creating the bundle" , err )
@@ -548,6 +555,35 @@ func (mh *Memcached) implementingMetrics() {
548
555
goFilesHeader ,
549
556
metricsFragment )
550
557
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 )
551
587
}
552
588
553
589
func (mh * Memcached ) implementingAlerts () {
@@ -618,16 +654,14 @@ func (mh *Memcached) implementingPromRuleCi() {
618
654
}
619
655
620
656
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/"
624
658
625
659
// 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 )
627
661
pkg .CheckError ("creating runbooks directory" , err )
628
662
629
663
// 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" )
631
665
_ , err = os .Create (memcachedDeploymentSizeUndesiredRunbookPath )
632
666
pkg .CheckError ("creating MemcachedDeploymentSizeUndesired runbook file" , err )
633
667
@@ -638,7 +672,7 @@ func (mh *Memcached) implementingRunbooks() {
638
672
pkg .CheckError ("adding MemcachedDeploymentSizeUndesired runbook content" , err )
639
673
640
674
// 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" )
642
676
_ , err = os .Create (memcachedOperatorDownRunbookPath )
643
677
pkg .CheckError ("creating MemcachedOperatorDown runbook file" , err )
644
678
@@ -776,6 +810,12 @@ func (mh *Memcached) customizingMakefile() {
776
810
`$(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -` ,
777
811
makefileFragment )
778
812
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 )
779
819
}
780
820
781
821
const metricsFragment = `
@@ -787,21 +827,157 @@ import (
787
827
"sigs.k8s.io/controller-runtime/pkg/metrics"
788
828
)
789
829
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
+
790
847
var (
791
848
// MemcachedDeploymentSizeUndesiredCountTotal will count how many times was required
792
849
// to perform the operation to ensure that the number of replicas on the cluster
793
850
// is the same as the quantity desired and specified via the custom resource size spec.
794
851
MemcachedDeploymentSizeUndesiredCountTotal = prometheus.NewCounter(
795
852
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 ,
798
855
},
799
856
)
800
857
)
801
- // Register metrics with the global prometheus registry
858
+
859
+ // RegisterMetrics will register metrics with the global prometheus registry
802
860
func RegisterMetrics() {
803
861
metrics.Registry.MustRegister(MemcachedDeploymentSizeUndesiredCountTotal)
804
862
}
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
+ }
805
981
`
806
982
807
983
const alertsFragment = `
@@ -820,7 +996,7 @@ const (
820
996
deploymentSizeUndesiredAlert = "MemcachedDeploymentSizeUndesired"
821
997
operatorDownAlert = "MemcachedOperatorDown"
822
998
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/"
824
1000
)
825
1001
826
1002
// NewPrometheusRule creates new PrometheusRule(CR) for the operator to have alerts and recording rules
@@ -930,15 +1106,15 @@ tests:
930
1106
description: "Memcached-sample deployment size was not as desired more than 3 times in the last 5 minutes."
931
1107
exp_labels:
932
1108
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"
934
1110
- eval_time: 5m
935
1111
alertname: MemcachedOperatorDown
936
1112
exp_alerts:
937
1113
- exp_annotations:
938
1114
description: "No running memcached-operator pods were detected in the last 5 min."
939
1115
exp_labels:
940
1116
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"
942
1118
# it must not trigger before 15m
943
1119
- eval_time: 14m
944
1120
alertname: MemcachedDeploymentSizeUndesired
@@ -954,15 +1130,15 @@ tests:
954
1130
description: "Memcached-sample deployment size was not as desired more than 3 times in the last 5 minutes."
955
1131
exp_labels:
956
1132
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"
958
1134
- eval_time: 15m
959
1135
alertname: MemcachedOperatorDown
960
1136
exp_alerts:
961
1137
- exp_annotations:
962
1138
description: "No running memcached-operator pods were detected in the last 5 min."
963
1139
exp_labels:
964
1140
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"
966
1142
`
967
1143
968
1144
const ruleSpecDumperFragment = `
@@ -1207,6 +1383,15 @@ prom-rules-verify: build-prom-spec-dumper
1207
1383
1208
1384
`
1209
1385
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
+
1210
1395
const webhooksFragment = `
1211
1396
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
1212
1397
//+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