Skip to content

Commit

Permalink
Add ScrapeErrors struct to simplify errors usage
Browse files Browse the repository at this point in the history
Add ScrapeErrors that contains a slice with errors and that has methods
to simplify adding new PartialScrapeErrors and regular generic errors.

Use new methods to refactor errors appends in
receiver/hostmetricsreceiver.

Use ScrapeErrors.Combine() in component/componenterror to not create
cycling dependencies in consumererrors package.
  • Loading branch information
ozerovandrei committed Jan 29, 2021
1 parent bd70c80 commit 37f7da5
Show file tree
Hide file tree
Showing 13 changed files with 312 additions and 223 deletions.
26 changes: 1 addition & 25 deletions component/componenterror/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package componenterror

import (
"errors"
"fmt"
"strings"

"go.opentelemetry.io/collector/consumer/consumererror"
)
Expand All @@ -37,27 +35,5 @@ var (

// CombineErrors converts a list of errors into one error.
func CombineErrors(errs []error) error {
numErrors := len(errs)
if numErrors == 0 {
// No errors
return nil
}

if numErrors == 1 {
return errs[0]
}

errMsgs := make([]string, 0, numErrors)
permanent := false
for _, err := range errs {
if !permanent && consumererror.IsPermanent(err) {
permanent = true
}
errMsgs = append(errMsgs, err.Error())
}
err := fmt.Errorf("[%s]", strings.Join(errMsgs, "; "))
if permanent {
err = consumererror.Permanent(err)
}
return err
return consumererror.CombineErrors(errs)
}
47 changes: 47 additions & 0 deletions consumer/consumererror/combineerrors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright The 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 consumererror

import (
"fmt"
"strings"
)

// CombineErrors converts a list of errors into one error.
func CombineErrors(errs []error) error {
numErrors := len(errs)
if numErrors == 0 {
// No errors
return nil
}

if numErrors == 1 {
return errs[0]
}

errMsgs := make([]string, 0, numErrors)
permanent := false
for _, err := range errs {
if !permanent && IsPermanent(err) {
permanent = true
}
errMsgs = append(errMsgs, err.Error())
}
err := fmt.Errorf("[%s]", strings.Join(errMsgs, "; "))
if permanent {
err = Permanent(err)
}
return err
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
// Copyright The OpenTelemetry Authors
// Copyright The 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
// 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 componenterror_test
package consumererror

import (
"fmt"
"testing"

"go.opentelemetry.io/collector/component/componenterror"
"go.opentelemetry.io/collector/consumer/consumererror"
)

func TestCombineErrors(t *testing.T) {
Expand Down Expand Up @@ -50,20 +47,20 @@ func TestCombineErrors(t *testing.T) {
errors: []error{
fmt.Errorf("foo"),
fmt.Errorf("bar"),
consumererror.Permanent(fmt.Errorf("permanent"))},
Permanent(fmt.Errorf("permanent"))},
expected: "Permanent error: [foo; bar; Permanent error: permanent]",
},
}

for _, tc := range testCases {
got := componenterror.CombineErrors(tc.errors)
got := CombineErrors(tc.errors)
if (got == nil) != tc.expectNil {
t.Errorf("CombineErrors(%v) == nil? Got: %t. Want: %t", tc.errors, got == nil, tc.expectNil)
}
if got != nil && tc.expected != got.Error() {
t.Errorf("CombineErrors(%v) = %q. Want: %q", tc.errors, got, tc.expected)
}
if tc.expectedPermanent && !consumererror.IsPermanent(got) {
if tc.expectedPermanent && !IsPermanent(got) {
t.Errorf("CombineErrors(%v) = %q. Want: consumererror.permanent", tc.errors, got)
}
}
Expand Down
75 changes: 75 additions & 0 deletions consumer/consumererror/scrapeerrors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright The 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 consumererror

import (
"fmt"
"strings"
)

// ScrapeErrors contains multiple PartialScrapeErrors and can also contain generic errors.
type ScrapeErrors struct {
errs []error
scrapeErrsCount int
}

// Add adds a PartialScrapeError with the provided failed count and error.
func (s *ScrapeErrors) Add(failed int, err error) {
s.errs = append(s.errs, NewPartialScrapeError(err, failed))
s.scrapeErrsCount++
}

// Addf adds a PartialScrapeError with the provided failed count and arguments to format an error.
func (s *ScrapeErrors) Addf(failed int, format string, a ...interface{}) {
s.errs = append(s.errs, NewPartialScrapeError(fmt.Errorf(format, a...), failed))
s.scrapeErrsCount++
}

// Add adds a regular generic error.
func (s *ScrapeErrors) AddRegular(err error) {
s.errs = append(s.errs, err)
}

// Add adds a regular generic error from the provided format specifier.
func (s *ScrapeErrors) AddRegularf(format string, a ...interface{}) {
s.errs = append(s.errs, fmt.Errorf(format, a...))
}

// Combine converts a slice of errors into one error.
// It will return a PartialScrapeError if at least one error in the slice is a PartialScrapeError.
func (s *ScrapeErrors) Combine() error {
if s.scrapeErrsCount == 0 {
return CombineErrors(s.errs)
}

errMsgs := make([]string, 0, len(s.errs))
failedScrapeCount := 0
for _, err := range s.errs {
if partialError, isPartial := err.(PartialScrapeError); isPartial {
failedScrapeCount += partialError.Failed
}

errMsgs = append(errMsgs, err.Error())
}

var err error
if len(s.errs) == 1 {
err = s.errs[0]
} else {
err = fmt.Errorf("[%s]", strings.Join(errMsgs, "; "))
}

return NewPartialScrapeError(err, failedScrapeCount)
}
145 changes: 145 additions & 0 deletions consumer/consumererror/scrapeerrors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright The 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 consumererror

import (
"errors"
"fmt"
"testing"

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

func TestScrapeErrorsAdd(t *testing.T) {
err1 := errors.New("err 1")
err2 := errors.New("err 2")
expected := []error{
PartialScrapeError{error: err1, Failed: 1},
PartialScrapeError{error: err2, Failed: 10},
}

var errs ScrapeErrors
errs.Add(1, err1)
errs.Add(10, err2)
assert.Equal(t, expected, errs.errs)
}

func TestScrapeErrorsAddf(t *testing.T) {
err1 := errors.New("err 10")
err2 := errors.New("err 20")
expected := []error{
PartialScrapeError{error: fmt.Errorf("err: %s", err1), Failed: 20},
PartialScrapeError{error: fmt.Errorf("err %s: %w", "2", err2), Failed: 2},
}

var errs ScrapeErrors
errs.Addf(20, "err: %s", err1)
errs.Addf(2, "err %s: %w", "2", err2)
assert.Equal(t, expected, errs.errs)
}

func TestScrapeErrorsAddRegular(t *testing.T) {
err1 := errors.New("err a")
err2 := errors.New("err b")
expected := []error{err1, err2}

var errs ScrapeErrors
errs.AddRegular(err1)
errs.AddRegular(err2)
assert.Equal(t, expected, errs.errs)
}

func TestScrapeErrorsAddRegularf(t *testing.T) {
err1 := errors.New("err aa")
err2 := errors.New("err bb")
expected := []error{
fmt.Errorf("err: %s", err1),
fmt.Errorf("err %s: %w", "bb", err2),
}

var errs ScrapeErrors
errs.AddRegularf("err: %s", err1)
errs.AddRegularf("err %s: %w", "bb", err2)
assert.Equal(t, expected, errs.errs)
}

func TestScrapeErrorsCombine(t *testing.T) {
testCases := []struct {
errs func() ScrapeErrors
expectedErr string
expectedFailedCount int
expectNil bool
expectedScrape bool
}{
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
return errs
},
expectNil: true,
},
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
errs.Add(10, errors.New("bad scrapes"))
errs.Addf(1, "err: %s", errors.New("bad scrape"))
return errs
},
expectedErr: "[bad scrapes; err: bad scrape]",
expectedFailedCount: 11,
expectedScrape: true,
},
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
errs.AddRegular(errors.New("bad regular"))
errs.AddRegularf("err: %s", errors.New("bad reg"))
return errs
},
expectedErr: "[bad regular; err: bad reg]",
},
{
errs: func() ScrapeErrors {
var errs ScrapeErrors
errs.Add(2, errors.New("bad two scrapes"))
errs.Addf(10, "%d scrapes failed: %s", 10, errors.New("bad things happened"))
errs.AddRegular(errors.New("bad event"))
errs.AddRegularf("event: %s", errors.New("something happened"))
return errs
},
expectedErr: "[bad two scrapes; 10 scrapes failed: bad things happened; bad event; event: something happened]",
expectedFailedCount: 12,
expectedScrape: true,
},
}

for _, tc := range testCases {
scrapeErrs := tc.errs()
if (scrapeErrs.Combine() == nil) != tc.expectNil {
t.Errorf("%+v.Combine() == nil? Got: %t. Want: %t", scrapeErrs, scrapeErrs.Combine() == nil, tc.expectNil)
}
if scrapeErrs.Combine() != nil && tc.expectedErr != scrapeErrs.Combine().Error() {
t.Errorf("%+v.Combine() = %q. Want: %q", scrapeErrs, scrapeErrs.Combine(), tc.expectedErr)
}
if tc.expectedScrape {
partialScrapeErr, ok := scrapeErrs.Combine().(PartialScrapeError)
if !ok {
t.Errorf("%+v.Combine() = %q. Want: PartialScrapeError", scrapeErrs, scrapeErrs.Combine())
} else if tc.expectedFailedCount != partialScrapeErr.Failed {
t.Errorf("%+v.Combine().Failed. Got %d Failed count. Want: %d", scrapeErrs, partialScrapeErr.Failed, tc.expectedFailedCount)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"go.opentelemetry.io/collector/consumer/pdata"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal"
"go.opentelemetry.io/collector/receiver/hostmetricsreceiver/internal/metadata"
"go.opentelemetry.io/collector/receiver/scraperhelper"
)

const (
Expand Down Expand Up @@ -71,15 +70,15 @@ func (s *scraper) Scrape(_ context.Context) (pdata.MetricSlice, error) {
return metrics, consumererror.NewPartialScrapeError(err, metricsLen)
}

var errors []error
var errors consumererror.ScrapeErrors
usages := make([]*deviceUsage, 0, len(partitions))
for _, partition := range partitions {
if !s.fsFilter.includePartition(partition) {
continue
}
usage, usageErr := s.usage(partition.Mountpoint)
if usageErr != nil {
errors = append(errors, consumererror.NewPartialScrapeError(usageErr, 0))
errors.Add(0, usageErr)
continue
}

Expand All @@ -92,7 +91,7 @@ func (s *scraper) Scrape(_ context.Context) (pdata.MetricSlice, error) {
appendSystemSpecificMetrics(metrics, 1, now, usages)
}

err = scraperhelper.CombineScrapeErrors(errors)
err = errors.Combine()
if err != nil && len(usages) == 0 {
partialErr := err.(consumererror.PartialScrapeError)
partialErr.Failed = metricsLen
Expand Down
Loading

0 comments on commit 37f7da5

Please sign in to comment.