Cleaning up.

* Wiped out a ton of warnings about comments.
* Created consts for `MetricType` values.
* `externalInfoMap` can now track multiple series with the same name/namespace and different labels.
* `namespace` parameter of external metrics queries is now respected (albeit very rudimentarily)
* Metric converter values for external metrics are now converted the same way as for custom metrics (probably still some opportunity for consolidation).
* Lots of TODOs actually done.
* Deleted a lot of commented out code.
This commit is contained in:
Tony Compton 2018-07-20 15:35:24 -04:00
parent 9641e70005
commit a94494337e
18 changed files with 155 additions and 188 deletions

View file

@ -34,7 +34,10 @@ type DiscoveryRule struct {
MetricsQuery string `yaml:"metricsQuery,omitempty"` MetricsQuery string `yaml:"metricsQuery,omitempty"`
// MetricType identifies whether the metrics derived from this rule should be classified // MetricType identifies whether the metrics derived from this rule should be classified
// as external or custom metrics. // 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. // 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. //MetricType identifies whether a given metric should be handled and interpreted as a Custom or External metric.
type MetricType string 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"
)

View file

@ -48,13 +48,15 @@ type MetricLister interface {
ListAllMetrics() (metricUpdateResult, error) ListAllMetrics() (metricUpdateResult, error)
} }
//A MetricListerWithNotification is a MetricLister that has the ability to notify listeners
//when new metric data is available.
type MetricListerWithNotification interface { type MetricListerWithNotification interface {
//It can list metrics, just like a normal MetricLister.
MetricLister MetricLister
//Because it periodically pulls metrics, it needs to be Runnable.
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)) AddNotificationReceiver(func(metricUpdateResult))
//UpdateNow forces an immediate refresh from the source data. Primarily for test purposes.
UpdateNow() UpdateNow()
} }
@ -64,6 +66,7 @@ type basicMetricLister struct {
lookback time.Duration 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 { func NewBasicMetricLister(promClient prom.Client, namers []MetricNamer, lookback time.Duration) MetricLister {
lister := basicMetricLister{ lister := basicMetricLister{
promClient: promClient, promClient: promClient,

View file

@ -5,24 +5,37 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
) )
//ExportedMetric is a description of an available metric.
type ExportedMetric struct { type ExportedMetric struct {
MetricName string MetricName string
Labels labels.Set Labels labels.Set
Namespace string Namespace string
} }
//ExternalInfoMap is a data object that accepts and organizes information
//about available metrics.
type ExternalInfoMap interface { type ExternalInfoMap interface {
//Begins tracking a metric, returning it to the caller.
TrackMetric(metricName string, generatedBy MetricNamer) ExternalMetricData TrackMetric(metricName string, generatedBy MetricNamer) ExternalMetricData
//Exports a collection of all of the metrics currently being tracked.
ExportMetrics() []ExportedMetric ExportMetrics() []ExportedMetric
//Finds a tracked metric with the given metric name, if it exists.
FindMetric(metricName string) (data ExternalMetricData, found bool) 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 { type ExternalMetricData interface {
//MetricName returns the name of the metric represented by this object.
MetricName() string MetricName() string
//WithSeries associates the provided labels with this metric.
WithSeries(labels labels.Set) WithSeries(labels labels.Set)
//WithNamespacedSeries associates the provided labels with this metric, but within a particular namespace.
WithNamespacedSeries(namespace string, labels labels.Set) WithNamespacedSeries(namespace string, labels labels.Set)
//Exports a collection of all the metrics currently being tracked.
ExportMetrics() []ExportedMetric 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 { type externalInfoMap struct {
@ -31,18 +44,20 @@ type externalInfoMap struct {
type externalMetricData struct { type externalMetricData struct {
metricName string metricName string
namespacedData map[string]labels.Set namespacedData map[string][]labels.Set
generatedBy MetricNamer generatedBy MetricNamer
} }
//NewExternalMetricData creates an ExternalMetricData for the provided metric name and namer.
func NewExternalMetricData(metricName string, generatedBy MetricNamer) ExternalMetricData { func NewExternalMetricData(metricName string, generatedBy MetricNamer) ExternalMetricData {
return &externalMetricData{ return &externalMetricData{
metricName: metricName, metricName: metricName,
generatedBy: generatedBy, 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 { func NewExternalInfoMap() ExternalInfoMap {
return &externalInfoMap{ return &externalInfoMap{
metrics: map[string]ExternalMetricData{}, metrics: map[string]ExternalMetricData{},
@ -78,18 +93,20 @@ func (d *externalMetricData) MetricName() string {
return d.metricName return d.metricName
} }
func (d *externalMetricData) GenerateQuery(selector labels.Selector) (prom.Selector, error) { func (d *externalMetricData) GenerateQuery(namespace string, selector labels.Selector) (prom.Selector, error) {
return d.generatedBy.QueryForExternalSeries(d.metricName, selector) return d.generatedBy.QueryForExternalSeries(namespace, d.metricName, selector)
} }
func (d *externalMetricData) ExportMetrics() []ExportedMetric { func (d *externalMetricData) ExportMetrics() []ExportedMetric {
results := make([]ExportedMetric, 0) results := make([]ExportedMetric, 0)
for namespace, labels := range d.namespacedData { for namespace, labelSets := range d.namespacedData {
results = append(results, ExportedMetric{ for _, labelSet := range labelSets {
Labels: labels, results = append(results, ExportedMetric{
MetricName: d.metricName, Labels: labelSet,
Namespace: namespace, MetricName: d.metricName,
}) Namespace: namespace,
})
}
} }
return results return results
@ -99,10 +116,13 @@ func (d *externalMetricData) WithSeries(labels labels.Set) {
d.WithNamespacedSeries("", labels) 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] data, found := d.namespacedData[namespace]
if !found { if !found {
data = labels data = []labels.Set{}
d.namespacedData[namespace] = data
} }
data = append(data, seriesLabels)
d.namespacedData[namespace] = data
} }

View file

@ -15,16 +15,7 @@ import (
conv "github.com/directxman12/k8s-prometheus-adapter/pkg/custom-provider/metric-converter" 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: AC - 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.
type externalPrometheusProvider struct { type externalPrometheusProvider struct {
promClient prom.Client promClient prom.Client
metricConverter conv.MetricConverter metricConverter conv.MetricConverter
@ -32,13 +23,7 @@ type externalPrometheusProvider struct {
seriesRegistry ExternalSeriesRegistry seriesRegistry ExternalSeriesRegistry
} }
//TODO: It probably makes more sense to, once this is functional and complete, roll the //NewExternalPrometheusProvider creates an ExternalMetricsProvider capable of responding to Kubernetes requests for external metric data.
//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.
func NewExternalPrometheusProvider(seriesRegistry ExternalSeriesRegistry, promClient prom.Client, converter conv.MetricConverter) provider.ExternalMetricsProvider { func NewExternalPrometheusProvider(seriesRegistry ExternalSeriesRegistry, promClient prom.Client, converter conv.MetricConverter) provider.ExternalMetricsProvider {
return &externalPrometheusProvider{ return &externalPrometheusProvider{
promClient: promClient, 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) { 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 { if !found {
return &external_metrics.ExternalMetricValueList{ return &external_metrics.ExternalMetricValueList{
Items: []external_metrics.ExternalMetricValue{}, Items: []external_metrics.ExternalMetricValue{},
}, nil }, 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) queryResults, err := p.promClient.Query(context.TODO(), pmodel.Now(), selector)
if err != nil { 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 return nil, err
} }

View file

@ -14,31 +14,33 @@ import (
"github.com/directxman12/k8s-prometheus-adapter/pkg/config" "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 { type ExternalSeriesRegistry interface {
// ListAllMetrics lists all metrics known to this registry // ListAllMetrics lists all metrics known to this registry
ListAllMetrics() []provider.ExternalMetricInfo 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 // overridableSeriesRegistry is a basic SeriesRegistry
type externalSeriesRegistry struct { type externalSeriesRegistry struct {
mu sync.RWMutex mu sync.RWMutex
externalInfo map[string]seriesInfo
// metrics is the list of all known metrics // metrics is the list of all known metrics
metrics []provider.ExternalMetricInfo metrics []provider.ExternalMetricInfo
mapper apimeta.RESTMapper mapper apimeta.RESTMapper
metricLister MetricListerWithNotification metricLister MetricListerWithNotification
tonyExternalInfo ExternalInfoMap externalMetricInfo ExternalInfoMap
} }
//NewExternalSeriesRegistry creates an ExternalSeriesRegistry driven by the data from the provided MetricLister.
func NewExternalSeriesRegistry(lister MetricListerWithNotification, mapper apimeta.RESTMapper) ExternalSeriesRegistry { func NewExternalSeriesRegistry(lister MetricListerWithNotification, mapper apimeta.RESTMapper) ExternalSeriesRegistry {
var registry = externalSeriesRegistry{ var registry = externalSeriesRegistry{
mapper: mapper, mapper: mapper,
metricLister: lister, metricLister: lister,
tonyExternalInfo: NewExternalInfoMap(), externalMetricInfo: NewExternalInfoMap(),
} }
lister.AddNotificationReceiver(registry.onNewDataAvailable) lister.AddNotificationReceiver(registry.onNewDataAvailable)
@ -50,7 +52,7 @@ func (r *externalSeriesRegistry) filterMetrics(result metricUpdateResult) metric
namers := make([]MetricNamer, 0) namers := make([]MetricNamer, 0)
series := make([][]prom.Series, 0) series := make([][]prom.Series, 0)
targetType := config.MetricType("External") targetType := config.External
for i, namer := range result.namers { for i, namer := range result.namers {
if namer.MetricType() == targetType { if namer.MetricType() == targetType {
@ -98,10 +100,16 @@ func (r *externalSeriesRegistry) onNewDataAvailable(result metricUpdateResult) {
// namespaced := identity.namespaced // namespaced := identity.namespaced
name := identity.name name := identity.name
labels := r.convertLabels(series.Labels) 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 := 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() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
r.tonyExternalInfo = updatedCache r.externalMetricInfo = updatedCache
r.metrics = convertedMetrics r.metrics = convertedMetrics
} }
@ -135,29 +143,20 @@ func (r *externalSeriesRegistry) ListAllMetrics() []provider.ExternalMetricInfo
return r.metrics 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() r.mu.RLock()
defer r.mu.RUnlock() defer r.mu.RUnlock()
metric, found := r.tonyExternalInfo.FindMetric(metricName) metric, found := r.externalMetricInfo.FindMetric(metricName)
if !found { if !found {
glog.V(10).Infof("external metric %q not registered", metricName)
return "", false return "", false
} }
query, err := metric.GenerateQuery(metricSelector) query, err := metric.GenerateQuery(namespace, 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 := info.namer.QueryForExternalSeries(info.seriesName, metricSelector)
if err != nil { if err != nil {
//TODO: See what was being .String() and implement that for ExternalMetricInfo. glog.Errorf("unable to construct query for external metric %s: %v", metricName, err)
// errorVal := metricInfo.String()
errorVal := "something"
glog.Errorf("unable to construct query for metric %s: %v", errorVal, err)
return "", false return "", false
} }

View file

@ -32,6 +32,6 @@ func (c *matrixConverter) Convert(queryResult prom.QueryResult) (*external_metri
} }
func (c *matrixConverter) convert(result *model.Matrix) (*external_metrics.ExternalMetricValueList, error) { 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") return nil, errors.New("converting Matrix results is not yet supported")
} }

View file

@ -30,14 +30,10 @@ func (c *sampleConverter) Convert(sample *model.Sample) (*external_metrics.Exter
Timestamp: metav1.Time{ Timestamp: metav1.Time{
sample.Timestamp.Time(), sample.Timestamp.Time(),
}, },
//TODO: I'm not so sure about this type/conversions. Value: *resource.NewMilliQuantity(int64(sample.Value*1000.0), resource.DecimalSI),
//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, MetricLabels: labels,
} }
//TODO: Actual errors?
return &singleMetric, nil return &singleMetric, nil
} }

View file

@ -35,18 +35,11 @@ func (c *scalarConverter) Convert(queryResult prom.QueryResult) (*external_metri
func (c *scalarConverter) convert(input *model.Scalar) (*external_metrics.ExternalMetricValueList, error) { func (c *scalarConverter) convert(input *model.Scalar) (*external_metrics.ExternalMetricValueList, error) {
result := external_metrics.ExternalMetricValueList{ 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{ Items: []external_metrics.ExternalMetricValue{
{ {
Timestamp: metav1.Time{ Timestamp: metav1.Time{
input.Timestamp.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), Value: *resource.NewMilliQuantity(int64(input.Value*1000.0), resource.DecimalSI),
}, },
}, },

View file

@ -2,6 +2,7 @@ package provider
import ( import (
"errors" "errors"
"fmt"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -46,8 +47,13 @@ func (c *vectorConverter) convert(result model.Vector) (*external_metrics.Extern
} }
for _, val := range result { 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) items = append(items, *singleMetric)
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/directxman12/k8s-prometheus-adapter/pkg/config" "github.com/directxman12/k8s-prometheus-adapter/pkg/config"
) )
//MetricNameConverter provides functions for naming custom metrics from Promethes series.
type MetricNameConverter interface { type MetricNameConverter interface {
GetMetricNameForSeries(series prom.Series) (string, error) GetMetricNameForSeries(series prom.Series) (string, error)
} }
@ -17,6 +18,8 @@ type metricNameConverter struct {
nameAs string nameAs string
} }
//NewMetricNameConverter creates a MetricNameConverter capable of translating Prometheus series names
//into custom metric names.
func NewMetricNameConverter(mapping config.NameMapping) (MetricNameConverter, error) { func NewMetricNameConverter(mapping config.NameMapping) (MetricNameConverter, error) {
var nameMatches *regexp.Regexp var nameMatches *regexp.Regexp
var err error var err error

View file

@ -34,9 +34,10 @@ type MetricNamer interface {
// QueryForSeries returns the query for a given series (not API metric name), with // QueryForSeries returns the query for a given series (not API metric name), with
// the given namespace name (if relevant), resource, and resource names. // the given namespace name (if relevant), resource, and resource names.
QueryForSeries(series string, resource schema.GroupResource, namespace string, names ...string) (prom.Selector, error) 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) IdentifySeries(series prom.Series) (seriesIdentity, error)
MetricType() config.MetricType MetricType() config.MetricType
ExternalMetricNamespaceLabelName() string
} }
type seriesIdentity struct { type seriesIdentity struct {
@ -58,7 +59,8 @@ type metricNamer struct {
metricNameConverter MetricNameConverter metricNameConverter MetricNameConverter
mapper apimeta.RESTMapper mapper apimeta.RESTMapper
metricType config.MetricType metricType config.MetricType
externalMetricNamespaceLabel string
} }
// queryTemplateArgs are the arguments for the metrics query template. // queryTemplateArgs are the arguments for the metrics query template.
@ -74,6 +76,10 @@ func (n *metricNamer) MetricType() config.MetricType {
return n.metricType return n.metricType
} }
func (n *metricNamer) ExternalMetricNamespaceLabelName() string {
return n.externalMetricNamespaceLabel
}
func (n *metricNamer) IdentifySeries(series prom.Series) (seriesIdentity, error) { func (n *metricNamer) IdentifySeries(series prom.Series) (seriesIdentity, error) {
// TODO: warn if it doesn't match any resources // TODO: warn if it doesn't match any resources
resources, namespaced := n.resourceConverter.ResourcesForSeries(series) 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) 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{ namer := &metricNamer{
seriesQuery: prom.Selector(rule.SeriesQuery), seriesQuery: prom.Selector(rule.SeriesQuery),
mapper: mapper, mapper: mapper,
resourceConverter: resourceConverter, resourceConverter: resourceConverter,
queryBuilder: queryBuilder, queryBuilder: queryBuilder,
seriesFilterer: seriesFilterer, seriesFilterer: seriesFilterer,
metricNameConverter: metricNameConverter, metricNameConverter: metricNameConverter,
metricType: rule.MetricType, metricType: metricType,
externalMetricNamespaceLabel: namespaceLabel,
} }
namers[i] = namer namers[i] = namer
@ -236,8 +253,28 @@ func NamersFromConfig(cfg *config.MetricsDiscoveryConfig, mapper apimeta.RESTMap
return namers, nil return namers, nil
} }
func (n *metricNamer) QueryForExternalSeries(series string, metricSelector labels.Selector) (prom.Selector, error) { func (n *metricNamer) buildNamespaceQueryPartForExternalSeries(namespace string) (queryPart, error) {
queryParts := n.createQueryPartsFromSelector(metricSelector) 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) selector, err := n.queryBuilder.BuildSelector(series, "", []string{}, queryParts)
if err != nil { if err != nil {

View file

@ -87,69 +87,3 @@ func (l *periodicMetricLister) notifyListeners() {
func (l *periodicMetricLister) UpdateNow() { func (l *periodicMetricLister) UpdateNow() {
l.updateMetrics() 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
// }

View file

@ -46,12 +46,11 @@ type prometheusProvider struct {
SeriesRegistry 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) { 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) basicLister := NewBasicMetricLister(promClient, namers, updateInterval)
//TODO: Be sure to run this runnable.
// periodicLister, periodicRunnable := NewPeriodicMetricLister(basicLister, updateInterval)
periodicLister, _ := NewPeriodicMetricLister(basicLister, updateInterval) periodicLister, _ := NewPeriodicMetricLister(basicLister, updateInterval)
seriesRegistry := NewBasicSeriesRegistry(periodicLister, mapper) seriesRegistry := NewBasicSeriesRegistry(periodicLister, mapper)
return &prometheusProvider{ return &prometheusProvider{

View file

@ -9,6 +9,7 @@ import (
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
) )
//QueryBuilder provides functions for generating Prometheus queries.
type QueryBuilder interface { type QueryBuilder interface {
BuildSelector(seriesName string, groupBy string, groupBySlice []string, queryParts []queryPart) (prom.Selector, error) BuildSelector(seriesName string, groupBy string, groupBySlice []string, queryParts []queryPart) (prom.Selector, error)
} }
@ -17,6 +18,7 @@ type queryBuilder struct {
metricsQueryTemplate *template.Template metricsQueryTemplate *template.Template
} }
//NewQueryBuilder creates a QueryBuilder.
func NewQueryBuilder(metricsQuery string) (QueryBuilder, error) { func NewQueryBuilder(metricsQuery string) (QueryBuilder, error) {
metricsQueryTemplate, err := template.New("metrics-query").Delims("<<", ">>").Parse(metricsQuery) metricsQueryTemplate, err := template.New("metrics-query").Delims("<<", ">>").Parse(metricsQuery)
if err != nil { if err != nil {

View file

@ -79,4 +79,4 @@ func TestQueryWithGroupBy(t *testing.T) {
require.Equal(t, selector, expectation) 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.

View file

@ -17,6 +17,9 @@ import (
pmodel "github.com/prometheus/common/model" 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 { type ResourceConverter interface {
// ResourcesForSeries returns the group-resources associated with the given series, // ResourcesForSeries returns the group-resources associated with the given series,
// as well as whether or not the given series has the "namespace" resource). // as well as whether or not the given series has the "namespace" resource).
@ -34,6 +37,7 @@ type resourceConverter struct {
labelTemplate *template.Template 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) { func NewResourceConverter(resourceTemplate string, overrides map[string]config.GroupResource, mapper apimeta.RESTMapper) (ResourceConverter, error) {
converter := &resourceConverter{ converter := &resourceConverter{
labelToResource: make(map[pmodel.LabelName]schema.GroupResource), labelToResource: make(map[pmodel.LabelName]schema.GroupResource),

View file

@ -7,6 +7,8 @@ import (
"github.com/directxman12/k8s-prometheus-adapter/pkg/config" "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 { type SeriesFilterer interface {
FilterSeries(series []prom.Series) []prom.Series FilterSeries(series []prom.Series) []prom.Series
AddRequirement(filter config.RegexFilter) error AddRequirement(filter config.RegexFilter) error
@ -16,6 +18,8 @@ type seriesFilterer struct {
seriesMatchers []*reMatcher 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) { func NewSeriesFilterer(filters []config.RegexFilter) (SeriesFilterer, error) {
seriesMatchers := make([]*reMatcher, len(filters)) seriesMatchers := make([]*reMatcher, len(filters))
for i, filterRaw := range filters { for i, filterRaw := range filters {

View file

@ -21,7 +21,6 @@ import (
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider" "github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/provider"
apimeta "k8s.io/apimachinery/pkg/api/meta" apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/labels"
prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client" prom "github.com/directxman12/k8s-prometheus-adapter/pkg/client"
"github.com/directxman12/k8s-prometheus-adapter/pkg/config" "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 // 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) // 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) 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 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) MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool)
} }
@ -78,6 +75,7 @@ type basicSeriesRegistry struct {
metricLister MetricListerWithNotification metricLister MetricListerWithNotification
} }
//NewBasicSeriesRegistry creates a SeriesRegistry driven by the data from the provided MetricLister.
func NewBasicSeriesRegistry(lister MetricListerWithNotification, mapper apimeta.RESTMapper) SeriesRegistry { func NewBasicSeriesRegistry(lister MetricListerWithNotification, mapper apimeta.RESTMapper) SeriesRegistry {
var registry = basicSeriesRegistry{ var registry = basicSeriesRegistry{
mapper: mapper, mapper: mapper,
@ -93,7 +91,7 @@ func (r *basicSeriesRegistry) filterMetrics(result metricUpdateResult) metricUpd
namers := make([]MetricNamer, 0) namers := make([]MetricNamer, 0)
series := make([][]prom.Series, 0) series := make([][]prom.Series, 0)
targetType := config.MetricType("Custom") targetType := config.Custom
for i, namer := range result.namers { for i, namer := range result.namers {
if namer.MetricType() == targetType { if namer.MetricType() == targetType {
@ -205,30 +203,6 @@ func (r *basicSeriesRegistry) QueryForMetric(metricInfo provider.CustomMetricInf
return query, true 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) { func (r *basicSeriesRegistry) MatchValuesToNames(metricInfo provider.CustomMetricInfo, values pmodel.Vector) (matchedValues map[string]pmodel.SampleValue, found bool) {
r.mu.RLock() r.mu.RLock()
defer r.mu.RUnlock() defer r.mu.RUnlock()