Skip to content

Commit

Permalink
Add Prometheus metrics reader factory and config (#3049)
Browse files Browse the repository at this point in the history
* Add metrics reader factory and config

Signed-off-by: albertteoh <albert.teoh@logz.io>

* Address review comments

Signed-off-by: albertteoh <albert.teoh@logz.io>
  • Loading branch information
albertteoh authored Jun 3, 2021
1 parent c0fb781 commit 8b1fbe6
Show file tree
Hide file tree
Showing 12 changed files with 734 additions and 0 deletions.
23 changes: 23 additions & 0 deletions pkg/prometheus/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config

import "time"

// Configuration describes the options to customize the storage behavior.
type Configuration struct {
HostPort string `validate:"nonzero" mapstructure:"server"`
ConnectTimeout time.Duration `validate:"nonzero" mapstructure:"timeout"`
}
15 changes: 15 additions & 0 deletions pkg/prometheus/config/empty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package config
101 changes: 101 additions & 0 deletions plugin/metrics/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metrics

import (
"flag"
"fmt"

"github.com/spf13/viper"
"go.uber.org/zap"

"github.com/jaegertracing/jaeger/plugin"
"github.com/jaegertracing/jaeger/plugin/metrics/prometheus"
"github.com/jaegertracing/jaeger/storage"
"github.com/jaegertracing/jaeger/storage/metricsstore"
)

const (
prometheusStorageType = "prometheus"
)

// AllStorageTypes defines all available storage backends.
var AllStorageTypes = []string{prometheusStorageType}

// Factory implements storage.Factory interface as a meta-factory for storage components.
type Factory struct {
FactoryConfig
factories map[string]storage.MetricsFactory
}

// NewFactory creates the meta-factory.
func NewFactory(config FactoryConfig) (*Factory, error) {
f := &Factory{FactoryConfig: config}
uniqueTypes := map[string]struct{}{
f.MetricsStorageType: {},
}
f.factories = make(map[string]storage.MetricsFactory)
for t := range uniqueTypes {
ff, err := f.getFactoryOfType(t)
if err != nil {
return nil, err
}
f.factories[t] = ff
}
return f, nil
}

func (f *Factory) getFactoryOfType(factoryType string) (storage.MetricsFactory, error) {
switch factoryType {
case prometheusStorageType:
return prometheus.NewFactory(), nil
}
return nil, fmt.Errorf("unknown metrics type %q. Valid types are %v", factoryType, AllStorageTypes)
}

// Initialize implements storage.MetricsFactory.
func (f *Factory) Initialize(logger *zap.Logger) error {
for _, factory := range f.factories {
factory.Initialize(logger)
}
return nil
}

// CreateMetricsReader implements storage.MetricsFactory.
func (f *Factory) CreateMetricsReader() (metricsstore.Reader, error) {
factory, ok := f.factories[f.MetricsStorageType]
if !ok {
return nil, fmt.Errorf("no %q backend registered for metrics store", f.MetricsStorageType)
}
return factory.CreateMetricsReader()
}

// AddFlags implements plugin.Configurable.
func (f *Factory) AddFlags(flagSet *flag.FlagSet) {
for _, factory := range f.factories {
if conf, ok := factory.(plugin.Configurable); ok {
conf.AddFlags(flagSet)
}
}
}

// InitFromViper implements plugin.Configurable.
func (f *Factory) InitFromViper(v *viper.Viper) {
for _, factory := range f.factories {
if conf, ok := factory.(plugin.Configurable); ok {
conf.InitFromViper(v)
}
}
}
36 changes: 36 additions & 0 deletions plugin/metrics/factory_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metrics

import (
"os"
)

const (
// StorageTypeEnvVar is the name of the env var that defines the type of backend used for metrics storage.
StorageTypeEnvVar = "METRICS_STORAGE_TYPE"
)

// FactoryConfig tells the Factory which types of backends it needs to create for different storage types.
type FactoryConfig struct {
MetricsStorageType string
}

// FactoryConfigFromEnv reads the desired types of storage backends from METRICS_STORAGE_TYPE.
func FactoryConfigFromEnv() FactoryConfig {
return FactoryConfig{
MetricsStorageType: os.Getenv(StorageTypeEnvVar),
}
}
42 changes: 42 additions & 0 deletions plugin/metrics/factory_config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metrics

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func clearEnv(t *testing.T) {
err := os.Setenv(StorageTypeEnvVar, "")
require.NoError(t, err)
}

func TestFactoryConfigFromEnv(t *testing.T) {
clearEnv(t)
defer clearEnv(t)

fc := FactoryConfigFromEnv()
assert.Empty(t, fc.MetricsStorageType)

err := os.Setenv(StorageTypeEnvVar, prometheusStorageType)
require.NoError(t, err)

fc = FactoryConfigFromEnv()
assert.Equal(t, prometheusStorageType, fc.MetricsStorageType)
}
108 changes: 108 additions & 0 deletions plugin/metrics/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package metrics

import (
"flag"
"testing"

"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

"github.com/jaegertracing/jaeger/storage"
"github.com/jaegertracing/jaeger/storage/mocks"
)

var _ storage.MetricsFactory = new(Factory)

func defaultCfg() FactoryConfig {
return FactoryConfig{
MetricsStorageType: prometheusStorageType,
}
}

func TestNewFactory(t *testing.T) {
f, err := NewFactory(defaultCfg())
require.NoError(t, err)
assert.NotEmpty(t, f.factories)
assert.NotEmpty(t, f.factories[prometheusStorageType])
assert.Equal(t, prometheusStorageType, f.MetricsStorageType)
}

func TestUnsupportedMetricsStorageType(t *testing.T) {
f, err := NewFactory(FactoryConfig{MetricsStorageType: "foo"})
require.Error(t, err)
assert.Nil(t, f)
assert.EqualError(t, err, `unknown metrics type "foo". Valid types are [prometheus]`)
}

func TestCreateMetricsReader(t *testing.T) {
f, err := NewFactory(defaultCfg())
require.NoError(t, err)
require.NotNil(t, f)

require.NoError(t, f.Initialize(zap.NewNop()))

reader, err := f.CreateMetricsReader()
require.NoError(t, err)
require.NotNil(t, reader)

f.MetricsStorageType = "foo"
reader, err = f.CreateMetricsReader()
require.Error(t, err)
require.Nil(t, reader)

assert.EqualError(t, err, `no "foo" backend registered for metrics store`)
}

type configurable struct {
mocks.MetricsFactory
flagSet *flag.FlagSet
viper *viper.Viper
}

// AddFlags implements plugin.Configurable.
func (f *configurable) AddFlags(flagSet *flag.FlagSet) {
f.flagSet = flagSet
}

// InitFromViper implements plugin.Configurable.
func (f *configurable) InitFromViper(v *viper.Viper) {
f.viper = v
}

func TestConfigurable(t *testing.T) {
clearEnv(t)
defer clearEnv(t)

f, err := NewFactory(defaultCfg())
require.NoError(t, err)
assert.NotEmpty(t, f.factories)
assert.NotEmpty(t, f.factories[prometheusStorageType])

mock := new(configurable)
f.factories[prometheusStorageType] = mock

fs := new(flag.FlagSet)
v := viper.New()

f.AddFlags(fs)
f.InitFromViper(v)

assert.Equal(t, fs, mock.flagSet)
assert.Equal(t, v, mock.viper)
}
59 changes: 59 additions & 0 deletions plugin/metrics/prometheus/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2021 The Jaeger Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package prometheus

import (
"flag"

"github.com/spf13/viper"
"go.uber.org/zap"

prometheusstore "github.com/jaegertracing/jaeger/plugin/metrics/prometheus/metricsstore"
"github.com/jaegertracing/jaeger/storage/metricsstore"
)

// Factory implements storage.Factory and creates storage components backed by memory store.
type Factory struct {
options *Options
logger *zap.Logger
}

// NewFactory creates a new Factory.
func NewFactory() *Factory {
return &Factory{
options: NewOptions("prometheus"),
}
}

// AddFlags implements plugin.Configurable.
func (f *Factory) AddFlags(flagSet *flag.FlagSet) {
f.options.AddFlags(flagSet)
}

// InitFromViper implements plugin.Configurable.
func (f *Factory) InitFromViper(v *viper.Viper) {
f.options.InitFromViper(v)
}

// Initialize implements storage.MetricsFactory.
func (f *Factory) Initialize(logger *zap.Logger) error {
f.logger = logger
return nil
}

// CreateMetricsReader implements storage.MetricsFactory.
func (f *Factory) CreateMetricsReader() (metricsstore.Reader, error) {
return prometheusstore.NewMetricsReader(f.logger, f.options.Primary.HostPort, f.options.Primary.ConnectTimeout)
}
Loading

0 comments on commit 8b1fbe6

Please sign in to comment.