Add support for retrieving Droplet monitoring metrics. (#491)
* Add metrics package with minimal copy of github.com/prometheus/common/model Package metrics is a minimal copy of github.com/prometheus/common/model providing types to work with the Prometheus-style results in a DigitalOcean Monitoring metrics response. We have copied this here as Prometheus' common packages are considered internal to Prometheus, without any stability guarantees for external usage. * Add support for retrieving Droplet monitoring metrics. * Use pointer receivers.
This commit is contained in:
parent
35a70e8f3a
commit
33658a69d8
|
@ -0,0 +1,81 @@
|
|||
// Copyright 2013 The Prometheus 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 is a minimal copy of github.com/prometheus/common/model
|
||||
// providing types to work with the Prometheus-style results in a DigitalOcean
|
||||
// Monitoring metrics response.
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// MetricNameLabel is the label name indicating the metric name of a
|
||||
// timeseries.
|
||||
MetricNameLabel = "__name__"
|
||||
)
|
||||
|
||||
// A LabelSet is a collection of LabelName and LabelValue pairs. The LabelSet
|
||||
// may be fully-qualified down to the point where it may resolve to a single
|
||||
// Metric in the data store or not. All operations that occur within the realm
|
||||
// of a LabelSet can emit a vector of Metric entities to which the LabelSet may
|
||||
// match.
|
||||
type LabelSet map[LabelName]LabelValue
|
||||
|
||||
func (l LabelSet) String() string {
|
||||
lstrs := make([]string, 0, len(l))
|
||||
for l, v := range l {
|
||||
lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v))
|
||||
}
|
||||
|
||||
sort.Strings(lstrs)
|
||||
return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
|
||||
}
|
||||
|
||||
// A LabelValue is an associated value for a MetricLabelName.
|
||||
type LabelValue string
|
||||
|
||||
// A LabelName is a key for a Metric.
|
||||
type LabelName string
|
||||
|
||||
// A Metric is similar to a LabelSet, but the key difference is that a Metric is
|
||||
// a singleton and refers to one and only one stream of samples.
|
||||
type Metric LabelSet
|
||||
|
||||
func (m Metric) String() string {
|
||||
metricName, hasName := m[MetricNameLabel]
|
||||
numLabels := len(m) - 1
|
||||
if !hasName {
|
||||
numLabels = len(m)
|
||||
}
|
||||
labelStrings := make([]string, 0, numLabels)
|
||||
for label, value := range m {
|
||||
if label != MetricNameLabel {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value))
|
||||
}
|
||||
}
|
||||
|
||||
switch numLabels {
|
||||
case 0:
|
||||
if hasName {
|
||||
return string(metricName)
|
||||
}
|
||||
return "{}"
|
||||
default:
|
||||
sort.Strings(labelStrings)
|
||||
return fmt.Sprintf("%s{%s}", metricName, strings.Join(labelStrings, ", "))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2013 The Prometheus 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 (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMetricToString(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input Metric
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "valid metric without __name__ label",
|
||||
input: Metric{
|
||||
"first_name": "electro",
|
||||
"occupation": "robot",
|
||||
"manufacturer": "westinghouse",
|
||||
},
|
||||
expected: `{first_name="electro", manufacturer="westinghouse", occupation="robot"}`,
|
||||
},
|
||||
{
|
||||
name: "valid metric with __name__ label",
|
||||
input: Metric{
|
||||
"__name__": "electro",
|
||||
"occupation": "robot",
|
||||
"manufacturer": "westinghouse",
|
||||
},
|
||||
expected: `electro{manufacturer="westinghouse", occupation="robot"}`,
|
||||
},
|
||||
{
|
||||
name: "empty metric with __name__ label",
|
||||
input: Metric{
|
||||
"__name__": "fooname",
|
||||
},
|
||||
expected: "fooname",
|
||||
},
|
||||
{
|
||||
name: "empty metric",
|
||||
input: Metric{},
|
||||
expected: "{}",
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.name, func(t *testing.T) {
|
||||
actual := scenario.input.String()
|
||||
if actual != scenario.expected {
|
||||
t.Errorf("expected string output %s but got %s", actual, scenario.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// Copyright 2013 The Prometheus 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// MinimumTick is the minimum supported time resolution. This has to be
|
||||
// at least time.Second in order for the code below to work.
|
||||
minimumTick = time.Millisecond
|
||||
// second is the Time duration equivalent to one second.
|
||||
second = int64(time.Second / minimumTick)
|
||||
// The number of nanoseconds per minimum tick.
|
||||
nanosPerTick = int64(minimumTick / time.Nanosecond)
|
||||
|
||||
// Earliest is the earliest Time representable. Handy for
|
||||
// initializing a high watermark.
|
||||
Earliest = Time(math.MinInt64)
|
||||
// Latest is the latest Time representable. Handy for initializing
|
||||
// a low watermark.
|
||||
Latest = Time(math.MaxInt64)
|
||||
)
|
||||
|
||||
// Time is the number of milliseconds since the epoch
|
||||
// (1970-01-01 00:00 UTC) excluding leap seconds.
|
||||
type Time int64
|
||||
|
||||
// Interval describes an interval between two timestamps.
|
||||
type Interval struct {
|
||||
Start, End Time
|
||||
}
|
||||
|
||||
// Now returns the current time as a Time.
|
||||
func Now() Time {
|
||||
return TimeFromUnixNano(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// TimeFromUnix returns the Time equivalent to the Unix Time t
|
||||
// provided in seconds.
|
||||
func TimeFromUnix(t int64) Time {
|
||||
return Time(t * second)
|
||||
}
|
||||
|
||||
// TimeFromUnixNano returns the Time equivalent to the Unix Time
|
||||
// t provided in nanoseconds.
|
||||
func TimeFromUnixNano(t int64) Time {
|
||||
return Time(t / nanosPerTick)
|
||||
}
|
||||
|
||||
// Equal reports whether two Times represent the same instant.
|
||||
func (t Time) Equal(o Time) bool {
|
||||
return t == o
|
||||
}
|
||||
|
||||
// Before reports whether the Time t is before o.
|
||||
func (t Time) Before(o Time) bool {
|
||||
return t < o
|
||||
}
|
||||
|
||||
// After reports whether the Time t is after o.
|
||||
func (t Time) After(o Time) bool {
|
||||
return t > o
|
||||
}
|
||||
|
||||
// Add returns the Time t + d.
|
||||
func (t Time) Add(d time.Duration) Time {
|
||||
return t + Time(d/minimumTick)
|
||||
}
|
||||
|
||||
// Sub returns the Duration t - o.
|
||||
func (t Time) Sub(o Time) time.Duration {
|
||||
return time.Duration(t-o) * minimumTick
|
||||
}
|
||||
|
||||
// Time returns the time.Time representation of t.
|
||||
func (t Time) Time() time.Time {
|
||||
return time.Unix(int64(t)/second, (int64(t)%second)*nanosPerTick)
|
||||
}
|
||||
|
||||
// Unix returns t as a Unix time, the number of seconds elapsed
|
||||
// since January 1, 1970 UTC.
|
||||
func (t Time) Unix() int64 {
|
||||
return int64(t) / second
|
||||
}
|
||||
|
||||
// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
|
||||
// since January 1, 1970 UTC.
|
||||
func (t Time) UnixNano() int64 {
|
||||
return int64(t) * nanosPerTick
|
||||
}
|
||||
|
||||
// The number of digits after the dot.
|
||||
var dotPrecision = int(math.Log10(float64(second)))
|
||||
|
||||
// String returns a string representation of the Time.
|
||||
func (t Time) String() string {
|
||||
return strconv.FormatFloat(float64(t)/float64(second), 'f', -1, 64)
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaler interface.
|
||||
func (t Time) MarshalJSON() ([]byte, error) {
|
||||
return []byte(t.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||
func (t *Time) UnmarshalJSON(b []byte) error {
|
||||
p := strings.Split(string(b), ".")
|
||||
switch len(p) {
|
||||
case 1:
|
||||
v, err := strconv.ParseInt(string(p[0]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = Time(v * second)
|
||||
|
||||
case 2:
|
||||
v, err := strconv.ParseInt(string(p[0]), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v *= second
|
||||
|
||||
prec := dotPrecision - len(p[1])
|
||||
if prec < 0 {
|
||||
p[1] = p[1][:dotPrecision]
|
||||
} else if prec > 0 {
|
||||
p[1] = p[1] + strings.Repeat("0", prec)
|
||||
}
|
||||
|
||||
va, err := strconv.ParseInt(p[1], 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the value was something like -0.1 the negative is lost in the
|
||||
// parsing because of the leading zero, this ensures that we capture it.
|
||||
if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 {
|
||||
*t = Time(v+va) * -1
|
||||
} else {
|
||||
*t = Time(v + va)
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("invalid time %q", string(b))
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2013 The Prometheus 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 (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestComparators(t *testing.T) {
|
||||
t1a := TimeFromUnix(0)
|
||||
t1b := TimeFromUnix(0)
|
||||
t2 := TimeFromUnix(2*second - 1)
|
||||
|
||||
if !t1a.Equal(t1b) {
|
||||
t.Fatalf("Expected %s to be equal to %s", t1a, t1b)
|
||||
}
|
||||
if t1a.Equal(t2) {
|
||||
t.Fatalf("Expected %s to not be equal to %s", t1a, t2)
|
||||
}
|
||||
|
||||
if !t1a.Before(t2) {
|
||||
t.Fatalf("Expected %s to be before %s", t1a, t2)
|
||||
}
|
||||
if t1a.Before(t1b) {
|
||||
t.Fatalf("Expected %s to not be before %s", t1a, t1b)
|
||||
}
|
||||
|
||||
if !t2.After(t1a) {
|
||||
t.Fatalf("Expected %s to be after %s", t2, t1a)
|
||||
}
|
||||
if t1b.After(t1a) {
|
||||
t.Fatalf("Expected %s to not be after %s", t1b, t1a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeConversions(t *testing.T) {
|
||||
unixSecs := int64(1136239445)
|
||||
unixNsecs := int64(123456789)
|
||||
unixNano := unixSecs*1e9 + unixNsecs
|
||||
|
||||
t1 := time.Unix(unixSecs, unixNsecs-unixNsecs%nanosPerTick)
|
||||
t2 := time.Unix(unixSecs, unixNsecs)
|
||||
|
||||
ts := TimeFromUnixNano(unixNano)
|
||||
if !ts.Time().Equal(t1) {
|
||||
t.Fatalf("Expected %s, got %s", t1, ts.Time())
|
||||
}
|
||||
|
||||
// Test available precision.
|
||||
ts = TimeFromUnixNano(t2.UnixNano())
|
||||
if !ts.Time().Equal(t1) {
|
||||
t.Fatalf("Expected %s, got %s", t1, ts.Time())
|
||||
}
|
||||
|
||||
if ts.UnixNano() != unixNano-unixNano%nanosPerTick {
|
||||
t.Fatalf("Expected %d, got %d", unixNano, ts.UnixNano())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDuration(t *testing.T) {
|
||||
duration := time.Second + time.Minute + time.Hour
|
||||
goTime := time.Unix(1136239445, 0)
|
||||
|
||||
ts := TimeFromUnix(goTime.Unix())
|
||||
if !goTime.Add(duration).Equal(ts.Add(duration).Time()) {
|
||||
t.Fatalf("Expected %s to be equal to %s", goTime.Add(duration), ts.Add(duration))
|
||||
}
|
||||
|
||||
earlier := ts.Add(-duration)
|
||||
delta := ts.Sub(earlier)
|
||||
if delta != duration {
|
||||
t.Fatalf("Expected %s to be equal to %s", delta, duration)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
in Time
|
||||
out string
|
||||
}{
|
||||
{Time(1), `0.001`},
|
||||
{Time(-1), `-0.001`},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
b, err := test.in.MarshalJSON()
|
||||
if err != nil {
|
||||
t.Fatalf("Error marshaling time: %v", err)
|
||||
}
|
||||
|
||||
if string(b) != test.out {
|
||||
t.Errorf("Mismatch in marshal expected=%s actual=%s", test.out, b)
|
||||
}
|
||||
|
||||
var tm Time
|
||||
if err := tm.UnmarshalJSON(b); err != nil {
|
||||
t.Fatalf("Error Unmarshaling time: %v", err)
|
||||
}
|
||||
|
||||
if !test.in.Equal(tm) {
|
||||
t.Fatalf("Mismatch after Unmarshal expected=%v actual=%v", test.in, tm)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2013 The Prometheus 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A SampleValue is a representation of a value for a given sample at a given time.
|
||||
type SampleValue float64
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (v *SampleValue) UnmarshalJSON(b []byte) error {
|
||||
if len(b) < 2 || b[0] != '"' || b[len(b)-1] != '"' {
|
||||
return fmt.Errorf("sample value must be a quoted string")
|
||||
}
|
||||
f, err := strconv.ParseFloat(string(b[1:len(b)-1]), 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = SampleValue(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (v SampleValue) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.String())
|
||||
}
|
||||
|
||||
func (v SampleValue) String() string {
|
||||
return strconv.FormatFloat(float64(v), 'f', -1, 64)
|
||||
}
|
||||
|
||||
// Equal returns true if the value of v and o is equal or if both are NaN. Note
|
||||
// that v==o is false if both are NaN. If you want the conventional float
|
||||
// behavior, use == to compare two SampleValues.
|
||||
func (v SampleValue) Equal(o SampleValue) bool {
|
||||
if v == o {
|
||||
return true
|
||||
}
|
||||
return math.IsNaN(float64(v)) && math.IsNaN(float64(o))
|
||||
}
|
||||
|
||||
// SamplePair pairs a SampleValue with a Timestamp.
|
||||
type SamplePair struct {
|
||||
Timestamp Time
|
||||
Value SampleValue
|
||||
}
|
||||
|
||||
func (s SamplePair) String() string {
|
||||
return fmt.Sprintf("%s @[%s]", s.Value, s.Timestamp)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (s *SamplePair) UnmarshalJSON(b []byte) error {
|
||||
v := [...]json.Unmarshaler{&s.Timestamp, &s.Value}
|
||||
return json.Unmarshal(b, &v)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (s SamplePair) MarshalJSON() ([]byte, error) {
|
||||
t, err := json.Marshal(s.Timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v, err := json.Marshal(s.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(fmt.Sprintf("[%s,%s]", t, v)), nil
|
||||
}
|
||||
|
||||
// SampleStream is a stream of Values belonging to an attached COWMetric.
|
||||
type SampleStream struct {
|
||||
Metric Metric `json:"metric"`
|
||||
Values []SamplePair `json:"values"`
|
||||
}
|
||||
|
||||
func (ss SampleStream) String() string {
|
||||
vals := make([]string, len(ss.Values))
|
||||
for i, v := range ss.Values {
|
||||
vals[i] = v.String()
|
||||
}
|
||||
return fmt.Sprintf("%s =>\n%s", ss.Metric, strings.Join(vals, "\n"))
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
// Copyright 2013 The Prometheus 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 (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEqualValues(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
in1, in2 SampleValue
|
||||
want bool
|
||||
}{
|
||||
"equal floats": {
|
||||
in1: 3.14,
|
||||
in2: 3.14,
|
||||
want: true,
|
||||
},
|
||||
"unequal floats": {
|
||||
in1: 3.14,
|
||||
in2: 3.1415,
|
||||
want: false,
|
||||
},
|
||||
"positive inifinities": {
|
||||
in1: SampleValue(math.Inf(+1)),
|
||||
in2: SampleValue(math.Inf(+1)),
|
||||
want: true,
|
||||
},
|
||||
"negative inifinities": {
|
||||
in1: SampleValue(math.Inf(-1)),
|
||||
in2: SampleValue(math.Inf(-1)),
|
||||
want: true,
|
||||
},
|
||||
"different inifinities": {
|
||||
in1: SampleValue(math.Inf(+1)),
|
||||
in2: SampleValue(math.Inf(-1)),
|
||||
want: false,
|
||||
},
|
||||
"number and infinity": {
|
||||
in1: 42,
|
||||
in2: SampleValue(math.Inf(+1)),
|
||||
want: false,
|
||||
},
|
||||
"number and NaN": {
|
||||
in1: 42,
|
||||
in2: SampleValue(math.NaN()),
|
||||
want: false,
|
||||
},
|
||||
"NaNs": {
|
||||
in1: SampleValue(math.NaN()),
|
||||
in2: SampleValue(math.NaN()),
|
||||
want: true, // !!!
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
got := test.in1.Equal(test.in2)
|
||||
if got != test.want {
|
||||
t.Errorf("Comparing %s, %f and %f: got %t, want %t", name, test.in1, test.in2, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSamplePairJSON(t *testing.T) {
|
||||
input := []struct {
|
||||
plain string
|
||||
value SamplePair
|
||||
}{
|
||||
{
|
||||
plain: `[1234.567,"123.1"]`,
|
||||
value: SamplePair{
|
||||
Value: 123.1,
|
||||
Timestamp: 1234567,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range input {
|
||||
b, err := json.Marshal(test.value)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if string(b) != test.plain {
|
||||
t.Errorf("encoding error: expected %q, got %q", test.plain, b)
|
||||
continue
|
||||
}
|
||||
|
||||
var sp SamplePair
|
||||
err = json.Unmarshal(b, &sp)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if sp != test.value {
|
||||
t.Errorf("decoding error: expected %v, got %v", test.value, sp)
|
||||
}
|
||||
}
|
||||
}
|
137
monitoring.go
137
monitoring.go
|
@ -4,11 +4,15 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
monitoringBasePath = "v2/monitoring"
|
||||
alertPolicyBasePath = monitoringBasePath + "/alerts"
|
||||
monitoringBasePath = "v2/monitoring"
|
||||
alertPolicyBasePath = monitoringBasePath + "/alerts"
|
||||
dropletMetricsBasePath = monitoringBasePath + "/metrics/droplet"
|
||||
|
||||
DropletCPUUtilizationPercent = "v1/insights/droplet/cpu"
|
||||
DropletMemoryUtilizationPercent = "v1/insights/droplet/memory_utilization_percent"
|
||||
|
@ -33,6 +37,18 @@ type MonitoringService interface {
|
|||
CreateAlertPolicy(context.Context, *AlertPolicyCreateRequest) (*AlertPolicy, *Response, error)
|
||||
UpdateAlertPolicy(context.Context, string, *AlertPolicyUpdateRequest) (*AlertPolicy, *Response, error)
|
||||
DeleteAlertPolicy(context.Context, string) (*Response, error)
|
||||
|
||||
GetDropletBandwidth(context.Context, *DropletBandwidthMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletAvailableMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletCPU(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletFilesystemFree(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletFilesystemSize(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletLoad1(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletLoad5(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletLoad15(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletCachedMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletFreeMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
GetDropletTotalMemory(context.Context, *DropletMetricsRequest) (*MetricsResponse, *Response, error)
|
||||
}
|
||||
|
||||
// MonitoringServiceOp handles communication with monitoring related methods of the
|
||||
|
@ -115,6 +131,32 @@ type alertPolicyRoot struct {
|
|||
AlertPolicy *AlertPolicy `json:"policy,omitempty"`
|
||||
}
|
||||
|
||||
// DropletMetricsRequest holds the information needed to retrieve Droplet various metrics.
|
||||
type DropletMetricsRequest struct {
|
||||
HostID string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
|
||||
// DropletBandwidthMetricsRequest holds the information needed to retrieve Droplet bandwidth metrics.
|
||||
type DropletBandwidthMetricsRequest struct {
|
||||
DropletMetricsRequest
|
||||
Interface string
|
||||
Direction string
|
||||
}
|
||||
|
||||
// MetricsResponse holds a Metrics query response.
|
||||
type MetricsResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data MetricsData `json:"data"`
|
||||
}
|
||||
|
||||
// MetricsData holds the data portion of a Metrics response.
|
||||
type MetricsData struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Result []metrics.SampleStream `json:"result"`
|
||||
}
|
||||
|
||||
// ListAlertPolicies all alert policies
|
||||
func (s *MonitoringServiceOp) ListAlertPolicies(ctx context.Context, opt *ListOptions) ([]AlertPolicy, *Response, error) {
|
||||
path := alertPolicyBasePath
|
||||
|
@ -221,3 +263,94 @@ func (s *MonitoringServiceOp) DeleteAlertPolicy(ctx context.Context, uuid string
|
|||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetDropletBandwidth retrieves Droplet bandwidth metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletBandwidth(ctx context.Context, args *DropletBandwidthMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
path := dropletMetricsBasePath + "/bandwidth"
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("host_id", args.HostID)
|
||||
q.Add("interface", args.Interface)
|
||||
q.Add("direction", args.Direction)
|
||||
q.Add("start", fmt.Sprintf("%d", args.Start.Unix()))
|
||||
q.Add("end", fmt.Sprintf("%d", args.End.Unix()))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
root := new(MetricsResponse)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
|
||||
return root, resp, err
|
||||
}
|
||||
|
||||
// GetDropletCPU retrieves Droplet CPU metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletCPU(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/cpu", args)
|
||||
}
|
||||
|
||||
// GetDropletFilesystemFree retrieves Droplet filesystem free metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletFilesystemFree(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/filesystem_free", args)
|
||||
}
|
||||
|
||||
// GetDropletFilesystemSize retrieves Droplet filesystem size metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletFilesystemSize(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/filesystem_size", args)
|
||||
}
|
||||
|
||||
// GetDropletLoad1 retrieves Droplet load 1 metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletLoad1(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/load_1", args)
|
||||
}
|
||||
|
||||
// GetDropletLoad5 retrieves Droplet load 5 metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletLoad5(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/load_5", args)
|
||||
}
|
||||
|
||||
// GetDropletLoad15 retrieves Droplet load 15 metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletLoad15(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/load_15", args)
|
||||
}
|
||||
|
||||
// GetDropletCachedMemory retrieves Droplet cached memory metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletCachedMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/memory_cached", args)
|
||||
}
|
||||
|
||||
// GetDropletFreeMemory retrieves Droplet free memory metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletFreeMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/memory_free", args)
|
||||
}
|
||||
|
||||
// GetDropletTotalMemory retrieves Droplet total memory metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletTotalMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/memory_total", args)
|
||||
}
|
||||
|
||||
// GetDropletAvailableMemory retrieves Droplet available memory metrics.
|
||||
func (s *MonitoringServiceOp) GetDropletAvailableMemory(ctx context.Context, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
return s.getDropletMetrics(ctx, "/memory_available", args)
|
||||
}
|
||||
|
||||
func (s *MonitoringServiceOp) getDropletMetrics(ctx context.Context, path string, args *DropletMetricsRequest) (*MetricsResponse, *Response, error) {
|
||||
fullPath := dropletMetricsBasePath + path
|
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, fullPath, nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
q.Add("host_id", args.HostID)
|
||||
q.Add("start", fmt.Sprintf("%d", args.Start.Unix()))
|
||||
q.Add("end", fmt.Sprintf("%d", args.End.Unix()))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
root := new(MetricsResponse)
|
||||
resp, err := s.client.Do(ctx, req, root)
|
||||
|
||||
return root, resp, err
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@ import (
|
|||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/digitalocean/godo/metrics"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -170,6 +174,557 @@ var (
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
bandwidthRespJSON = `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "matrix",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"direction": "inbound",
|
||||
"host_id": "222651441",
|
||||
"interface": "private"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1634052360,
|
||||
"0.016600450090265357"
|
||||
],
|
||||
[
|
||||
1634052480,
|
||||
"0.015085955677299055"
|
||||
],
|
||||
[
|
||||
1634052600,
|
||||
"0.014941163855322308"
|
||||
],
|
||||
[
|
||||
1634052720,
|
||||
"0.016214285714285712"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
memoryRespJSON = `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "matrix",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"1028956160"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"1028956160"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"1028956160"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
filesystemRespJSON = `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "matrix",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"device": "/dev/vda1",
|
||||
"fstype": "ext4",
|
||||
"host_id": "123",
|
||||
"mountpoint": "/"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"25832407040"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"25832407040"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"25832407040"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
loadRespJSON = `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "matrix",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"0.04"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"0.03"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"0.01"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
cpuRespJSON = `
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "matrix",
|
||||
"result": [
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "idle"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"122901.18"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"123020.92"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"123140.8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "iowait"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"14.99"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"15.01"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"15.01"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "irq"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"0"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"0"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"0"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "nice"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"66.35"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"66.35"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"66.35"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "softirq"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"2.13"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"2.13"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"2.13"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "steal"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"7.89"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"7.9"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"7.91"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "system"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"140.09"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"140.2"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"140.23"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"metric": {
|
||||
"host_id": "123",
|
||||
"mode": "user"
|
||||
},
|
||||
"values": [
|
||||
[
|
||||
1635386880,
|
||||
"278.57"
|
||||
],
|
||||
[
|
||||
1635387000,
|
||||
"278.65"
|
||||
],
|
||||
[
|
||||
1635387120,
|
||||
"278.69"
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}`
|
||||
|
||||
testCPUResponse = &MetricsResponse{
|
||||
Status: "success",
|
||||
Data: MetricsData{
|
||||
ResultType: "matrix",
|
||||
Result: []metrics.SampleStream{
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "idle",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 122901.18,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 123020.92,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 123140.8,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "iowait",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 14.99,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 15.01,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 15.01,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "irq",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 0,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 0,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "nice",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 66.35,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 66.35,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 66.35,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "softirq",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 2.13,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 2.13,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 2.13,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "steal",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 7.89,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 7.9,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 7.91,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "system",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 140.09,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 140.2,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 140.23,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
"mode": "user",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 278.57,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 278.65,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 278.69,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testLoadResponse = &MetricsResponse{
|
||||
Status: "success",
|
||||
Data: MetricsData{
|
||||
ResultType: "matrix",
|
||||
Result: []metrics.SampleStream{
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 0.04,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 0.03,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 0.01,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testFilesystemResponse = &MetricsResponse{
|
||||
Status: "success",
|
||||
Data: MetricsData{
|
||||
ResultType: "matrix",
|
||||
Result: []metrics.SampleStream{
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"device": "/dev/vda1",
|
||||
"fstype": "ext4",
|
||||
"host_id": "123",
|
||||
"mountpoint": "/",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 25832407040,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 25832407040,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 25832407040,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testMemoryResponse = &MetricsResponse{
|
||||
Status: "success",
|
||||
Data: MetricsData{
|
||||
ResultType: "matrix",
|
||||
Result: []metrics.SampleStream{
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "123",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1635386880000,
|
||||
Value: 1.02895616e+09,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387000000,
|
||||
Value: 1.02895616e+09,
|
||||
},
|
||||
{
|
||||
Timestamp: 1635387120000,
|
||||
Value: 1.02895616e+09,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func TestAlertPolicies_List(t *testing.T) {
|
||||
|
@ -368,3 +923,385 @@ func TestAlertPolicy_Update(t *testing.T) {
|
|||
t.Errorf("Monitoring.UpdateAlertPolicy returned %+v, expected %+v", policy, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDropletBandwidth(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletBandwidthMetricsRequest{
|
||||
DropletMetricsRequest: DropletMetricsRequest{HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
},
|
||||
Interface: "private",
|
||||
Direction: "inbound",
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/bandwidth", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
inter := r.URL.Query().Get("interface")
|
||||
direction := r.URL.Query().Get("direction")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, metricReq.Interface, inter)
|
||||
assert.Equal(t, metricReq.Direction, direction)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, bandwidthRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletBandwidth(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletBandwidthMetrics returned error: %v", err)
|
||||
}
|
||||
|
||||
expected := &MetricsResponse{
|
||||
Status: "success",
|
||||
Data: MetricsData{
|
||||
ResultType: "matrix",
|
||||
Result: []metrics.SampleStream{
|
||||
{
|
||||
Metric: metrics.Metric{
|
||||
"host_id": "222651441",
|
||||
"direction": "inbound",
|
||||
"interface": "private",
|
||||
},
|
||||
Values: []metrics.SamplePair{
|
||||
{
|
||||
Timestamp: 1634052360000,
|
||||
Value: 0.016600450090265357,
|
||||
},
|
||||
{
|
||||
Timestamp: 1634052480000,
|
||||
Value: 0.015085955677299055,
|
||||
},
|
||||
{
|
||||
Timestamp: 1634052600000,
|
||||
Value: 0.014941163855322308,
|
||||
},
|
||||
{
|
||||
Timestamp: 1634052720000,
|
||||
Value: 0.016214285714285712,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletTotalMemory(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/memory_total", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, memoryRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletTotalMemory(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletTotalMemory returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testMemoryResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletFreeMemory(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/memory_free", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, memoryRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletFreeMemory(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletFreeMemory returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testMemoryResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletAvailableMemory(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/memory_available", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, memoryRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletAvailableMemory(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletAvailableMemory returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testMemoryResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletCachedMemory(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/memory_cached", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, memoryRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletCachedMemory(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletCachedMemory returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testMemoryResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletFilesystemFree(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/filesystem_free", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, filesystemRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletFilesystemFree(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletFilesystemFree returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testFilesystemResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletFilesystemSize(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/filesystem_size", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, filesystemRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletFilesystemSize(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletFilesystemSize returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testFilesystemResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletLoad1(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/load_1", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, loadRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletLoad1(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletLoad1 returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testLoadResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletLoad5(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/load_5", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, loadRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletLoad5(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletLoad5 returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testLoadResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletLoad15(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/load_15", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, loadRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletLoad15(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletLoad15 returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testLoadResponse, metricsResp)
|
||||
}
|
||||
|
||||
func TestGetDropletCPU(t *testing.T) {
|
||||
setup()
|
||||
defer teardown()
|
||||
now := time.Now()
|
||||
metricReq := &DropletMetricsRequest{
|
||||
HostID: "123",
|
||||
Start: now.Add(-300 * time.Second),
|
||||
End: now,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/v2/monitoring/metrics/droplet/cpu", func(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.URL.Query().Get("host_id")
|
||||
start := r.URL.Query().Get("start")
|
||||
end := r.URL.Query().Get("end")
|
||||
|
||||
assert.Equal(t, metricReq.HostID, hostID)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.Start.Unix()), start)
|
||||
assert.Equal(t, fmt.Sprintf("%d", metricReq.End.Unix()), end)
|
||||
testMethod(t, r, http.MethodGet)
|
||||
|
||||
fmt.Fprintf(w, cpuRespJSON)
|
||||
})
|
||||
|
||||
metricsResp, _, err := client.Monitoring.GetDropletCPU(ctx, metricReq)
|
||||
if err != nil {
|
||||
t.Errorf("Monitoring.GetDropletCPU returned error: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, testCPUResponse, metricsResp)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue