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.
121135func (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
168191func 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