Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metrics package #40

Merged
merged 7 commits into from
Jul 1, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions pkg/metrics/gauge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package metrics

import (
"fmt"
"strings"
"sync"
"time"
)

// Gauge is a metric type that represents a single numerical value that can
// arbitrarily go up and down.
type Gauge struct {
name string
labels map[string]string

value float64
timestamp int64
mutex sync.RWMutex
}

// Set allows setting the gauge to an arbitrary value.
func (g *Gauge) Set(value float64) {
g.mutex.Lock()
defer g.mutex.Unlock()

g.value = value
g.timestamp = time.Now().UnixNano() / int64(time.Millisecond)
pdyraga marked this conversation as resolved.
Show resolved Hide resolved
}

// Exposes the gauge in the text-based exposition format.
func (g *Gauge) expose() string {
g.mutex.RLock()
defer g.mutex.RUnlock()

typeLine := fmt.Sprintf("# TYPE %v %v", g.name, "gauge")

labelsStrings := make([]string, 0)
for name, value := range g.labels {
labelsStrings = append(
labelsStrings,
fmt.Sprintf("%v=\"%v\"", name, value),
)
}
labels := strings.Join(labelsStrings, ",")

if len(labels) > 0 {
labels = "{" + labels + "}"
}

body := fmt.Sprintf("%v%v %v %v", g.name, labels, g.value, g.timestamp)

return fmt.Sprintf("%v\n%v", typeLine, body)
}
84 changes: 84 additions & 0 deletions pkg/metrics/gauge_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package metrics

import (
"testing"
)

func TestGaugeSet(t *testing.T) {
gauge := &Gauge{
name: "test_gauge",
labels: map[string]string{"label": "value"},
}

if gauge.value != 0 {
t.Fatal("incorrect gauge initial value")
}

if gauge.timestamp != 0 {
t.Fatal("incorrect gauge initial timestamp")
}

newGaugeValue := float64(500)

gauge.Set(newGaugeValue)

if gauge.value != newGaugeValue {
t.Fatalf(
"incorrect gauge value:\n"+
"expected: [%v]\n"+
"actual: [%v]",
newGaugeValue,
gauge.value,
)
}

if gauge.timestamp == 0 {
t.Fatal("timestamp should be set")
}
}

func TestGaugeExpose(t *testing.T) {
gauge := &Gauge{
name: "test_gauge",
labels: map[string]string{"label": "value"},
value: 500,
timestamp: 1000,
}

actualText := gauge.expose()

expectedText := "# TYPE test_gauge gauge\ntest_gauge{label=\"value\"} 500 1000"

if actualText != expectedText {
t.Fatalf(
"incorrect gauge expose text:\n"+
"expected: [%v]\n"+
"actual: [%v]",
expectedText,
actualText,
)
}
}

func TestGaugeWithoutLabelsExpose(t *testing.T) {
gauge := &Gauge{
name: "test_gauge",
labels: map[string]string{},
value: 500,
timestamp: 1000,
}

actualText := gauge.expose()

expectedText := "# TYPE test_gauge gauge\ntest_gauge 500 1000"

if actualText != expectedText {
t.Fatalf(
"incorrect gauge expose text:\n"+
"expected: [%v]\n"+
"actual: [%v]",
expectedText,
actualText,
)
}
}
27 changes: 27 additions & 0 deletions pkg/metrics/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package metrics

import (
"fmt"
"strings"
)

// Info is a metric type that represents a constant information
// that cannot change in the time.
type Info struct {
name string
labels map[string]string
}

// Exposes the info in the text-based exposition format.
func (i *Info) expose() string {
labelsStrings := make([]string, 0)
for name, value := range i.labels {
labelsStrings = append(
labelsStrings,
fmt.Sprintf("%v=\"%v\"", name, value),
)
}
labels := strings.Join(labelsStrings, ",")

return fmt.Sprintf("%v{%v} %v", i.name, labels, "1")
}
24 changes: 24 additions & 0 deletions pkg/metrics/info_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package metrics

import "testing"

func TestInfoExpose(t *testing.T) {
info := &Info{
name: "test_info",
labels: map[string]string{"label": "value"},
}

actualText := info.expose()

expectedText := "test_info{label=\"value\"} 1"

if actualText != expectedText {
t.Fatalf(
"incorrect gauge expose text:\n"+
"expected: [%v]\n"+
"actual: [%v]",
expectedText,
actualText,
)
}
}
40 changes: 40 additions & 0 deletions pkg/metrics/observer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package metrics

import (
"context"
"time"
)

// ObserverInput defines a source of metric data.
type ObserverInput func() float64

// ObserverOutput defines a destination of collected metric data.
type ObserverOutput interface {
Set(value float64)
}

// Observer represent a definition of a cyclic metric observation process.
type Observer struct {
input ObserverInput
output ObserverOutput
}

// Observe triggers a cyclic metric observation process.
func (o *Observer) Observe(
ctx context.Context,
tick time.Duration,
) {
go func() {
ticker := time.NewTicker(tick)
defer ticker.Stop()

for {
select {
case <-ticker.C:
o.output.Set(o.input())
case <-ctx.Done():
return
}
}
}()
}
36 changes: 36 additions & 0 deletions pkg/metrics/observer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package metrics

import (
"context"
"testing"
"time"
)

func TestObserve(t *testing.T) {
input := func() float64 {
return 5000
}
gauge := &Gauge{}
ctx, _ := context.WithTimeout(context.Background(), 5*time.Millisecond)

observer := &Observer{input, gauge}

observer.Observe(ctx, 1*time.Millisecond)

<-ctx.Done()

expectedGaugeValue := float64(5000)
if gauge.value != expectedGaugeValue {
t.Fatalf(
"incorrect gauge value:\n"+
"expected: [%v]\n"+
"actual: [%v]",
expectedGaugeValue,
gauge.value,
)
}

if gauge.timestamp == 0 {
t.Fatal("timestamp should be set")
}
}
Loading