Skip to content

Commit

Permalink
Initial commit of host metrics load scraper (open-telemetry#974)
Browse files Browse the repository at this point in the history
  • Loading branch information
james-bebbington authored and wyTrivail committed Jul 13, 2020
1 parent 7c1656f commit 7682d35
Show file tree
Hide file tree
Showing 15 changed files with 657 additions and 3 deletions.
2 changes: 2 additions & 0 deletions receiver/hostmetricsreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper"
)
Expand Down Expand Up @@ -63,6 +64,7 @@ func TestLoadConfig(t *testing.T) {
Scrapers: map[string]internal.Config{
cpuscraper.TypeStr: &cpuscraper.Config{ReportPerCPU: true},
diskscraper.TypeStr: &diskscraper.Config{},
loadscraper.TypeStr: &loadscraper.Config{},
filesystemscraper.TypeStr: &filesystemscraper.Config{},
memoryscraper.TypeStr: &memoryscraper.Config{},
networkscraper.TypeStr: &networkscraper.Config{},
Expand Down
2 changes: 1 addition & 1 deletion receiver/hostmetricsreceiver/example_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ receivers:
collection_interval: 1m
scrapers:
cpu:
report_per_cpu: false
load:
memory:
disk:
filesystem:
Expand Down
2 changes: 2 additions & 0 deletions receiver/hostmetricsreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/cpuscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/diskscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/filesystemscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/loadscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/memoryscraper"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/scraper/networkscraper"
)
Expand All @@ -53,6 +54,7 @@ func NewFactory() *Factory {
scraperFactories: map[string]internal.Factory{
cpuscraper.TypeStr: &cpuscraper.Factory{},
diskscraper.TypeStr: &diskscraper.Factory{},
loadscraper.TypeStr: &loadscraper.Factory{},
filesystemscraper.TypeStr: &filesystemscraper.Factory{},
memoryscraper.TypeStr: &memoryscraper.Factory{},
networkscraper.TypeStr: &networkscraper.Factory{},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2020, OpenTelemetry 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 loadscraper

import "go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"

// Config relating to Load Metric Scraper.
type Config struct {
internal.ConfigSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2020, OpenTelemetry 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 loadscraper

import (
"context"

"go.uber.org/zap"

"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
)

// This file implements Factory for Load scraper.

const (
// The value of "type" key in configuration.
TypeStr = "load"
)

// Factory is the Factory for scraper.
type Factory struct {
}

// CreateDefaultConfig creates the default configuration for the Scraper.
func (f *Factory) CreateDefaultConfig() internal.Config {
return &Config{}
}

// CreateMetricsScraper creates a scraper based on provided config.
func (f *Factory) CreateMetricsScraper(
ctx context.Context,
logger *zap.Logger,
config internal.Config,
) (internal.Scraper, error) {
cfg := config.(*Config)
return newLoadScraper(ctx, logger, cfg), nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2020, OpenTelemetry 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 loadscraper

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"go.uber.org/zap"
)

func TestCreateDefaultConfig(t *testing.T) {
factory := &Factory{}
cfg := factory.CreateDefaultConfig()
assert.IsType(t, &Config{}, cfg)
}

func TestCreateMetricsScraper(t *testing.T) {
factory := &Factory{}
cfg := &Config{}

scraper, err := factory.CreateMetricsScraper(context.Background(), zap.NewNop(), cfg)

assert.NoError(t, err)
assert.NotNil(t, scraper)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2020, OpenTelemetry 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 loadscraper

import (
"go.opentelemetry.io/collector/consumer/pdata"
)

// load metric constants

var metric1MLoadDescriptor = createMetric1MLoadDescriptor()

func createMetric1MLoadDescriptor() pdata.MetricDescriptor {
descriptor := pdata.NewMetricDescriptor()
descriptor.InitEmpty()
descriptor.SetName("host/load/1m")
descriptor.SetDescription("Average CPU Load over 1 minute.")
descriptor.SetUnit("1")
descriptor.SetType(pdata.MetricTypeGaugeDouble)
return descriptor
}

var metric5MLoadDescriptor = createMetric5MLoadDescriptor()

func createMetric5MLoadDescriptor() pdata.MetricDescriptor {
descriptor := pdata.NewMetricDescriptor()
descriptor.InitEmpty()
descriptor.SetName("host/load/5m")
descriptor.SetDescription("Average CPU Load over 5 minutes.")
descriptor.SetUnit("1")
descriptor.SetType(pdata.MetricTypeGaugeDouble)
return descriptor
}

var metric15MLoadDescriptor = createMetric15MLoadDescriptor()

func createMetric15MLoadDescriptor() pdata.MetricDescriptor {
descriptor := pdata.NewMetricDescriptor()
descriptor.InitEmpty()
descriptor.SetName("host/load/15m")
descriptor.SetDescription("Average CPU Load over 15 minutes.")
descriptor.SetUnit("1")
descriptor.SetType(pdata.MetricTypeGaugeDouble)
return descriptor
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2020, OpenTelemetry 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 loadscraper

import (
"context"
"time"

"go.opencensus.io/trace"
"go.uber.org/zap"

"go.opentelemetry.io/collector/consumer/pdata"
)

// scraper for Load Metrics
type scraper struct {
logger *zap.Logger
config *Config
}

// newLoadScraper creates a set of Load related metrics
func newLoadScraper(_ context.Context, logger *zap.Logger, cfg *Config) *scraper {
return &scraper{logger: logger, config: cfg}
}

// Initialize
func (s *scraper) Initialize(ctx context.Context) error {
return startSampling(ctx, s.logger)
}

// Close
func (s *scraper) Close(ctx context.Context) error {
return stopSampling(ctx)
}

// ScrapeMetrics
func (s *scraper) ScrapeMetrics(ctx context.Context) (pdata.MetricSlice, error) {
_, span := trace.StartSpan(ctx, "loadscraper.ScrapeMetrics")
defer span.End()

metrics := pdata.NewMetricSlice()

avgLoadValues, err := getSampledLoadAverages()
if err != nil {
return metrics, err
}

metrics.Resize(3)
initializeLoadMetric(metrics.At(0), metric1MLoadDescriptor, avgLoadValues.Load1)
initializeLoadMetric(metrics.At(1), metric5MLoadDescriptor, avgLoadValues.Load5)
initializeLoadMetric(metrics.At(2), metric15MLoadDescriptor, avgLoadValues.Load15)
return metrics, nil
}

func initializeLoadMetric(metric pdata.Metric, metricDescriptor pdata.MetricDescriptor, value float64) {
metricDescriptor.CopyTo(metric.MetricDescriptor())

idps := metric.DoubleDataPoints()
idps.Resize(1)
dp := idps.At(0)
dp.SetTimestamp(pdata.TimestampUnixNano(uint64(time.Now().UnixNano())))
dp.SetValue(value)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2020, OpenTelemetry 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.

// +build !windows

package loadscraper

import (
"context"

"github.com/shirou/gopsutil/load"
"go.uber.org/zap"
)

// unix based systems sample & compute load averages in the kernel, so nothing to do here
func startSampling(_ context.Context, _ *zap.Logger) error {
return nil
}

func stopSampling(_ context.Context) error {
return nil
}

func getSampledLoadAverages() (*load.AvgStat, error) {
return load.Avg()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2020, OpenTelemetry 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 loadscraper

import (
"context"
"testing"

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

"go.opentelemetry.io/collector/consumer/pdata"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
)

type validationFn func(*testing.T, pdata.MetricSlice)

func TestScrapeMetrics(t *testing.T) {
createScraperAndValidateScrapedMetrics(t, &Config{}, func(t *testing.T, metrics pdata.MetricSlice) {
// expect 3 metrics
assert.Equal(t, 3, metrics.Len())

// expect a single datapoint for 1m, 5m & 15m load metrics
assertMetricHasSingleDatapoint(t, metrics.At(0), metric1MLoadDescriptor)
assertMetricHasSingleDatapoint(t, metrics.At(1), metric5MLoadDescriptor)
assertMetricHasSingleDatapoint(t, metrics.At(2), metric15MLoadDescriptor)
})
}

func assertMetricHasSingleDatapoint(t *testing.T, metric pdata.Metric, descriptor pdata.MetricDescriptor) {
internal.AssertDescriptorEqual(t, descriptor, metric.MetricDescriptor())
assert.Equal(t, 1, metric.DoubleDataPoints().Len())
}

func createScraperAndValidateScrapedMetrics(t *testing.T, config *Config, assertFn validationFn) {
scraper := newLoadScraper(context.Background(), zap.NewNop(), config)
err := scraper.Initialize(context.Background())
require.NoError(t, err, "Failed to initialize load scraper: %v", err)
defer func() { assert.NoError(t, scraper.Close(context.Background())) }()

metrics, err := scraper.ScrapeMetrics(context.Background())
require.NoError(t, err, "Failed to scrape metrics: %v", err)

assertFn(t, metrics)
}
Loading

0 comments on commit 7682d35

Please sign in to comment.