Skip to content

Commit 254132d

Browse files
committed
generator: improvements + godoc
1 parent 5dc84dd commit 254132d

File tree

2 files changed

+99
-112
lines changed

2 files changed

+99
-112
lines changed

exp/metric-gen/generator/generator.go

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func (g CustomResourceConfigGenerator) RegisterMarkers(into *ctrlmarkers.Registr
4949
}
5050

5151
// Generate generates artifacts produced by this marker.
52-
// It's called *after* RegisterMarkers has been called.
52+
// It's called after RegisterMarkers has been called.
5353
func (g CustomResourceConfigGenerator) Generate(ctx *genall.GenerationContext) error {
5454
// Create the parser which is specific to the metric generator.
5555
parser := newParser(
@@ -60,62 +60,55 @@ func (g CustomResourceConfigGenerator) Generate(ctx *genall.GenerationContext) e
6060
)
6161

6262
// Loop over all passed packages.
63-
for _, root := range ctx.Roots {
63+
for _, pkg := range ctx.Roots {
6464
// skip packages which don't import metav1 because they can't define a CRD without meta v1.
65-
metav1 := root.Imports()["k8s.io/apimachinery/pkg/apis/meta/v1"]
65+
metav1 := pkg.Imports()["k8s.io/apimachinery/pkg/apis/meta/v1"]
6666
if metav1 == nil {
6767
continue
6868
}
6969

70-
// parse the given package to feed crd.FindKubeKinds to find CRD objects.
71-
parser.NeedPackage(root)
70+
// parse the given package to feed crd.FindKubeKinds with Kubernetes Objects.
71+
parser.NeedPackage(pkg)
72+
7273
kubeKinds := crd.FindKubeKinds(parser.Parser, metav1)
7374
if len(kubeKinds) == 0 {
7475
klog.Fatalf("no objects in the roots")
7576
}
7677

78+
// Create metrics for all Custom Resources in this package.
79+
// This creates the customresourcestate.Resource object which contains all metric
80+
// definitions for the Custom Resource, if it is part of the package.
7781
for _, gv := range kubeKinds {
78-
// Create customresourcestate.Resource for each CRD which contains all metric
79-
// definitions for the CRD.
80-
parser.NeedResourceFor(gv)
82+
parser.NeedResourceFor(pkg, gv)
8183
}
8284
}
8385

84-
// Build customresourcestate configuration file from generated data.
86+
// Initialize empty customresourcestate configuration file and fill it with the
87+
// customresourcestate.Resource objects from the parser.
8588
metrics := customresourcestate.Metrics{
8689
Spec: customresourcestate.MetricsSpec{
8790
Resources: []customresourcestate.Resource{},
8891
},
8992
}
9093

91-
// Sort the resources to get a deterministic output.
92-
9394
for _, resource := range parser.CustomResourceStates {
95+
if resource == nil {
96+
continue
97+
}
9498
if len(resource.Metrics) > 0 {
95-
// sort the metrics
99+
// Sort the metrics to get a deterministic output.
96100
sort.Slice(resource.Metrics, func(i, j int) bool {
97101
return resource.Metrics[i].Name < resource.Metrics[j].Name
98102
})
99103

100-
metrics.Spec.Resources = append(metrics.Spec.Resources, resource)
104+
metrics.Spec.Resources = append(metrics.Spec.Resources, *resource)
101105
}
102106
}
103107

108+
// Sort the resources by GVK to get a deterministic output.
104109
sort.Slice(metrics.Spec.Resources, func(i, j int) bool {
105-
if metrics.Spec.Resources[i].MetricNamePrefix == nil && metrics.Spec.Resources[j].MetricNamePrefix == nil {
106-
a := metrics.Spec.Resources[i].GroupVersionKind.Group + "/" + metrics.Spec.Resources[i].GroupVersionKind.Version + "/" + metrics.Spec.Resources[i].GroupVersionKind.Kind
107-
b := metrics.Spec.Resources[j].GroupVersionKind.Group + "/" + metrics.Spec.Resources[j].GroupVersionKind.Version + "/" + metrics.Spec.Resources[j].GroupVersionKind.Kind
108-
return a < b
109-
}
110-
111-
// Either a or b will not be the empty string, so we can compare them.
112-
var a, b string
113-
if metrics.Spec.Resources[i].MetricNamePrefix != nil {
114-
a = *metrics.Spec.Resources[i].MetricNamePrefix
115-
}
116-
if metrics.Spec.Resources[j].MetricNamePrefix != nil {
117-
b = *metrics.Spec.Resources[j].MetricNamePrefix
118-
}
110+
a := metrics.Spec.Resources[i].GroupVersionKind.String()
111+
b := metrics.Spec.Resources[j].GroupVersionKind.String()
119112
return a < b
120113
})
121114

exp/metric-gen/generator/parser.go

Lines changed: 79 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"fmt"
2020
"go/ast"
2121
"go/types"
22-
"sort"
2322
"strings"
2423

2524
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -35,82 +34,65 @@ import (
3534
type parser struct {
3635
*crd.Parser
3736

38-
CustomResourceStates map[schema.GroupKind]customresourcestate.Resource
39-
FlattenedMetrics map[crd.TypeIdent][]customresourcestate.Metric
37+
CustomResourceStates map[crd.TypeIdent]*customresourcestate.Resource
4038
}
4139

4240
func newParser(p *crd.Parser) *parser {
4341
return &parser{
4442
Parser: p,
45-
CustomResourceStates: make(map[schema.GroupKind]customresourcestate.Resource),
46-
FlattenedMetrics: make(map[crd.TypeIdent][]customresourcestate.Metric),
43+
CustomResourceStates: make(map[crd.TypeIdent]*customresourcestate.Resource),
4744
}
4845
}
4946

50-
func (p *parser) NeedResourceFor(groupKind schema.GroupKind) {
51-
if _, exists := p.CustomResourceStates[groupKind]; exists {
47+
// NeedResourceFor creates the customresourcestate.Resource object for the given
48+
// GroupKind located at the package identified by packageID.
49+
func (p *parser) NeedResourceFor(pkg *loader.Package, groupKind schema.GroupKind) {
50+
typeIdent := crd.TypeIdent{Package: pkg, Name: groupKind.Kind}
51+
// Skip if type was already processed.
52+
if _, exists := p.CustomResourceStates[typeIdent]; exists {
5253
return
5354
}
5455

55-
var packages []*loader.Package
56-
for pkg, gv := range p.GroupVersions {
57-
if gv.Group != groupKind.Group {
58-
continue
59-
}
60-
packages = append(packages, pkg)
56+
// Already mark the cacheID so the next time it enters NeedResourceFor it skips early.
57+
p.CustomResourceStates[typeIdent] = nil
58+
59+
// Build the type identifier for the custom resource.
60+
typeInfo := p.Types[typeIdent]
61+
// typeInfo is nil if this GroupKind is not part of this package. In that case
62+
// we have nothing to process.
63+
if typeInfo == nil {
64+
return
65+
}
66+
67+
// Skip if gvk marker is not set. This marker is the opt-in for creating metrics
68+
// for a custom resource.
69+
if m := typeInfo.Markers.Get(markers.GVKMarkerName); m == nil {
70+
return
6171
}
6272

73+
// Initialize the Resource object.
6374
resource := customresourcestate.Resource{
6475
GroupVersionKind: customresourcestate.GroupVersionKind{
65-
Group: groupKind.Group,
66-
Kind: groupKind.Kind,
76+
Group: groupKind.Group,
77+
Kind: groupKind.Kind,
78+
Version: p.GroupVersions[pkg].Version,
6779
},
80+
// Create the metrics generators for the custom resource.
81+
Metrics: p.NeedMetricsGeneratorFor(typeIdent),
6882
}
6983

70-
for _, pkg := range packages {
71-
typeIdent := crd.TypeIdent{Package: pkg, Name: groupKind.Kind}
72-
typeInfo := p.Types[typeIdent]
73-
if typeInfo == nil {
74-
continue
75-
}
76-
77-
// Skip if gvk marker is not set to not create configuration for CRs used in other CRs.
78-
// E.g. to not create configuration for KubeadmControlPlaneTemplate.
79-
if m := typeInfo.Markers.Get(markers.GVKMarkerName); m == nil {
80-
continue
81-
}
82-
83-
resource.Metrics = p.NeedMetricsGeneratorFor(typeIdent)
84-
85-
sort.Slice(resource.Metrics, func(i, j int) bool {
86-
return resource.Metrics[i].Name < resource.Metrics[j].Name
87-
})
88-
89-
if resource.GroupVersionKind.Version != "" {
90-
klog.Fatal("GroupVersionKind.Version is already set", "resource", resource)
91-
}
92-
resource.GroupVersionKind.Version = p.GroupVersions[pkg].Version
93-
}
94-
95-
for _, pkg := range packages {
96-
typeIdent := crd.TypeIdent{Package: pkg, Name: groupKind.Kind}
97-
typeInfo := p.Types[typeIdent]
98-
if typeInfo == nil {
99-
continue
100-
}
101-
102-
for _, markerVals := range typeInfo.Markers {
103-
for _, val := range markerVals {
104-
if resourceMarker, isResourceMarker := val.(markers.ResourceMarker); isResourceMarker {
105-
if err := resourceMarker.ApplyToResource(&resource); err != nil {
106-
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
107-
}
84+
// Iterate through all markers and run the ApplyToResource function of the ResourceMarkers.
85+
for _, markerVals := range typeInfo.Markers {
86+
for _, val := range markerVals {
87+
if resourceMarker, isResourceMarker := val.(markers.ResourceMarker); isResourceMarker {
88+
if err := resourceMarker.ApplyToResource(&resource); err != nil {
89+
pkg.AddError(loader.ErrFromNode(err /* an okay guess */, typeInfo.RawSpec))
10890
}
10991
}
11092
}
11193
}
11294

113-
p.CustomResourceStates[groupKind] = resource
95+
p.CustomResourceStates[typeIdent] = &resource
11496
}
11597

11698
type generatorRequester interface {
@@ -134,19 +116,6 @@ func newGeneratorContext(pkg *loader.Package, req generatorRequester) *generator
134116
}
135117
}
136118

137-
// requestGenerator asks for the generator for a type in the package with the
138-
// given import path.
139-
func (c *generatorContext) requestGenerator(pkgPath, typeName string) []customresourcestate.Generator {
140-
pkg := c.pkg
141-
if pkgPath != "" {
142-
pkg = c.pkg.Imports()[pkgPath]
143-
}
144-
return c.generatorRequester.NeedMetricsGeneratorFor(crd.TypeIdent{
145-
Package: pkg,
146-
Name: typeName,
147-
})
148-
}
149-
150119
func generatorsFromMarkers(m ctrlmarkers.MarkerValues, basePath ...string) []customresourcestate.Generator {
151120
generators := []customresourcestate.Generator{}
152121

@@ -163,18 +132,22 @@ func generatorsFromMarkers(m ctrlmarkers.MarkerValues, basePath ...string) []cus
163132
return generators
164133
}
165134

135+
// NeedMetricsGeneratorFor creates the customresourcestate.Generator object for a
136+
// Custom Resource.
166137
func (p *parser) NeedMetricsGeneratorFor(typ crd.TypeIdent) []customresourcestate.Generator {
167-
if _, knownMetrics := p.FlattenedMetrics[typ]; knownMetrics {
168-
return nil
169-
}
170-
171138
info, gotInfo := p.Types[typ]
172139
if !gotInfo {
173140
klog.Fatal("expected to get info for %v but does not exist", typ)
174141
}
175142

143+
// Add metric generators defined by markers at the type.
176144
generators := generatorsFromMarkers(info.Markers)
145+
146+
// Traverse fields of the object and process markers.
147+
// Note: Partially inspired by controller-tools.
148+
// xref: https://github.com/kubernetes-sigs/controller-tools/blob/d89d6ae3df218a85f7cd9e477157cace704b37d1/pkg/crd/schema.go#L350
177149
for _, f := range info.Fields {
150+
// Only fields with the `json:"..."` tag are relevant. Others are not part of the Custom Resource.
178151
jsonTag, hasTag := f.Tag.Lookup("json")
179152
if !hasTag {
180153
// if the field doesn't have a JSON tag, it doesn't belong in output (and shouldn't exist in a serialized type)
@@ -186,30 +159,22 @@ func (p *parser) NeedMetricsGeneratorFor(typ crd.TypeIdent) []customresourcestat
186159
continue
187160
}
188161

162+
// Add metric generators defined by markers at the field.
189163
generators = append(generators, generatorsFromMarkers(f.Markers, jsonOpts[0])...)
190164

165+
// Create new generator context and recursively process the fields.
191166
generatorCtx := newGeneratorContext(typ.Package, p)
192167
for _, generator := range generatorsFor(generatorCtx, f.RawField.Type) {
193-
generators = append(generators, prependPathOnGenerator(generator, jsonOpts[0]))
168+
generators = append(generators, addPathPrefixOnGenerator(generator, jsonOpts[0]))
194169
}
195170
}
196171

197172
return generators
198173
}
199174

200-
func prependPathOnGenerator(generator customresourcestate.Generator, pathPrefix string) customresourcestate.Generator {
201-
switch generator.Each.Type {
202-
case customresourcestate.MetricTypeGauge:
203-
generator.Each.Gauge.MetricMeta.Path = append([]string{pathPrefix}, generator.Each.Gauge.MetricMeta.Path...)
204-
case customresourcestate.MetricTypeStateSet:
205-
generator.Each.StateSet.MetricMeta.Path = append([]string{pathPrefix}, generator.Each.StateSet.MetricMeta.Path...)
206-
case customresourcestate.MetricTypeInfo:
207-
generator.Each.Info.MetricMeta.Path = append([]string{pathPrefix}, generator.Each.Info.MetricMeta.Path...)
208-
}
209-
210-
return generator
211-
}
212-
175+
// generatorsFor creates generators for the given AST type.
176+
// Note: Partially inspired by controller-tools.
177+
// xref: https://github.com/kubernetes-sigs/controller-tools/blob/d89d6ae3df218a85f7cd9e477157cace704b37d1/pkg/crd/schema.go#L167-L193
213178
func generatorsFor(ctx *generatorContext, rawType ast.Expr) []customresourcestate.Generator {
214179
switch expr := rawType.(type) {
215180
case *ast.Ident:
@@ -236,6 +201,8 @@ func generatorsFor(ctx *generatorContext, rawType ast.Expr) []customresourcestat
236201
return nil
237202
}
238203

204+
// localNamedToGenerators recurses back to NeedMetricsGeneratorFor for the type to
205+
// get generators defined at the objects in a custom resource.
239206
func localNamedToGenerators(ctx *generatorContext, ident *ast.Ident) []customresourcestate.Generator {
240207
typeInfo := ctx.pkg.TypesInfo.TypeOf(ident)
241208
if typeInfo == types.Typ[types.Invalid] {
@@ -260,3 +227,30 @@ func localNamedToGenerators(ctx *generatorContext, ident *ast.Ident) []customres
260227
}
261228
return ctx.requestGenerator(pkgPath, typeNameInfo.Name())
262229
}
230+
231+
// requestGenerator asks for the generator for a type in the package with the
232+
// given import path.
233+
func (c *generatorContext) requestGenerator(pkgPath, typeName string) []customresourcestate.Generator {
234+
pkg := c.pkg
235+
if pkgPath != "" {
236+
pkg = c.pkg.Imports()[pkgPath]
237+
}
238+
return c.generatorRequester.NeedMetricsGeneratorFor(crd.TypeIdent{
239+
Package: pkg,
240+
Name: typeName,
241+
})
242+
}
243+
244+
// addPathPrefixOnGenerator prefixes the path set at the generators MetricMeta object.
245+
func addPathPrefixOnGenerator(generator customresourcestate.Generator, pathPrefix string) customresourcestate.Generator {
246+
switch generator.Each.Type {
247+
case customresourcestate.MetricTypeGauge:
248+
generator.Each.Gauge.MetricMeta.Path = append([]string{pathPrefix}, generator.Each.Gauge.MetricMeta.Path...)
249+
case customresourcestate.MetricTypeStateSet:
250+
generator.Each.StateSet.MetricMeta.Path = append([]string{pathPrefix}, generator.Each.StateSet.MetricMeta.Path...)
251+
case customresourcestate.MetricTypeInfo:
252+
generator.Each.Info.MetricMeta.Path = append([]string{pathPrefix}, generator.Each.Info.MetricMeta.Path...)
253+
}
254+
255+
return generator
256+
}

0 commit comments

Comments
 (0)