Skip to content

Commit

Permalink
Merge pull request #180 from newrelic/ihost-2154
Browse files Browse the repository at this point in the history
Add positive versions of rate and delta
  • Loading branch information
alejandrodnm authored May 30, 2019
2 parents 1d56d08 + cb1ec5b commit 7a26f2f
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 10 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## 3.2.0

### Added

- Adds the positive only version of `RATE` and `DELTA`, named `PRATE` and
`PDELTA` respectively.

## 3.1.5

### Fixed
Expand Down
9 changes: 8 additions & 1 deletion data/metric/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var (
ErrNonNumeric = errors.New("non-numeric value for rate/delta")
ErrNoStoreToCalcDiff = errors.New("cannot use deltas nor rates without persistent store")
ErrTooCloseSamples = errors.New("samples too close in time, skipping")
ErrNegativeDiff = errors.New("source was reset, skipping")
ErrOverrideSetAttrs = errors.New("cannot overwrite metric-set attributes")
ErrDeltaWithNoAttrs = errors.New("delta/rate metrics should be attached to an attribute identified metric-set")
)
Expand Down Expand Up @@ -80,7 +81,7 @@ func (ms *Set) SetMetric(name string, value interface{}, sourceType SourceType)

// Only sample metrics of numeric type
switch sourceType {
case RATE, DELTA:
case RATE, DELTA, PRATE, PDELTA:
if len(ms.nsAttributes) == 0 {
err = ErrDeltaWithNoAttrs
return
Expand Down Expand Up @@ -163,6 +164,12 @@ func (ms *Set) elapsedDifference(name string, absolute interface{}, sourceType S
}

elapsed = newValue - oldValue

if elapsed < 0 && sourceType.IsPositive() {
err = ErrNegativeDiff
return
}

if sourceType == RATE {
elapsed = elapsed / float64(duration)
}
Expand Down
53 changes: 46 additions & 7 deletions data/metric/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestSet_SetMetricAttribute(t *testing.T) {
func TestSet_SetMetricCachesRateAndDeltas(t *testing.T) {
storer := persist.NewInMemoryStore()

for _, sourceType := range []SourceType{DELTA, RATE} {
for _, sourceType := range []SourceType{DELTA, RATE, PRATE, PDELTA} {
persist.SetNow(growingTime)

ms := NewSet("some-event-type", storer, Attr("k", "v"))
Expand All @@ -84,14 +84,53 @@ func TestSet_SetMetricCachesRateAndDeltas(t *testing.T) {
}
}

func TestSet_SetMetricAllowsNegativeDeltas(t *testing.T) {
persist.SetNow(growingTime)
func TestSet_SetMetricsRatesAndDeltas(t *testing.T) {
var testCases = []struct {
sourceType SourceType
firstValue float64
secondValue float64
finalValue float64
}{
{DELTA, 5, 2, -3.0},
{DELTA, 2, 5, 3.0},
{PDELTA, 1, 5, 4.0},
{RATE, 2, 4, 2.0},
{RATE, 4, 2, -2.0},
{PRATE, 2, 4, 2.0},
}

ms := NewSet("some-event-type", persist.NewInMemoryStore(), Attr("k", "v"))
for _, tc := range testCases {
t.Run(string(tc.sourceType), func(t *testing.T) {

persist.SetNow(growingTime)

assert.NoError(t, ms.SetMetric("d", 5, DELTA))
assert.NoError(t, ms.SetMetric("d", 2, DELTA))
assert.Equal(t, ms.Metrics["d"], -3.0)
ms := NewSet("some-event-type", persist.NewInMemoryStore(), Attr("k", "v"))

assert.NoError(t, ms.SetMetric("d", tc.firstValue, tc.sourceType))
assert.NoError(t, ms.SetMetric("d", tc.secondValue, tc.sourceType))
assert.Equal(t, ms.Metrics["d"], tc.finalValue)
})
}
}

func TestSet_SetMetricPositivesThrowsOnNegativeValues(t *testing.T) {
for _, sourceType := range []SourceType{PDELTA, PRATE} {
t.Run(string(sourceType), func(t *testing.T) {
persist.SetNow(growingTime)
ms := NewSet(
"some-event-type",
persist.NewInMemoryStore(),
Attr("k", "v"),
)
assert.NoError(t, ms.SetMetric("d", 5, sourceType))
assert.Error(
t,
ms.SetMetric("d", 2, sourceType),
"source was reset, skipping",
)
assert.Equal(t, ms.Metrics["d"], 0.0)
})
}
}

func TestSet_SetMetric_NilStorer(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions data/metric/set_marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ const (
// If the value does not match one of the values below an error will be returned.
// - gauge
// - rate
// - prate
// - delta
// - pdelta
// - attribute
//
// If one of the required tags is missing an error will be returned.
Expand All @@ -44,6 +46,8 @@ const (
// Attribute string `metric_name:"metric.attribute" source_type:"attribute"`
// Rate float64 `metric_name:"metric.rate" source_type:"RATE"`
// Delta float64 `metric_name:"metric.delta" source_type:"delta"`
// PRate float64 `metric_name:"metric.prate" source_type:"prate"`
// PDelta float64 `metric_name:"metric.pdelta" source_type:"pdelta"`
// }
//
// Any non-struct/non-pointer value that has the correct struct field tags
Expand Down
6 changes: 6 additions & 0 deletions data/metric/set_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ func TestSet_MarshalMetricsSimpleStruct(t *testing.T) {
Attribute string `metric_name:"metric.attribute" source_type:"attribute"`
Rate float64 `metric_name:"metric.rate" source_type:"rate"`
Delta float64 `metric_name:"metric.delta" source_type:"delta"`
PRate float64 `metric_name:"metric.prate" source_type:"prate"`
PDelta float64 `metric_name:"metric.pdelta" source_type:"pdelta"`
}{
10,
"some-attribute",
float64(20),
float64(30),
float64(40),
float64(50),
}

expectedMarshall := map[string]interface{}{
Expand All @@ -27,6 +31,8 @@ func TestSet_MarshalMetricsSimpleStruct(t *testing.T) {
"metric.rate": 0.,
"metric.attribute": "some-attribute",
"metric.delta": 0.,
"metric.pdelta": 0.,
"metric.prate": 0.,
}

ms := newTestSet()
Expand Down
16 changes: 14 additions & 2 deletions data/metric/source_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const (
DELTA SourceType = iota
// ATTRIBUTE is any string value
ATTRIBUTE SourceType = iota
// PRATE is a version of RATE that only allows positive values.
PRATE SourceType = iota
// PDELTA is a version of DELTA that only allows positive values.
PDELTA SourceType = iota
)

// SourcesTypeToName metric sources list mapping its type to readable name.
Expand All @@ -36,18 +40,26 @@ var SourcesNameToType = map[string]SourceType{
"gauge": GAUGE,
"rate": RATE,
"delta": DELTA,
"prate": PRATE,
"pdelta": PDELTA,
"attribute": ATTRIBUTE,
}

// String fulfills stringer interface, returning empty string on invalid source types.
func (t *SourceType) String() string {
if s, ok := SourcesTypeToName[*t]; ok {
func (t SourceType) String() string {
if s, ok := SourcesTypeToName[t]; ok {
return s
}

return ""
}

// IsPositive checks that the `SourceType` belongs to the positive only
// list of `SourceType`s
func (t SourceType) IsPositive() bool {
return t == PRATE || t == PDELTA
}

// SourceTypeForName does a case insensitive conversion from a string to a SourceType.
// An error will be returned if no valid SourceType matched.
func SourceTypeForName(sourceTypeTag string) (SourceType, error) {
Expand Down
20 changes: 20 additions & 0 deletions data/metric/source_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ func TestSourceType_String(t *testing.T) {
assert.Equal(t, "rate", st.String())
}

func TestSourceType_Positive(t *testing.T) {

var testCases = []struct {
sourceType SourceType
isPositive bool
}{
{GAUGE, false},
{RATE, false},
{DELTA, false},
{PRATE, true},
{PDELTA, true},
}

for _, tc := range testCases {
t.Run(string(tc.sourceType), func(t *testing.T) {
assert.Equal(t, tc.isPositive, tc.sourceType.IsPositive())
})
}
}

func TestSourceTypeForName(t *testing.T) {
name, err := SourceTypeForName("delta")
assert.NoError(t, err)
Expand Down

0 comments on commit 7a26f2f

Please sign in to comment.