diff --git a/pkg/custom-provider/external_metric_query_builder.go b/pkg/custom-provider/external_metric_query_builder.go index 411f0de2..60edbad3 100644 --- a/pkg/custom-provider/external_metric_query_builder.go +++ b/pkg/custom-provider/external_metric_query_builder.go @@ -4,12 +4,13 @@ import ( "fmt" s "strings" + provider "github.com/directxman12/k8s-prometheus-adapter/pkg/custom-provider/metric-converter" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" ) type ExternalMetricQueryBuilder interface { - BuildPrometheusQuery(namespace string, metricName string, metricSelector labels.Selector) string + BuildPrometheusQuery(namespace string, metricName string, metricSelector labels.Selector, queryMetadata provider.QueryMetadata) string } type externalMetricQueryBuilder struct { @@ -19,13 +20,27 @@ func NewExternalMetricQueryBuilder() ExternalMetricQueryBuilder { return &externalMetricQueryBuilder{} } -func (p *externalMetricQueryBuilder) BuildPrometheusQuery(namespace string, metricName string, metricSelector labels.Selector) string { - namespaceSelector := p.makeLabelFilter("namespace", "=", namespace) - otherSelectors := p.convertSelectors(metricSelector) +func (p *externalMetricQueryBuilder) BuildPrometheusQuery(namespace string, metricName string, metricSelector labels.Selector, queryMetadata provider.QueryMetadata) string { + //TODO: At least for my Prometheus install, the "namespace" label doesn't seem to be + //directly applied to the time series. I'm using prometheus-operator. The grafana dashboards + //seem to query for the pods in a namespace from kube_pod_info and then apply pod-specific + //label filters. This might need some more thought. Disabling for now. + // namespaceSelector := p.makeLabelFilter("namespace", "=", namespace) + labelSelectors := p.convertSelectors(metricSelector) + joinedLabels := s.Join(labelSelectors, ", ") - finalTargets := append([]string{namespaceSelector}, otherSelectors...) - joinedLabels := s.Join(finalTargets, ", ") - return fmt.Sprintf("%s{%s}", metricName, joinedLabels) + //TODO: Both the aggregation method and window should probably be configurable. + //I don't think we can make assumptions about the nature of someone's metrics. + //I'm guessing this might be covered by the recently added advanced configuration + //code, but I haven't yet had an opportunity to dig into that and understand it. + //We'll leave this here for testing purposes for now. + //As reasonable defaults, maybe: + //rate(...) for counters + //avg_over_time(...) for gauges + //I'm guessing that SeriesRegistry might store the metric type, but I haven't looked yet. + aggregation := queryMetadata.Aggregation + window := queryMetadata.WindowInSeconds + return fmt.Sprintf("%s(%s{%s}[%ss])", aggregation, metricName, joinedLabels, window) } func (p *externalMetricQueryBuilder) makeLabelFilter(labelName string, operator string, targetValue string) string { @@ -72,7 +87,9 @@ func (p *externalMetricQueryBuilder) selectOperator(operator selection.Operator, func (p *externalMetricQueryBuilder) selectRegexOperator(operator selection.Operator) string { switch operator { case selection.Equals: + case selection.In: return "=~" + case selection.NotIn: case selection.NotEquals: return "!~" } diff --git a/pkg/custom-provider/external_provider.go b/pkg/custom-provider/external_provider.go index 58abb95a..aabcec5b 100644 --- a/pkg/custom-provider/external_provider.go +++ b/pkg/custom-provider/external_provider.go @@ -66,7 +66,14 @@ func NewExternalPrometheusProvider(mapper apimeta.RESTMapper, kubeClient dynamic } func (p *externalPrometheusProvider) GetExternalMetric(namespace string, metricName string, metricSelector labels.Selector) (*external_metrics.ExternalMetricValueList, error) { - query := p.queryBuilder.BuildPrometheusQuery(namespace, metricName, metricSelector) + //TODO: Get the appropriate time window and aggregation type from somewhere + //based on the metric being selected. Does SeriesRegistry have the metric type cached? + queryMetadata := conv.QueryMetadata{ + MetricName: metricName, + WindowInSeconds: 120, + Aggregation: "rate", + } + query := p.queryBuilder.BuildPrometheusQuery(namespace, metricName, metricSelector, queryMetadata) selector := prom.Selector(query) //TODO: I don't yet know what a context is, but apparently I should use a real one. @@ -78,10 +85,6 @@ func (p *externalPrometheusProvider) GetExternalMetric(namespace string, metricN return nil, err } - queryMetadata := conv.QueryMetadata{ - MetricName: metricName, - WindowInSeconds: 0, - } return p.metricConverter.Convert(queryMetadata, queryResults) } diff --git a/pkg/custom-provider/metric-converter/matrix_converter.go b/pkg/custom-provider/metric-converter/matrix_converter.go index 9b0be424..e13c8404 100644 --- a/pkg/custom-provider/metric-converter/matrix_converter.go +++ b/pkg/custom-provider/metric-converter/matrix_converter.go @@ -33,5 +33,5 @@ func (c *matrixConverter) Convert(metadata QueryMetadata, queryResult prom.Query func (c *matrixConverter) convert(result *model.Matrix) (*external_metrics.ExternalMetricValueList, error) { //TODO: Implementation. - return nil, nil + return nil, errors.New("converting Matrix results is not yet supported") } diff --git a/pkg/custom-provider/metric-converter/query_metadata.go b/pkg/custom-provider/metric-converter/query_metadata.go index f8ef28e9..613cd3ea 100644 --- a/pkg/custom-provider/metric-converter/query_metadata.go +++ b/pkg/custom-provider/metric-converter/query_metadata.go @@ -8,4 +8,6 @@ package provider type QueryMetadata struct { MetricName string WindowInSeconds int64 + //TODO: Type this? + Aggregation string } diff --git a/pkg/custom-provider/metric-converter/sample_converter.go b/pkg/custom-provider/metric-converter/sample_converter.go new file mode 100644 index 00000000..8216ff80 --- /dev/null +++ b/pkg/custom-provider/metric-converter/sample_converter.go @@ -0,0 +1,53 @@ +package provider + +import ( + "github.com/prometheus/common/model" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/metrics/pkg/apis/external_metrics" +) + +type sampleConverter struct { +} + +//SampleConverter is capable of translating Prometheus Sample objects +//into ExternamMetricValue objects. +type SampleConverter interface { + Convert(metadata QueryMetadata, sample *model.Sample) (*external_metrics.ExternalMetricValue, error) +} + +//NewSampleConverter creates a SampleConverter capable of translating Prometheus Sample objects +//into ExternamMetricValue objects. +func NewSampleConverter() SampleConverter { + return &sampleConverter{} +} + +func (c *sampleConverter) Convert(metadata QueryMetadata, sample *model.Sample) (*external_metrics.ExternalMetricValue, error) { + labels := c.convertLabels(sample.Metric) + + singleMetric := external_metrics.ExternalMetricValue{ + MetricName: string(sample.Metric[model.LabelName("__name__")]), + Timestamp: metav1.Time{ + sample.Timestamp.Time(), + }, + WindowSeconds: &metadata.WindowInSeconds, + //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), + MetricLabels: labels, + } + + //TODO: Actual errors? + return &singleMetric, nil +} + +func (c *sampleConverter) convertLabels(inLabels model.Metric) map[string]string { + numLabels := len(inLabels) + outLabels := make(map[string]string, numLabels) + for labelName, labelVal := range inLabels { + outLabels[string(labelName)] = string(labelVal) + } + + return outLabels +} diff --git a/pkg/custom-provider/metric-converter/vector_converter.go b/pkg/custom-provider/metric-converter/vector_converter.go index 28a88268..4174e2bd 100644 --- a/pkg/custom-provider/metric-converter/vector_converter.go +++ b/pkg/custom-provider/metric-converter/vector_converter.go @@ -5,18 +5,19 @@ import ( prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" "github.com/prometheus/common/model" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/metrics/pkg/apis/external_metrics" ) type vectorConverter struct { + SampleConverter SampleConverter } //NewVectorConverter creates a VectorConverter capable of converting //vector Prometheus query results into external metric types. -func NewVectorConverter() MetricConverter { - return &vectorConverter{} +func NewVectorConverter(sampleConverter *SampleConverter) MetricConverter { + return &vectorConverter{ + SampleConverter: *sampleConverter, + } } func (c *vectorConverter) Convert(metadata QueryMetadata, queryResult prom.QueryResult) (*external_metrics.ExternalMetricValueList, error) { @@ -45,20 +46,13 @@ func (c *vectorConverter) convert(metadata QueryMetadata, result model.Vector) ( } for _, val := range result { - singleMetric := external_metrics.ExternalMetricValue{ - MetricName: string(val.Metric[model.LabelName("__name__")]), - Timestamp: metav1.Time{ - val.Timestamp.Time(), - }, - WindowSeconds: &metadata.WindowInSeconds, - //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(val.Value)), resource.DecimalSI), - } - - items = append(items, singleMetric) + //TODO: Care about potential errors here. + singleMetric, _ := c.SampleConverter.Convert(metadata, val) + items = append(items, *singleMetric) } + metricValueList = external_metrics.ExternalMetricValueList{ + Items: items, + } return &metricValueList, nil }