Skip to content

Commit 0a4f0fb

Browse files
authored
Collect SnippetsFilter information (#2677)
Collect SnippetsFilters count, the directive-context of each snippet, and the total count of each unique directive-context. Problem: I want to know if SnippetsFilters are being used, and what are the most popular directives being used. Solution: Collect SnippetsFilter count, the directive-context of each snippet, and the total count of each unique directive-context. Testing: Manual testing and unit tests.
1 parent 8dd553d commit 0a4f0fb

File tree

8 files changed

+278
-24
lines changed

8 files changed

+278
-24
lines changed

internal/mode/static/telemetry/collector.go

Lines changed: 134 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"errors"
66
"fmt"
77
"runtime"
8+
"sort"
9+
"strings"
810

911
tel "github.com/nginxinc/telemetry-exporter/pkg/telemetry"
1012
appsv1 "k8s.io/api/apps/v1"
@@ -14,6 +16,7 @@ import (
1416
k8sversion "k8s.io/apimachinery/pkg/util/version"
1517
"sigs.k8s.io/controller-runtime/pkg/client"
1618

19+
ngfAPI "github.com/nginxinc/nginx-gateway-fabric/apis/v1alpha1"
1720
"github.com/nginxinc/nginx-gateway-fabric/internal/framework/kinds"
1821
"github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/config"
1922
"github.com/nginxinc/nginx-gateway-fabric/internal/mode/static/state/dataplane"
@@ -46,8 +49,17 @@ type Data struct {
4649
// FlagValues contains the values of the command-line flags, where each value corresponds to the flag from FlagNames
4750
// at the same index.
4851
// Each value is either 'true' or 'false' for boolean flags and 'default' or 'user-defined' for non-boolean flags.
49-
FlagValues []string
50-
NGFResourceCounts // embedding is required by the generator.
52+
FlagValues []string
53+
// SnippetsFiltersDirectives contains the directive-context strings of all applied SnippetsFilters.
54+
// Both lists are ordered first by count, then by lexicographical order of the context string,
55+
// then lastly by directive string.
56+
SnippetsFiltersDirectives []string
57+
// SnippetsFiltersDirectivesCount contains the count of the directive-context strings, where each count
58+
// corresponds to the string from SnippetsFiltersDirectives at the same index.
59+
// Both lists are ordered first by count, then by lexicographical order of the context string,
60+
// then lastly by directive string.
61+
SnippetsFiltersDirectivesCount []int64
62+
NGFResourceCounts // embedding is required by the generator.
5163
// NGFReplicaCount is the number of replicas of the NGF Pod.
5264
NGFReplicaCount int64
5365
}
@@ -83,6 +95,8 @@ type NGFResourceCounts struct {
8395
ObservabilityPolicyCount int64
8496
// NginxProxyCount is the number of NginxProxies.
8597
NginxProxyCount int64
98+
// SnippetsFilterCount is the number of SnippetsFilters.
99+
SnippetsFilterCount int64
86100
}
87101

88102
// DataCollectorConfig holds configuration parameters for DataCollectorImpl.
@@ -119,12 +133,17 @@ func NewDataCollectorImpl(
119133

120134
// Collect collects and returns telemetry Data.
121135
func (c DataCollectorImpl) Collect(ctx context.Context) (Data, error) {
136+
g := c.cfg.GraphGetter.GetLatestGraph()
137+
if g == nil {
138+
return Data{}, errors.New("failed to collect telemetry data: latest graph cannot be nil")
139+
}
140+
122141
clusterInfo, err := collectClusterInformation(ctx, c.cfg.K8sClientReader)
123142
if err != nil {
124143
return Data{}, fmt.Errorf("failed to collect cluster information: %w", err)
125144
}
126145

127-
graphResourceCount, err := collectGraphResourceCount(c.cfg.GraphGetter, c.cfg.ConfigurationGetter)
146+
graphResourceCount, err := collectGraphResourceCount(g, c.cfg.ConfigurationGetter)
128147
if err != nil {
129148
return Data{}, fmt.Errorf("failed to collect NGF resource counts: %w", err)
130149
}
@@ -144,6 +163,8 @@ func (c DataCollectorImpl) Collect(ctx context.Context) (Data, error) {
144163
return Data{}, fmt.Errorf("failed to get NGF deploymentID: %w", err)
145164
}
146165

166+
snippetsFiltersDirectives, snippetsFiltersDirectivesCount := collectSnippetsFilterDirectives(g)
167+
147168
data := Data{
148169
Data: tel.Data{
149170
ProjectName: "NGF",
@@ -155,27 +176,25 @@ func (c DataCollectorImpl) Collect(ctx context.Context) (Data, error) {
155176
InstallationID: deploymentID,
156177
ClusterNodeCount: int64(clusterInfo.NodeCount),
157178
},
158-
NGFResourceCounts: graphResourceCount,
159-
ImageSource: c.cfg.ImageSource,
160-
FlagNames: c.cfg.Flags.Names,
161-
FlagValues: c.cfg.Flags.Values,
162-
NGFReplicaCount: int64(replicaCount),
179+
NGFResourceCounts: graphResourceCount,
180+
ImageSource: c.cfg.ImageSource,
181+
FlagNames: c.cfg.Flags.Names,
182+
FlagValues: c.cfg.Flags.Values,
183+
NGFReplicaCount: int64(replicaCount),
184+
SnippetsFiltersDirectives: snippetsFiltersDirectives,
185+
SnippetsFiltersDirectivesCount: snippetsFiltersDirectivesCount,
163186
}
164187

165188
return data, nil
166189
}
167190

168191
func collectGraphResourceCount(
169-
graphGetter GraphGetter,
192+
g *graph.Graph,
170193
configurationGetter ConfigurationGetter,
171194
) (NGFResourceCounts, error) {
172195
ngfResourceCounts := NGFResourceCounts{}
173-
g := graphGetter.GetLatestGraph()
174196
cfg := configurationGetter.GetLatestConfiguration()
175197

176-
if g == nil {
177-
return ngfResourceCounts, errors.New("latest graph cannot be nil")
178-
}
179198
if cfg == nil {
180199
return ngfResourceCounts, errors.New("latest configuration cannot be nil")
181200
}
@@ -227,6 +246,8 @@ func collectGraphResourceCount(
227246
ngfResourceCounts.NginxProxyCount = 1
228247
}
229248

249+
ngfResourceCounts.SnippetsFilterCount = int64(len(g.SnippetsFilters))
250+
230251
return ngfResourceCounts, nil
231252
}
232253

@@ -378,3 +399,103 @@ func collectClusterInformation(ctx context.Context, k8sClient client.Reader) (cl
378399

379400
return clusterInfo, nil
380401
}
402+
403+
type sfDirectiveContext struct {
404+
directive string
405+
context string
406+
}
407+
408+
func collectSnippetsFilterDirectives(g *graph.Graph) ([]string, []int64) {
409+
directiveContextMap := make(map[sfDirectiveContext]int)
410+
411+
for _, sf := range g.SnippetsFilters {
412+
if sf == nil {
413+
continue
414+
}
415+
416+
for nginxContext, snippetValue := range sf.Snippets {
417+
var parsedContext string
418+
419+
switch nginxContext {
420+
case ngfAPI.NginxContextMain:
421+
parsedContext = "main"
422+
case ngfAPI.NginxContextHTTP:
423+
parsedContext = "http"
424+
case ngfAPI.NginxContextHTTPServer:
425+
parsedContext = "server"
426+
case ngfAPI.NginxContextHTTPServerLocation:
427+
parsedContext = "location"
428+
default:
429+
parsedContext = "unknown"
430+
}
431+
432+
directives := parseSnippetValueIntoDirectives(snippetValue)
433+
for _, directive := range directives {
434+
directiveContext := sfDirectiveContext{
435+
directive: directive,
436+
context: parsedContext,
437+
}
438+
directiveContextMap[directiveContext]++
439+
}
440+
}
441+
}
442+
443+
return parseDirectiveContextMapIntoLists(directiveContextMap)
444+
}
445+
446+
func parseSnippetValueIntoDirectives(snippetValue string) []string {
447+
separatedDirectives := strings.Split(snippetValue, ";")
448+
directives := make([]string, 0, len(separatedDirectives))
449+
450+
for _, directive := range separatedDirectives {
451+
// the strings.TrimSpace is needed in the case of multi-line NGINX Snippet values
452+
directive = strings.Split(strings.TrimSpace(directive), " ")[0]
453+
454+
// splitting on the delimiting character can result in a directive being empty or a space/newline character,
455+
// so we check here to ensure it's not
456+
if directive != "" {
457+
directives = append(directives, directive)
458+
}
459+
}
460+
461+
return directives
462+
}
463+
464+
// parseDirectiveContextMapIntoLists returns two same-length lists where the elements at each corresponding index
465+
// are paired together.
466+
// The first list contains strings which are the NGINX directive and context of a Snippet joined with a hyphen.
467+
// The second list contains ints which are the count of total same directive-context values of the first list.
468+
// Both lists are ordered first by count, then by lexicographical order of the context string,
469+
// then lastly by directive string.
470+
func parseDirectiveContextMapIntoLists(directiveContextMap map[sfDirectiveContext]int) ([]string, []int64) {
471+
type sfDirectiveContextCount struct {
472+
directive, context string
473+
count int64
474+
}
475+
476+
kvPairs := make([]sfDirectiveContextCount, 0, len(directiveContextMap))
477+
478+
for k, v := range directiveContextMap {
479+
kvPairs = append(kvPairs, sfDirectiveContextCount{k.directive, k.context, int64(v)})
480+
}
481+
482+
sort.Slice(kvPairs, func(i, j int) bool {
483+
if kvPairs[i].count == kvPairs[j].count {
484+
if kvPairs[i].context == kvPairs[j].context {
485+
return kvPairs[i].directive < kvPairs[j].directive
486+
}
487+
return kvPairs[i].context < kvPairs[j].context
488+
}
489+
return kvPairs[i].count > kvPairs[j].count
490+
})
491+
492+
directiveContextList := make([]string, len(kvPairs))
493+
countList := make([]int64, len(kvPairs))
494+
495+
for i, pair := range kvPairs {
496+
directiveContextList[i] = pair.directive + "-" + pair.context
497+
countList[i] = pair.count
498+
}
499+
500+
return directiveContextList, countList
501+
}

0 commit comments

Comments
 (0)