diff --git a/pkg/config/config.go b/pkg/config/config.go index 9d6c0471..c79e2752 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -34,7 +34,10 @@ type DiscoveryRule struct { MetricsQuery string `yaml:"metricsQuery,omitempty"` // MetricType identifies whether the metrics derived from this rule should be classified // as external or custom metrics. - MetricType MetricType `yaml:"metricType"` + MetricType MetricType `yaml:"metricType,omitempty"` + // ExternalMetricNamespaceLabelName identifies what Prometheus label should be examined + // to apply a namespace to metrics creates from this rule. + ExternalMetricNamespaceLabelName string `yaml:"externalMetricNamespaceLabelName,omitempty"` } // RegexFilter is a filter that matches positively or negatively against a regex. @@ -79,3 +82,12 @@ type NameMapping struct { //MetricType identifies whether a given metric should be handled and interpreted as a Custom or External metric. type MetricType string + +// Operator represents a key/field's relationship to value(s). +// See labels.Requirement and fields.Requirement for more details. +type Operator string + +const ( + External MetricType = "External" + Custom MetricType = "Custom" +) diff --git a/pkg/custom-provider/basic_metric_lister.go b/pkg/custom-provider/basic_metric_lister.go index bf640ee4..1bbbd15d 100644 --- a/pkg/custom-provider/basic_metric_lister.go +++ b/pkg/custom-provider/basic_metric_lister.go @@ -48,13 +48,15 @@ type MetricLister interface { ListAllMetrics() (metricUpdateResult, error) } +//A MetricListerWithNotification is a MetricLister that has the ability to notify listeners +//when new metric data is available. type MetricListerWithNotification interface { - //It can list metrics, just like a normal MetricLister. MetricLister - //Because it periodically pulls metrics, it needs to be Runnable. Runnable - //It provides notifications when it has new data to supply. + + //AddNotificationReceiver registers a callback to be invoked when new metric data is available. AddNotificationReceiver(func(metricUpdateResult)) + //UpdateNow forces an immediate refresh from the source data. Primarily for test purposes. UpdateNow() } @@ -64,6 +66,7 @@ type basicMetricLister struct { lookback time.Duration } +//NewBasicMetricLister creates a MetricLister that is capable of interactly directly with Prometheus to list metrics. func NewBasicMetricLister(promClient prom.Client, namers []MetricNamer, lookback time.Duration) MetricLister { lister := basicMetricLister{ promClient: promClient, diff --git a/pkg/custom-provider/external_info_map.go b/pkg/custom-provider/external_info_map.go index aae1f360..03933baf 100644 --- a/pkg/custom-provider/external_info_map.go +++ b/pkg/custom-provider/external_info_map.go @@ -5,24 +5,37 @@ import ( "k8s.io/apimachinery/pkg/labels" ) +//ExportedMetric is a description of an available metric. type ExportedMetric struct { MetricName string Labels labels.Set Namespace string } +//ExternalInfoMap is a data object that accepts and organizes information +//about available metrics. type ExternalInfoMap interface { + //Begins tracking a metric, returning it to the caller. TrackMetric(metricName string, generatedBy MetricNamer) ExternalMetricData + //Exports a collection of all of the metrics currently being tracked. ExportMetrics() []ExportedMetric + //Finds a tracked metric with the given metric name, if it exists. FindMetric(metricName string) (data ExternalMetricData, found bool) } +//ExternalMetricData is a data object that accepts and organizes information +//about the various series/namespaces that a metric is associated with. type ExternalMetricData interface { + //MetricName returns the name of the metric represented by this object. MetricName() string + //WithSeries associates the provided labels with this metric. WithSeries(labels labels.Set) + //WithNamespacedSeries associates the provided labels with this metric, but within a particular namespace. WithNamespacedSeries(namespace string, labels labels.Set) + //Exports a collection of all the metrics currently being tracked. ExportMetrics() []ExportedMetric - GenerateQuery(selector labels.Selector) (prom.Selector, error) + //Generates a query to select the series/values for the metric this object represents. + GenerateQuery(namespace string, selector labels.Selector) (prom.Selector, error) } type externalInfoMap struct { @@ -31,18 +44,20 @@ type externalInfoMap struct { type externalMetricData struct { metricName string - namespacedData map[string]labels.Set + namespacedData map[string][]labels.Set generatedBy MetricNamer } +//NewExternalMetricData creates an ExternalMetricData for the provided metric name and namer. func NewExternalMetricData(metricName string, generatedBy MetricNamer) ExternalMetricData { return &externalMetricData{ metricName: metricName, generatedBy: generatedBy, - namespacedData: map[string]labels.Set{}, + namespacedData: map[string][]labels.Set{}, } } +//NewExternalInfoMap creates an empty ExternalInfoMap for storing external metric information. func NewExternalInfoMap() ExternalInfoMap { return &externalInfoMap{ metrics: map[string]ExternalMetricData{}, @@ -78,18 +93,20 @@ func (d *externalMetricData) MetricName() string { return d.metricName } -func (d *externalMetricData) GenerateQuery(selector labels.Selector) (prom.Selector, error) { - return d.generatedBy.QueryForExternalSeries(d.metricName, selector) +func (d *externalMetricData) GenerateQuery(namespace string, selector labels.Selector) (prom.Selector, error) { + return d.generatedBy.QueryForExternalSeries(namespace, d.metricName, selector) } func (d *externalMetricData) ExportMetrics() []ExportedMetric { results := make([]ExportedMetric, 0) - for namespace, labels := range d.namespacedData { - results = append(results, ExportedMetric{ - Labels: labels, - MetricName: d.metricName, - Namespace: namespace, - }) + for namespace, labelSets := range d.namespacedData { + for _, labelSet := range labelSets { + results = append(results, ExportedMetric{ + Labels: labelSet, + MetricName: d.metricName, + Namespace: namespace, + }) + } } return results @@ -99,10 +116,13 @@ func (d *externalMetricData) WithSeries(labels labels.Set) { d.WithNamespacedSeries("", labels) } -func (d *externalMetricData) WithNamespacedSeries(namespace string, labels labels.Set) { +func (d *externalMetricData) WithNamespacedSeries(namespace string, seriesLabels labels.Set) { data, found := d.namespacedData[namespace] if !found { - data = labels - d.namespacedData[namespace] = data + data = []labels.Set{} } + + data = append(data, seriesLabels) + d.namespacedData[namespace] = data + } diff --git a/pkg/custom-provider/external_provider.go b/pkg/custom-provider/external_provider.go index cceb6608..b10a98ce 100644 --- a/pkg/custom-provider/external_provider.go +++ b/pkg/custom-provider/external_provider.go @@ -15,16 +15,7 @@ import ( conv "github.com/directxman12/k8s-prometheus-adapter/pkg/custom-provider/metric-converter" ) -//TODO: Make sure everything has the proper licensing disclosure at the top. -//TODO: I'd like to move these files into another directory, but the compiler was giving me -//some static around unexported types. I'm going to leave things as-is for now, but it -//might be worthwhile to, once the shared components are discovered, move some things around. - -//TODO: Some of these members may not be necessary. -//Some of them are definitely duplicated between the -//external and custom providers. They should probably share -//the same instances of these objects (especially the SeriesRegistry) -//to cut down on unnecessary chatter/bookkeeping. +//TODO: AC - Make sure everything has the proper licensing disclosure at the top. type externalPrometheusProvider struct { promClient prom.Client metricConverter conv.MetricConverter @@ -32,13 +23,7 @@ type externalPrometheusProvider struct { seriesRegistry ExternalSeriesRegistry } -//TODO: It probably makes more sense to, once this is functional and complete, roll the -//prometheusProvider and externalPrometheusProvider up into a single type -//that implements both interfaces or provide a thin wrapper that composes them. -//Just glancing at start.go looks like it would be much more straightforward -//to do one of those two things instead of trying to run the two providers -//independently. - +//NewExternalPrometheusProvider creates an ExternalMetricsProvider capable of responding to Kubernetes requests for external metric data. func NewExternalPrometheusProvider(seriesRegistry ExternalSeriesRegistry, promClient prom.Client, converter conv.MetricConverter) provider.ExternalMetricsProvider { return &externalPrometheusProvider{ promClient: promClient, @@ -48,21 +33,17 @@ func NewExternalPrometheusProvider(seriesRegistry ExternalSeriesRegistry, promCl } func (p *externalPrometheusProvider) GetExternalMetric(namespace string, metricName string, metricSelector labels.Selector) (*external_metrics.ExternalMetricValueList, error) { - selector, found := p.seriesRegistry.QueryForMetric(metricName, metricSelector) + selector, found := p.seriesRegistry.QueryForMetric(namespace, metricName, metricSelector) if !found { return &external_metrics.ExternalMetricValueList{ Items: []external_metrics.ExternalMetricValue{}, }, nil } - // query := p.queryBuilder.BuildPrometheusQuery(namespace, metricName, metricSelector, queryMetadata) - //TODO: I don't yet know what a context is, but apparently I should use a real one. queryResults, err := p.promClient.Query(context.TODO(), pmodel.Now(), selector) if err != nil { - //TODO: Is this how folks normally deal w/ errors? Just propagate them upwards? - //I should go look at what the customProvider does. return nil, err } diff --git a/pkg/custom-provider/external_series_registry.go b/pkg/custom-provider/external_series_registry.go index 0d5fb200..ad1752c9 100644 --- a/pkg/custom-provider/external_series_registry.go +++ b/pkg/custom-provider/external_series_registry.go @@ -14,31 +14,33 @@ import ( "github.com/directxman12/k8s-prometheus-adapter/pkg/config" ) +//ExternalSeriesRegistry acts as the top-level converter for transforming Kubernetes requests +//for external metrics into Prometheus queries. type ExternalSeriesRegistry interface { // ListAllMetrics lists all metrics known to this registry ListAllMetrics() []provider.ExternalMetricInfo - QueryForMetric(metricName string, metricSelector labels.Selector) (query prom.Selector, found bool) + QueryForMetric(namespace string, metricName string, metricSelector labels.Selector) (query prom.Selector, found bool) } // overridableSeriesRegistry is a basic SeriesRegistry type externalSeriesRegistry struct { mu sync.RWMutex - externalInfo map[string]seriesInfo // metrics is the list of all known metrics metrics []provider.ExternalMetricInfo mapper apimeta.RESTMapper - metricLister MetricListerWithNotification - tonyExternalInfo ExternalInfoMap + metricLister MetricListerWithNotification + externalMetricInfo ExternalInfoMap } +//NewExternalSeriesRegistry creates an ExternalSeriesRegistry driven by the data from the provided MetricLister. func NewExternalSeriesRegistry(lister MetricListerWithNotification, mapper apimeta.RESTMapper) ExternalSeriesRegistry { var registry = externalSeriesRegistry{ - mapper: mapper, - metricLister: lister, - tonyExternalInfo: NewExternalInfoMap(), + mapper: mapper, + metricLister: lister, + externalMetricInfo: NewExternalInfoMap(), } lister.AddNotificationReceiver(registry.onNewDataAvailable) @@ -50,7 +52,7 @@ func (r *externalSeriesRegistry) filterMetrics(result metricUpdateResult) metric namers := make([]MetricNamer, 0) series := make([][]prom.Series, 0) - targetType := config.MetricType("External") + targetType := config.External for i, namer := range result.namers { if namer.MetricType() == targetType { @@ -98,10 +100,16 @@ func (r *externalSeriesRegistry) onNewDataAvailable(result metricUpdateResult) { // namespaced := identity.namespaced name := identity.name labels := r.convertLabels(series.Labels) - //TODO: Figure out the namespace, if applicable - metricNs := "" + + //Check for a label indicating namespace. + metricNs, found := series.Labels[model.LabelName(namer.ExternalMetricNamespaceLabelName())] + + if !found { + metricNs = "" + } + trackedMetric := updatedCache.TrackMetric(name, namer) - trackedMetric.WithNamespacedSeries(metricNs, labels) + trackedMetric.WithNamespacedSeries(string(metricNs), labels) } } @@ -112,7 +120,7 @@ func (r *externalSeriesRegistry) onNewDataAvailable(result metricUpdateResult) { r.mu.Lock() defer r.mu.Unlock() - r.tonyExternalInfo = updatedCache + r.externalMetricInfo = updatedCache r.metrics = convertedMetrics } @@ -135,29 +143,20 @@ func (r *externalSeriesRegistry) ListAllMetrics() []provider.ExternalMetricInfo return r.metrics } -func (r *externalSeriesRegistry) QueryForMetric(metricName string, metricSelector labels.Selector) (query prom.Selector, found bool) { +func (r *externalSeriesRegistry) QueryForMetric(namespace string, metricName string, metricSelector labels.Selector) (query prom.Selector, found bool) { r.mu.RLock() defer r.mu.RUnlock() - metric, found := r.tonyExternalInfo.FindMetric(metricName) + metric, found := r.externalMetricInfo.FindMetric(metricName) if !found { + glog.V(10).Infof("external metric %q not registered", metricName) return "", false } - query, err := metric.GenerateQuery(metricSelector) - // info, infoFound := r.info[metricInfo] - // if !infoFound { - // //TODO: Weird that it switches between types here. - // glog.V(10).Infof("metric %v not registered", metricInfo) - // return "", false - // } + query, err := metric.GenerateQuery(namespace, metricSelector) - // query, err := info.namer.QueryForExternalSeries(info.seriesName, metricSelector) if err != nil { - //TODO: See what was being .String() and implement that for ExternalMetricInfo. - // errorVal := metricInfo.String() - errorVal := "something" - glog.Errorf("unable to construct query for metric %s: %v", errorVal, err) + glog.Errorf("unable to construct query for external metric %s: %v", metricName, err) return "", false } diff --git a/pkg/custom-provider/metric-converter/matrix_converter.go b/pkg/custom-provider/metric-converter/matrix_converter.go index 453ce7e3..bd5f9c9b 100644 --- a/pkg/custom-provider/metric-converter/matrix_converter.go +++ b/pkg/custom-provider/metric-converter/matrix_converter.go @@ -32,6 +32,6 @@ func (c *matrixConverter) Convert(queryResult prom.QueryResult) (*external_metri } func (c *matrixConverter) convert(result *model.Matrix) (*external_metrics.ExternalMetricValueList, error) { - //TODO: Implementation. + //TODO: AC - Implementation. return nil, errors.New("converting Matrix results is not yet supported") } diff --git a/pkg/custom-provider/metric-converter/sample_converter.go b/pkg/custom-provider/metric-converter/sample_converter.go index 8cd3bb23..29444eb3 100644 --- a/pkg/custom-provider/metric-converter/sample_converter.go +++ b/pkg/custom-provider/metric-converter/sample_converter.go @@ -30,14 +30,10 @@ func (c *sampleConverter) Convert(sample *model.Sample) (*external_metrics.Exter Timestamp: metav1.Time{ sample.Timestamp.Time(), }, - //TODO: I'm not so sure about this type/conversions. - //This can't possibly be the right way to convert this. - //Also, does K8S only deal win integer metrics? - Value: *resource.NewQuantity(int64(float64(sample.Value)), resource.DecimalSI), + Value: *resource.NewMilliQuantity(int64(sample.Value*1000.0), resource.DecimalSI), MetricLabels: labels, } - //TODO: Actual errors? return &singleMetric, nil } diff --git a/pkg/custom-provider/metric-converter/scalar_converter.go b/pkg/custom-provider/metric-converter/scalar_converter.go index 2a4e2095..284f55d5 100644 --- a/pkg/custom-provider/metric-converter/scalar_converter.go +++ b/pkg/custom-provider/metric-converter/scalar_converter.go @@ -35,18 +35,11 @@ func (c *scalarConverter) Convert(queryResult prom.QueryResult) (*external_metri func (c *scalarConverter) convert(input *model.Scalar) (*external_metrics.ExternalMetricValueList, error) { result := external_metrics.ExternalMetricValueList{ - //Using prometheusProvider.metricsFor(...) as an example, - //it seems that I don't need to provide values for - //TypeMeta and ListMeta. - //TODO: Get some confirmation on this. Items: []external_metrics.ExternalMetricValue{ { Timestamp: metav1.Time{ input.Timestamp.Time(), }, - //TODO: I'm not so sure about this type/conversions. - //Is there a meaningful loss of precision here? - //Does K8S only deal win integer metrics? Value: *resource.NewMilliQuantity(int64(input.Value*1000.0), resource.DecimalSI), }, }, diff --git a/pkg/custom-provider/metric-converter/vector_converter.go b/pkg/custom-provider/metric-converter/vector_converter.go index 71b13631..9171d5ca 100644 --- a/pkg/custom-provider/metric-converter/vector_converter.go +++ b/pkg/custom-provider/metric-converter/vector_converter.go @@ -2,6 +2,7 @@ package provider import ( "errors" + "fmt" prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" "github.com/prometheus/common/model" @@ -46,8 +47,13 @@ func (c *vectorConverter) convert(result model.Vector) (*external_metrics.Extern } for _, val := range result { - //TODO: Care about potential errors here. - singleMetric, _ := c.SampleConverter.Convert(val) + + singleMetric, err := c.SampleConverter.Convert(val) + + if err != nil { + return nil, fmt.Errorf("unable to convert vector: %v", err) + } + items = append(items, *singleMetric) } diff --git a/pkg/custom-provider/metric_name_converter.go b/pkg/custom-provider/metric_name_converter.go index a35d6b2d..6a3c26f6 100644 --- a/pkg/custom-provider/metric_name_converter.go +++ b/pkg/custom-provider/metric_name_converter.go @@ -8,6 +8,7 @@ import ( "github.com/directxman12/k8s-prometheus-adapter/pkg/config" ) +//MetricNameConverter provides functions for naming custom metrics from Promethes series. type MetricNameConverter interface { GetMetricNameForSeries(series prom.Series) (string, error) } @@ -17,6 +18,8 @@ type metricNameConverter struct { nameAs string } +//NewMetricNameConverter creates a MetricNameConverter capable of translating Prometheus series names +//into custom metric names. func NewMetricNameConverter(mapping config.NameMapping) (MetricNameConverter, error) { var nameMatches *regexp.Regexp var err error diff --git a/pkg/custom-provider/metric_namer.go b/pkg/custom-provider/metric_namer.go index f1ee45d7..248a9988 100644 --- a/pkg/custom-provider/metric_namer.go +++ b/pkg/custom-provider/metric_namer.go @@ -34,9 +34,10 @@ type MetricNamer interface { // QueryForSeries returns the query for a given series (not API metric name), with // the given namespace name (if relevant), resource, and resource names. QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error) - QueryForExternalSeries(series string, metricSelector labels.Selector) (prom.Selector, error) + QueryForExternalSeries(namespace string, series string, metricSelector labels.Selector) (prom.Selector, error) IdentifySeries(series prom.Series) (seriesIdentity, error) MetricType() config.MetricType + ExternalMetricNamespaceLabelName() string } type seriesIdentity struct { @@ -58,7 +59,8 @@ type metricNamer struct { metricNameConverter MetricNameConverter mapper apimeta.RESTMapper - metricType config.MetricType + metricType config.MetricType + externalMetricNamespaceLabel string } // queryTemplateArgs are the arguments for the metrics query template. @@ -74,6 +76,10 @@ func (n *metricNamer) MetricType() config.MetricType { return n.metricType } +func (n *metricNamer) ExternalMetricNamespaceLabelName() string { + return n.externalMetricNamespaceLabel +} + func (n *metricNamer) IdentifySeries(series prom.Series) (seriesIdentity, error) { // TODO: warn if it doesn't match any resources resources, namespaced := n.resourceConverter.ResourcesForSeries(series) @@ -219,15 +225,26 @@ func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMap return nil, fmt.Errorf("unable to create a MetricNameConverter associated with series query %q: %v", rule.SeriesQuery, err) } + namespaceLabel := "" + if rule.MetricType == config.External { + namespaceLabel = rule.ExternalMetricNamespaceLabelName + } + + metricType := rule.MetricType + if metricType == config.MetricType("") { + metricType = config.Custom + } + namer := &metricNamer{ seriesQuery: prom.Selector(rule.SeriesQuery), mapper: mapper, - resourceConverter: resourceConverter, - queryBuilder: queryBuilder, - seriesFilterer: seriesFilterer, - metricNameConverter: metricNameConverter, - metricType: rule.MetricType, + resourceConverter: resourceConverter, + queryBuilder: queryBuilder, + seriesFilterer: seriesFilterer, + metricNameConverter: metricNameConverter, + metricType: metricType, + externalMetricNamespaceLabel: namespaceLabel, } namers[i] = namer @@ -236,8 +253,28 @@ func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMap return namers, nil } -func (n *metricNamer) QueryForExternalSeries(series string, metricSelector labels.Selector) (prom.Selector, error) { - queryParts := n.createQueryPartsFromSelector(metricSelector) +func (n *metricNamer) buildNamespaceQueryPartForExternalSeries(namespace string) (queryPart, error) { + return queryPart{ + labelName: n.externalMetricNamespaceLabel, + values: []string{namespace}, + }, nil +} + +func (n *metricNamer) QueryForExternalSeries(namespace string, series string, metricSelector labels.Selector) (prom.Selector, error) { + queryParts := []queryPart{} + + if namespace != "" { + //Build up the namespace part of the query. + namespaceQueryPart, err := n.buildNamespaceQueryPartForExternalSeries(namespace) + if err != nil { + return "", err + } + + queryParts = append(queryParts, namespaceQueryPart) + } + + //Build up the query parts from the selector. + queryParts = append(queryParts, n.createQueryPartsFromSelector(metricSelector)...) selector, err := n.queryBuilder.BuildSelector(series, "", []string{}, queryParts) if err != nil { diff --git a/pkg/custom-provider/periodic_metric_lister.go b/pkg/custom-provider/periodic_metric_lister.go index 81fceee7..2100865b 100644 --- a/pkg/custom-provider/periodic_metric_lister.go +++ b/pkg/custom-provider/periodic_metric_lister.go @@ -87,69 +87,3 @@ func (l *periodicMetricLister) notifyListeners() { func (l *periodicMetricLister) UpdateNow() { l.updateMetrics() } - -// func (l *periodicMetricLister) updateMetrics() (metricUpdateResult, error) { - -// result := metricUpdateResult{ -// series: make([][]prom.Series, 0), -// namers: make([]MetricNamer, 0), -// } - -// startTime := pmodel.Now().Add(-1 * l.updateInterval) - -// // these can take a while on large clusters, so launch in parallel -// // and don't duplicate -// selectors := make(map[prom.Selector]struct{}) -// selectorSeriesChan := make(chan selectorSeries, len(l.namers)) -// errs := make(chan error, len(l.namers)) -// for _, namer := range l.namers { -// sel := namer.Selector() -// if _, ok := selectors[sel]; ok { -// errs <- nil -// selectorSeriesChan <- selectorSeries{} -// continue -// } -// selectors[sel] = struct{}{} -// go func() { -// series, err := l.promClient.Series(context.TODO(), pmodel.Interval{startTime, 0}, sel) -// if err != nil { -// errs <- fmt.Errorf("unable to fetch metrics for query %q: %v", sel, err) -// return -// } -// errs <- nil -// selectorSeriesChan <- selectorSeries{ -// selector: sel, -// series: series, -// } -// }() -// } - -// // don't do duplicate queries when it's just the matchers that change -// seriesCacheByQuery := make(map[prom.Selector][]prom.Series) - -// // iterate through, blocking until we've got all results -// for range l.namers { -// if err := <-errs; err != nil { -// return result, fmt.Errorf("unable to update list of all metrics: %v", err) -// } -// if ss := <-selectorSeriesChan; ss.series != nil { -// seriesCacheByQuery[ss.selector] = ss.series -// } -// } -// close(errs) - -// newSeries := make([][]prom.Series, len(l.namers)) -// for i, namer := range l.namers { -// series, cached := seriesCacheByQuery[namer.Selector()] -// if !cached { -// return result, fmt.Errorf("unable to update list of all metrics: no metrics retrieved for query %q", namer.Selector()) -// } -// newSeries[i] = namer.FilterSeries(series) -// } - -// glog.V(10).Infof("Set available metric list from Prometheus to: %v", newSeries) - -// result.series = newSeries -// result.namers = l.namers -// return result, nil -// } diff --git a/pkg/custom-provider/provider.go b/pkg/custom-provider/provider.go index 011456a9..15ec3cec 100644 --- a/pkg/custom-provider/provider.go +++ b/pkg/custom-provider/provider.go @@ -46,12 +46,11 @@ type prometheusProvider struct { SeriesRegistry } +//NewPrometheusProvider creates an CustomMetricsProvider capable of responding to Kubernetes requests for custom metric data. func NewPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic.Interface, promClient prom.Client, namers []MetricNamer, updateInterval time.Duration) (provider.CustomMetricsProvider, Runnable) { + //TODO: AC - Consider injecting these objects and calling .Run() before calling this function. basicLister := NewBasicMetricLister(promClient, namers, updateInterval) - //TODO: Be sure to run this runnable. - // periodicLister, periodicRunnable := NewPeriodicMetricLister(basicLister, updateInterval) periodicLister, _ := NewPeriodicMetricLister(basicLister, updateInterval) - seriesRegistry := NewBasicSeriesRegistry(periodicLister, mapper) return &prometheusProvider{ diff --git a/pkg/custom-provider/query_builder.go b/pkg/custom-provider/query_builder.go index 64db037b..ebaa8037 100644 --- a/pkg/custom-provider/query_builder.go +++ b/pkg/custom-provider/query_builder.go @@ -9,6 +9,7 @@ import ( prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" ) +//QueryBuilder provides functions for generating Prometheus queries. type QueryBuilder interface { BuildSelector(seriesName string, groupBy string, groupBySlice []string, queryParts []queryPart) (prom.Selector, error) } @@ -17,6 +18,7 @@ type queryBuilder struct { metricsQueryTemplate *template.Template } +//NewQueryBuilder creates a QueryBuilder. func NewQueryBuilder(metricsQuery string) (QueryBuilder, error) { metricsQueryTemplate, err := template.New("metrics-query").Delims("<<", ">>").Parse(metricsQuery) if err != nil { diff --git a/pkg/custom-provider/query_builder_test.go b/pkg/custom-provider/query_builder_test.go index 300a7cf7..308794c7 100644 --- a/pkg/custom-provider/query_builder_test.go +++ b/pkg/custom-provider/query_builder_test.go @@ -79,4 +79,4 @@ func TestQueryWithGroupBy(t *testing.T) { require.Equal(t, selector, expectation) } -//TODO: Ensure that the LabelValuesByName and GroupBySlice placeholders function correctly. +//TODO: AC - Ensure that the LabelValuesByName and GroupBySlice placeholders function correctly. diff --git a/pkg/custom-provider/resource_converter.go b/pkg/custom-provider/resource_converter.go index 006ec12f..6008ac02 100644 --- a/pkg/custom-provider/resource_converter.go +++ b/pkg/custom-provider/resource_converter.go @@ -17,6 +17,9 @@ import ( pmodel "github.com/prometheus/common/model" ) +//ResourceConverter is a type for extracting associated Kubernetes GroupResource objects from +//Prometheus series and generating appropriate labels to target specific Kubernetes GroupResource +//objects. type ResourceConverter interface { // ResourcesForSeries returns the group-resources associated with the given series, // as well as whether or not the given series has the "namespace" resource). @@ -34,6 +37,7 @@ type resourceConverter struct { labelTemplate *template.Template } +//NewResourceConverter creates a ResourceConverter that will use the provided parameters to map data between Prometheus and Kubernetes. func NewResourceConverter(resourceTemplate string, overrides map[string]config.GroupResource, mapper apimeta.RESTMapper) (ResourceConverter, error) { converter := &resourceConverter{ labelToResource: make(map[pmodel.LabelName]schema.GroupResource), diff --git a/pkg/custom-provider/series_filterer.go b/pkg/custom-provider/series_filterer.go index 5a05444f..303b572d 100644 --- a/pkg/custom-provider/series_filterer.go +++ b/pkg/custom-provider/series_filterer.go @@ -7,6 +7,8 @@ import ( "github.com/directxman12/k8s-prometheus-adapter/pkg/config" ) +//SeriesFilterer provides functions for filtering collections of Prometheus series +//to only those that meet certain requirements. type SeriesFilterer interface { FilterSeries(series []prom.Series) []prom.Series AddRequirement(filter config.RegexFilter) error @@ -16,6 +18,8 @@ type seriesFilterer struct { seriesMatchers []*reMatcher } +//NewSeriesFilterer creates a SeriesFilterer that will remove any series that do not +//meet the requirements of the provided RegexFilter(s). func NewSeriesFilterer(filters []config.RegexFilter) (SeriesFilterer, error) { seriesMatchers := make([]*reMatcher, len(filters)) for i, filterRaw := range filters { diff --git a/pkg/custom-provider/series_registry.go b/pkg/custom-provider/series_registry.go index 092f7fe4..77b58726 100644 --- a/pkg/custom-provider/series_registry.go +++ b/pkg/custom-provider/series_registry.go @@ -21,7 +21,6 @@ import ( "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" apimeta "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/labels" prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" "github.com/directxman12/k8s-prometheus-adapter/pkg/config" @@ -49,8 +48,6 @@ type SeriesRegistry interface { // SeriesForMetric looks up the minimum required series information to make a query for the given metric // against the given resource (namespace may be empty for non-namespaced resources) QueryForMetric(info provider.CustomMetricInfo, namespace string, resourceNames ...string) (query prom.Selector, found bool) - // TODO: Don't house the external metric stuff side-by-side with the custom metric stuff. - QueryForExternalMetric(info provider.ExternalMetricInfo, metricSelector labels.Selector) (query prom.Selector, found bool) // MatchValuesToNames matches result values to resource names for the given metric and value set MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool) } @@ -78,6 +75,7 @@ type basicSeriesRegistry struct { metricLister MetricListerWithNotification } +//NewBasicSeriesRegistry creates a SeriesRegistry driven by the data from the provided MetricLister. func NewBasicSeriesRegistry(lister MetricListerWithNotification, mapper apimeta.RESTMapper) SeriesRegistry { var registry = basicSeriesRegistry{ mapper: mapper, @@ -93,7 +91,7 @@ func (r *basicSeriesRegistry) filterMetrics(result metricUpdateResult) metricUpd namers := make([]MetricNamer, 0) series := make([][]prom.Series, 0) - targetType := config.MetricType("Custom") + targetType := config.Custom for i, namer := range result.namers { if namer.MetricType() == targetType { @@ -205,30 +203,6 @@ func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.CustomMetricInf return query, true } -func (r *basicSeriesRegistry) QueryForExternalMetric(metricInfo provider.ExternalMetricInfo, metricSelector labels.Selector) (query prom.Selector, found bool) { - r.mu.RLock() - defer r.mu.RUnlock() - //TODO: Implementation - return "", false - // info, infoFound := r.info[metricInfo] - // if !infoFound { - // //TODO: Weird that it switches between types here. - // glog.V(10).Infof("metric %v not registered", metricInfo) - // return "", false - // } - - // query, err := info.namer.QueryForExternalSeries(info.seriesName, metricSelector) - // if err != nil { - // //TODO: See what was being .String() and implement that for ExternalMetricInfo. - // // errorVal := metricInfo.String() - // errorVal := "something" - // glog.Errorf("unable to construct query for metric %s: %v", errorVal, err) - // return "", false - // } - - // return query, true -} - func (r *basicSeriesRegistry) MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool) { r.mu.RLock() defer r.mu.RUnlock()