Update go dependencies

This commit is contained in:
Manuel de Brito Fontes 2018-05-26 11:27:53 -04:00 committed by Manuel Alejandro de Brito Fontes
parent 15ffb51394
commit bb4d483837
No known key found for this signature in database
GPG key ID: 786136016A8BA02A
1621 changed files with 86368 additions and 284392 deletions

View file

@ -1,183 +0,0 @@
// Copyright 2014 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 prometheus
import (
"sync"
"testing"
)
func BenchmarkCounterWithLabelValues(b *testing.B) {
m := NewCounterVec(
CounterOpts{
Name: "benchmark_counter",
Help: "A counter to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.WithLabelValues("eins", "zwei", "drei").Inc()
}
}
func BenchmarkCounterWithLabelValuesConcurrent(b *testing.B) {
m := NewCounterVec(
CounterOpts{
Name: "benchmark_counter",
Help: "A counter to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
for j := 0; j < b.N/10; j++ {
m.WithLabelValues("eins", "zwei", "drei").Inc()
}
wg.Done()
}()
}
wg.Wait()
}
func BenchmarkCounterWithMappedLabels(b *testing.B) {
m := NewCounterVec(
CounterOpts{
Name: "benchmark_counter",
Help: "A counter to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.With(Labels{"two": "zwei", "one": "eins", "three": "drei"}).Inc()
}
}
func BenchmarkCounterWithPreparedMappedLabels(b *testing.B) {
m := NewCounterVec(
CounterOpts{
Name: "benchmark_counter",
Help: "A counter to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
labels := Labels{"two": "zwei", "one": "eins", "three": "drei"}
for i := 0; i < b.N; i++ {
m.With(labels).Inc()
}
}
func BenchmarkCounterNoLabels(b *testing.B) {
m := NewCounter(CounterOpts{
Name: "benchmark_counter",
Help: "A counter to benchmark it.",
})
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Inc()
}
}
func BenchmarkGaugeWithLabelValues(b *testing.B) {
m := NewGaugeVec(
GaugeOpts{
Name: "benchmark_gauge",
Help: "A gauge to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.WithLabelValues("eins", "zwei", "drei").Set(3.1415)
}
}
func BenchmarkGaugeNoLabels(b *testing.B) {
m := NewGauge(GaugeOpts{
Name: "benchmark_gauge",
Help: "A gauge to benchmark it.",
})
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Set(3.1415)
}
}
func BenchmarkSummaryWithLabelValues(b *testing.B) {
m := NewSummaryVec(
SummaryOpts{
Name: "benchmark_summary",
Help: "A summary to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415)
}
}
func BenchmarkSummaryNoLabels(b *testing.B) {
m := NewSummary(SummaryOpts{
Name: "benchmark_summary",
Help: "A summary to benchmark it.",
},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Observe(3.1415)
}
}
func BenchmarkHistogramWithLabelValues(b *testing.B) {
m := NewHistogramVec(
HistogramOpts{
Name: "benchmark_histogram",
Help: "A histogram to benchmark it.",
},
[]string{"one", "two", "three"},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.WithLabelValues("eins", "zwei", "drei").Observe(3.1415)
}
}
func BenchmarkHistogramNoLabels(b *testing.B) {
m := NewHistogram(HistogramOpts{
Name: "benchmark_histogram",
Help: "A histogram to benchmark it.",
},
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.Observe(3.1415)
}
}

View file

@ -1,58 +0,0 @@
// Copyright 2014 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 prometheus
import (
"math"
"testing"
dto "github.com/prometheus/client_model/go"
)
func TestCounterAdd(t *testing.T) {
counter := NewCounter(CounterOpts{
Name: "test",
Help: "test help",
ConstLabels: Labels{"a": "1", "b": "2"},
}).(*counter)
counter.Inc()
if expected, got := 1., math.Float64frombits(counter.valBits); expected != got {
t.Errorf("Expected %f, got %f.", expected, got)
}
counter.Add(42)
if expected, got := 43., math.Float64frombits(counter.valBits); expected != got {
t.Errorf("Expected %f, got %f.", expected, got)
}
if expected, got := "counter cannot decrease in value", decreaseCounter(counter).Error(); expected != got {
t.Errorf("Expected error %q, got %q.", expected, got)
}
m := &dto.Metric{}
counter.Write(m)
if expected, got := `label:<name:"a" value:"1" > label:<name:"b" value:"2" > counter:<value:43 > `, m.String(); expected != got {
t.Errorf("expected %q, got %q", expected, got)
}
}
func decreaseCounter(c *counter) (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
}
}()
c.Add(-1)
return nil
}

View file

@ -1,118 +0,0 @@
// Copyright 2014 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 prometheus_test
import "github.com/prometheus/client_golang/prometheus"
// ClusterManager is an example for a system that might have been built without
// Prometheus in mind. It models a central manager of jobs running in a
// cluster. To turn it into something that collects Prometheus metrics, we
// simply add the two methods required for the Collector interface.
//
// An additional challenge is that multiple instances of the ClusterManager are
// run within the same binary, each in charge of a different zone. We need to
// make use of ConstLabels to be able to register each ClusterManager instance
// with Prometheus.
type ClusterManager struct {
Zone string
OOMCountDesc *prometheus.Desc
RAMUsageDesc *prometheus.Desc
// ... many more fields
}
// ReallyExpensiveAssessmentOfTheSystemState is a mock for the data gathering a
// real cluster manager would have to do. Since it may actually be really
// expensive, it must only be called once per collection. This implementation,
// obviously, only returns some made-up data.
func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() (
oomCountByHost map[string]int, ramUsageByHost map[string]float64,
) {
// Just example fake data.
oomCountByHost = map[string]int{
"foo.example.org": 42,
"bar.example.org": 2001,
}
ramUsageByHost = map[string]float64{
"foo.example.org": 6.023e23,
"bar.example.org": 3.14,
}
return
}
// Describe simply sends the two Descs in the struct to the channel.
func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) {
ch <- c.OOMCountDesc
ch <- c.RAMUsageDesc
}
// Collect first triggers the ReallyExpensiveAssessmentOfTheSystemState. Then it
// creates constant metrics for each host on the fly based on the returned data.
//
// Note that Collect could be called concurrently, so we depend on
// ReallyExpensiveAssessmentOfTheSystemState to be concurrency-safe.
func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) {
oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState()
for host, oomCount := range oomCountByHost {
ch <- prometheus.MustNewConstMetric(
c.OOMCountDesc,
prometheus.CounterValue,
float64(oomCount),
host,
)
}
for host, ramUsage := range ramUsageByHost {
ch <- prometheus.MustNewConstMetric(
c.RAMUsageDesc,
prometheus.GaugeValue,
ramUsage,
host,
)
}
}
// NewClusterManager creates the two Descs OOMCountDesc and RAMUsageDesc. Note
// that the zone is set as a ConstLabel. (It's different in each instance of the
// ClusterManager, but constant over the lifetime of an instance.) Then there is
// a variable label "host", since we want to partition the collected metrics by
// host. Since all Descs created in this way are consistent across instances,
// with a guaranteed distinction by the "zone" label, we can register different
// ClusterManager instances with the same registry.
func NewClusterManager(zone string) *ClusterManager {
return &ClusterManager{
Zone: zone,
OOMCountDesc: prometheus.NewDesc(
"clustermanager_oom_crashes_total",
"Number of OOM crashes.",
[]string{"host"},
prometheus.Labels{"zone": zone},
),
RAMUsageDesc: prometheus.NewDesc(
"clustermanager_ram_usage_bytes",
"RAM usage as reported to the cluster manager.",
[]string{"host"},
prometheus.Labels{"zone": zone},
),
}
}
func ExampleCollector() {
workerDB := NewClusterManager("db")
workerCA := NewClusterManager("ca")
// Since we are dealing with custom Collector implementations, it might
// be a good idea to try it out with a pedantic registry.
reg := prometheus.NewPedanticRegistry()
reg.MustRegister(workerDB)
reg.MustRegister(workerCA)
}

View file

@ -1,751 +0,0 @@
// Copyright 2014 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 prometheus_test
import (
"bytes"
"fmt"
"math"
"net/http"
"runtime"
"sort"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/expfmt"
"github.com/golang/protobuf/proto"
"github.com/prometheus/client_golang/prometheus"
)
func ExampleGauge() {
opsQueued := prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "our_company",
Subsystem: "blob_storage",
Name: "ops_queued",
Help: "Number of blob storage operations waiting to be processed.",
})
prometheus.MustRegister(opsQueued)
// 10 operations queued by the goroutine managing incoming requests.
opsQueued.Add(10)
// A worker goroutine has picked up a waiting operation.
opsQueued.Dec()
// And once more...
opsQueued.Dec()
}
func ExampleGaugeVec() {
opsQueued := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "our_company",
Subsystem: "blob_storage",
Name: "ops_queued",
Help: "Number of blob storage operations waiting to be processed, partitioned by user and type.",
},
[]string{
// Which user has requested the operation?
"user",
// Of what type is the operation?
"type",
},
)
prometheus.MustRegister(opsQueued)
// Increase a value using compact (but order-sensitive!) WithLabelValues().
opsQueued.WithLabelValues("bob", "put").Add(4)
// Increase a value with a map using WithLabels. More verbose, but order
// doesn't matter anymore.
opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc()
}
func ExampleGaugeFunc() {
if err := prometheus.Register(prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
Subsystem: "runtime",
Name: "goroutines_count",
Help: "Number of goroutines that currently exist.",
},
func() float64 { return float64(runtime.NumGoroutine()) },
)); err == nil {
fmt.Println("GaugeFunc 'goroutines_count' registered.")
}
// Note that the count of goroutines is a gauge (and not a counter) as
// it can go up and down.
// Output:
// GaugeFunc 'goroutines_count' registered.
}
func ExampleCounter() {
pushCounter := prometheus.NewCounter(prometheus.CounterOpts{
Name: "repository_pushes", // Note: No help string...
})
err := prometheus.Register(pushCounter) // ... so this will return an error.
if err != nil {
fmt.Println("Push counter couldn't be registered, no counting will happen:", err)
return
}
// Try it once more, this time with a help string.
pushCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "repository_pushes",
Help: "Number of pushes to external repository.",
})
err = prometheus.Register(pushCounter)
if err != nil {
fmt.Println("Push counter couldn't be registered AGAIN, no counting will happen:", err)
return
}
pushComplete := make(chan struct{})
// TODO: Start a goroutine that performs repository pushes and reports
// each completion via the channel.
for _ = range pushComplete {
pushCounter.Inc()
}
// Output:
// Push counter couldn't be registered, no counting will happen: descriptor Desc{fqName: "repository_pushes", help: "", constLabels: {}, variableLabels: []} is invalid: empty help string
}
func ExampleCounterVec() {
httpReqs := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "How many HTTP requests processed, partitioned by status code and HTTP method.",
},
[]string{"code", "method"},
)
prometheus.MustRegister(httpReqs)
httpReqs.WithLabelValues("404", "POST").Add(42)
// If you have to access the same set of labels very frequently, it
// might be good to retrieve the metric only once and keep a handle to
// it. But beware of deletion of that metric, see below!
m := httpReqs.WithLabelValues("200", "GET")
for i := 0; i < 1000000; i++ {
m.Inc()
}
// Delete a metric from the vector. If you have previously kept a handle
// to that metric (as above), future updates via that handle will go
// unseen (even if you re-create a metric with the same label set
// later).
httpReqs.DeleteLabelValues("200", "GET")
// Same thing with the more verbose Labels syntax.
httpReqs.Delete(prometheus.Labels{"method": "GET", "code": "200"})
}
func ExampleInstrumentHandler() {
// Handle the "/doc" endpoint with the standard http.FileServer handler.
// By wrapping the handler with InstrumentHandler, request count,
// request and response sizes, and request latency are automatically
// exported to Prometheus, partitioned by HTTP status code and method
// and by the handler name (here "fileserver").
http.Handle("/doc", prometheus.InstrumentHandler(
"fileserver", http.FileServer(http.Dir("/usr/share/doc")),
))
// The Prometheus handler still has to be registered to handle the
// "/metrics" endpoint. The handler returned by prometheus.Handler() is
// already instrumented - with "prometheus" as the handler name. In this
// example, we want the handler name to be "metrics", so we instrument
// the uninstrumented Prometheus handler ourselves.
http.Handle("/metrics", prometheus.InstrumentHandler(
"metrics", prometheus.UninstrumentedHandler(),
))
}
func ExampleLabelPairSorter() {
labelPairs := []*dto.LabelPair{
&dto.LabelPair{Name: proto.String("status"), Value: proto.String("404")},
&dto.LabelPair{Name: proto.String("method"), Value: proto.String("get")},
}
sort.Sort(prometheus.LabelPairSorter(labelPairs))
fmt.Println(labelPairs)
// Output:
// [name:"method" value:"get" name:"status" value:"404" ]
}
func ExampleRegister() {
// Imagine you have a worker pool and want to count the tasks completed.
taskCounter := prometheus.NewCounter(prometheus.CounterOpts{
Subsystem: "worker_pool",
Name: "completed_tasks_total",
Help: "Total number of tasks completed.",
})
// This will register fine.
if err := prometheus.Register(taskCounter); err != nil {
fmt.Println(err)
} else {
fmt.Println("taskCounter registered.")
}
// Don't forget to tell the HTTP server about the Prometheus handler.
// (In a real program, you still need to start the HTTP server...)
http.Handle("/metrics", prometheus.Handler())
// Now you can start workers and give every one of them a pointer to
// taskCounter and let it increment it whenever it completes a task.
taskCounter.Inc() // This has to happen somewhere in the worker code.
// But wait, you want to see how individual workers perform. So you need
// a vector of counters, with one element for each worker.
taskCounterVec := prometheus.NewCounterVec(
prometheus.CounterOpts{
Subsystem: "worker_pool",
Name: "completed_tasks_total",
Help: "Total number of tasks completed.",
},
[]string{"worker_id"},
)
// Registering will fail because we already have a metric of that name.
if err := prometheus.Register(taskCounterVec); err != nil {
fmt.Println("taskCounterVec not registered:", err)
} else {
fmt.Println("taskCounterVec registered.")
}
// To fix, first unregister the old taskCounter.
if prometheus.Unregister(taskCounter) {
fmt.Println("taskCounter unregistered.")
}
// Try registering taskCounterVec again.
if err := prometheus.Register(taskCounterVec); err != nil {
fmt.Println("taskCounterVec not registered:", err)
} else {
fmt.Println("taskCounterVec registered.")
}
// Bummer! Still doesn't work.
// Prometheus will not allow you to ever export metrics with
// inconsistent help strings or label names. After unregistering, the
// unregistered metrics will cease to show up in the /metrics HTTP
// response, but the registry still remembers that those metrics had
// been exported before. For this example, we will now choose a
// different name. (In a real program, you would obviously not export
// the obsolete metric in the first place.)
taskCounterVec = prometheus.NewCounterVec(
prometheus.CounterOpts{
Subsystem: "worker_pool",
Name: "completed_tasks_by_id",
Help: "Total number of tasks completed.",
},
[]string{"worker_id"},
)
if err := prometheus.Register(taskCounterVec); err != nil {
fmt.Println("taskCounterVec not registered:", err)
} else {
fmt.Println("taskCounterVec registered.")
}
// Finally it worked!
// The workers have to tell taskCounterVec their id to increment the
// right element in the metric vector.
taskCounterVec.WithLabelValues("42").Inc() // Code from worker 42.
// Each worker could also keep a reference to their own counter element
// around. Pick the counter at initialization time of the worker.
myCounter := taskCounterVec.WithLabelValues("42") // From worker 42 initialization code.
myCounter.Inc() // Somewhere in the code of that worker.
// Note that something like WithLabelValues("42", "spurious arg") would
// panic (because you have provided too many label values). If you want
// to get an error instead, use GetMetricWithLabelValues(...) instead.
notMyCounter, err := taskCounterVec.GetMetricWithLabelValues("42", "spurious arg")
if err != nil {
fmt.Println("Worker initialization failed:", err)
}
if notMyCounter == nil {
fmt.Println("notMyCounter is nil.")
}
// A different (and somewhat tricky) approach is to use
// ConstLabels. ConstLabels are pairs of label names and label values
// that never change. You might ask what those labels are good for (and
// rightfully so - if they never change, they could as well be part of
// the metric name). There are essentially two use-cases: The first is
// if labels are constant throughout the lifetime of a binary execution,
// but they vary over time or between different instances of a running
// binary. The second is what we have here: Each worker creates and
// registers an own Counter instance where the only difference is in the
// value of the ConstLabels. Those Counters can all be registered
// because the different ConstLabel values guarantee that each worker
// will increment a different Counter metric.
counterOpts := prometheus.CounterOpts{
Subsystem: "worker_pool",
Name: "completed_tasks",
Help: "Total number of tasks completed.",
ConstLabels: prometheus.Labels{"worker_id": "42"},
}
taskCounterForWorker42 := prometheus.NewCounter(counterOpts)
if err := prometheus.Register(taskCounterForWorker42); err != nil {
fmt.Println("taskCounterVForWorker42 not registered:", err)
} else {
fmt.Println("taskCounterForWorker42 registered.")
}
// Obviously, in real code, taskCounterForWorker42 would be a member
// variable of a worker struct, and the "42" would be retrieved with a
// GetId() method or something. The Counter would be created and
// registered in the initialization code of the worker.
// For the creation of the next Counter, we can recycle
// counterOpts. Just change the ConstLabels.
counterOpts.ConstLabels = prometheus.Labels{"worker_id": "2001"}
taskCounterForWorker2001 := prometheus.NewCounter(counterOpts)
if err := prometheus.Register(taskCounterForWorker2001); err != nil {
fmt.Println("taskCounterVForWorker2001 not registered:", err)
} else {
fmt.Println("taskCounterForWorker2001 registered.")
}
taskCounterForWorker2001.Inc()
taskCounterForWorker42.Inc()
taskCounterForWorker2001.Inc()
// Yet another approach would be to turn the workers themselves into
// Collectors and register them. See the Collector example for details.
// Output:
// taskCounter registered.
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string
// taskCounter unregistered.
// taskCounterVec not registered: a previously registered descriptor with the same fully-qualified name as Desc{fqName: "worker_pool_completed_tasks_total", help: "Total number of tasks completed.", constLabels: {}, variableLabels: [worker_id]} has different label names or a different help string
// taskCounterVec registered.
// Worker initialization failed: inconsistent label cardinality
// notMyCounter is nil.
// taskCounterForWorker42 registered.
// taskCounterForWorker2001 registered.
}
func ExampleSummary() {
temps := prometheus.NewSummary(prometheus.SummaryOpts{
Name: "pond_temperature_celsius",
Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
})
// Simulate some observations.
for i := 0; i < 1000; i++ {
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
}
// Just for demonstration, let's check the state of the summary by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
temps.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
// Output:
// summary: <
// sample_count: 1000
// sample_sum: 29969.50000000001
// quantile: <
// quantile: 0.5
// value: 31.1
// >
// quantile: <
// quantile: 0.9
// value: 41.3
// >
// quantile: <
// quantile: 0.99
// value: 41.9
// >
// >
}
func ExampleSummaryVec() {
temps := prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "pond_temperature_celsius",
Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
},
[]string{"species"},
)
// Simulate some observations.
for i := 0; i < 1000; i++ {
temps.WithLabelValues("litoria-caerulea").Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
temps.WithLabelValues("lithobates-catesbeianus").Observe(32 + math.Floor(100*math.Cos(float64(i)*0.11))/10)
}
// Create a Summary without any observations.
temps.WithLabelValues("leiopelma-hochstetteri")
// Just for demonstration, let's check the state of the summary vector
// by registering it with a custom registry and then let it collect the
// metrics.
reg := prometheus.NewRegistry()
reg.MustRegister(temps)
metricFamilies, err := reg.Gather()
if err != nil || len(metricFamilies) != 1 {
panic("unexpected behavior of custom test registry")
}
fmt.Println(proto.MarshalTextString(metricFamilies[0]))
// Output:
// name: "pond_temperature_celsius"
// help: "The temperature of the frog pond."
// type: SUMMARY
// metric: <
// label: <
// name: "species"
// value: "leiopelma-hochstetteri"
// >
// summary: <
// sample_count: 0
// sample_sum: 0
// quantile: <
// quantile: 0.5
// value: nan
// >
// quantile: <
// quantile: 0.9
// value: nan
// >
// quantile: <
// quantile: 0.99
// value: nan
// >
// >
// >
// metric: <
// label: <
// name: "species"
// value: "lithobates-catesbeianus"
// >
// summary: <
// sample_count: 1000
// sample_sum: 31956.100000000017
// quantile: <
// quantile: 0.5
// value: 32.4
// >
// quantile: <
// quantile: 0.9
// value: 41.4
// >
// quantile: <
// quantile: 0.99
// value: 41.9
// >
// >
// >
// metric: <
// label: <
// name: "species"
// value: "litoria-caerulea"
// >
// summary: <
// sample_count: 1000
// sample_sum: 29969.50000000001
// quantile: <
// quantile: 0.5
// value: 31.1
// >
// quantile: <
// quantile: 0.9
// value: 41.3
// >
// quantile: <
// quantile: 0.99
// value: 41.9
// >
// >
// >
}
func ExampleNewConstSummary() {
desc := prometheus.NewDesc(
"http_request_duration_seconds",
"A summary of the HTTP request durations.",
[]string{"code", "method"},
prometheus.Labels{"owner": "example"},
)
// Create a constant summary from values we got from a 3rd party telemetry system.
s := prometheus.MustNewConstSummary(
desc,
4711, 403.34,
map[float64]float64{0.5: 42.3, 0.9: 323.3},
"200", "get",
)
// Just for demonstration, let's check the state of the summary by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
s.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
// Output:
// label: <
// name: "code"
// value: "200"
// >
// label: <
// name: "method"
// value: "get"
// >
// label: <
// name: "owner"
// value: "example"
// >
// summary: <
// sample_count: 4711
// sample_sum: 403.34
// quantile: <
// quantile: 0.5
// value: 42.3
// >
// quantile: <
// quantile: 0.9
// value: 323.3
// >
// >
}
func ExampleHistogram() {
temps := prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "pond_temperature_celsius",
Help: "The temperature of the frog pond.", // Sorry, we can't measure how badly it smells.
Buckets: prometheus.LinearBuckets(20, 5, 5), // 5 buckets, each 5 centigrade wide.
})
// Simulate some observations.
for i := 0; i < 1000; i++ {
temps.Observe(30 + math.Floor(120*math.Sin(float64(i)*0.1))/10)
}
// Just for demonstration, let's check the state of the histogram by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
temps.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
// Output:
// histogram: <
// sample_count: 1000
// sample_sum: 29969.50000000001
// bucket: <
// cumulative_count: 192
// upper_bound: 20
// >
// bucket: <
// cumulative_count: 366
// upper_bound: 25
// >
// bucket: <
// cumulative_count: 501
// upper_bound: 30
// >
// bucket: <
// cumulative_count: 638
// upper_bound: 35
// >
// bucket: <
// cumulative_count: 816
// upper_bound: 40
// >
// >
}
func ExampleNewConstHistogram() {
desc := prometheus.NewDesc(
"http_request_duration_seconds",
"A histogram of the HTTP request durations.",
[]string{"code", "method"},
prometheus.Labels{"owner": "example"},
)
// Create a constant histogram from values we got from a 3rd party telemetry system.
h := prometheus.MustNewConstHistogram(
desc,
4711, 403.34,
map[float64]uint64{25: 121, 50: 2403, 100: 3221, 200: 4233},
"200", "get",
)
// Just for demonstration, let's check the state of the histogram by
// (ab)using its Write method (which is usually only used by Prometheus
// internally).
metric := &dto.Metric{}
h.Write(metric)
fmt.Println(proto.MarshalTextString(metric))
// Output:
// label: <
// name: "code"
// value: "200"
// >
// label: <
// name: "method"
// value: "get"
// >
// label: <
// name: "owner"
// value: "example"
// >
// histogram: <
// sample_count: 4711
// sample_sum: 403.34
// bucket: <
// cumulative_count: 121
// upper_bound: 25
// >
// bucket: <
// cumulative_count: 2403
// upper_bound: 50
// >
// bucket: <
// cumulative_count: 3221
// upper_bound: 100
// >
// bucket: <
// cumulative_count: 4233
// upper_bound: 200
// >
// >
}
func ExampleAlreadyRegisteredError() {
reqCounter := prometheus.NewCounter(prometheus.CounterOpts{
Name: "requests_total",
Help: "The total number of requests served.",
})
if err := prometheus.Register(reqCounter); err != nil {
if are, ok := err.(prometheus.AlreadyRegisteredError); ok {
// A counter for that metric has been registered before.
// Use the old counter from now on.
reqCounter = are.ExistingCollector.(prometheus.Counter)
} else {
// Something else went wrong!
panic(err)
}
}
}
func ExampleGatherers() {
reg := prometheus.NewRegistry()
temp := prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "temperature_kelvin",
Help: "Temperature in Kelvin.",
},
[]string{"location"},
)
reg.MustRegister(temp)
temp.WithLabelValues("outside").Set(273.14)
temp.WithLabelValues("inside").Set(298.44)
var parser expfmt.TextParser
text := `
# TYPE humidity_percent gauge
# HELP humidity_percent Humidity in %.
humidity_percent{location="outside"} 45.4
humidity_percent{location="inside"} 33.2
# TYPE temperature_kelvin gauge
# HELP temperature_kelvin Temperature in Kelvin.
temperature_kelvin{location="somewhere else"} 4.5
`
parseText := func() ([]*dto.MetricFamily, error) {
parsed, err := parser.TextToMetricFamilies(strings.NewReader(text))
if err != nil {
return nil, err
}
var result []*dto.MetricFamily
for _, mf := range parsed {
result = append(result, mf)
}
return result, nil
}
gatherers := prometheus.Gatherers{
reg,
prometheus.GathererFunc(parseText),
}
gathering, err := gatherers.Gather()
if err != nil {
fmt.Println(err)
}
out := &bytes.Buffer{}
for _, mf := range gathering {
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
panic(err)
}
}
fmt.Print(out.String())
fmt.Println("----------")
// Note how the temperature_kelvin metric family has been merged from
// different sources. Now try
text = `
# TYPE humidity_percent gauge
# HELP humidity_percent Humidity in %.
humidity_percent{location="outside"} 45.4
humidity_percent{location="inside"} 33.2
# TYPE temperature_kelvin gauge
# HELP temperature_kelvin Temperature in Kelvin.
# Duplicate metric:
temperature_kelvin{location="outside"} 265.3
# Wrong labels:
temperature_kelvin 4.5
`
gathering, err = gatherers.Gather()
if err != nil {
fmt.Println(err)
}
// Note that still as many metrics as possible are returned:
out.Reset()
for _, mf := range gathering {
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
panic(err)
}
}
fmt.Print(out.String())
// Output:
// # HELP humidity_percent Humidity in %.
// # TYPE humidity_percent gauge
// humidity_percent{location="inside"} 33.2
// humidity_percent{location="outside"} 45.4
// # HELP temperature_kelvin Temperature in Kelvin.
// # TYPE temperature_kelvin gauge
// temperature_kelvin{location="inside"} 298.44
// temperature_kelvin{location="outside"} 273.14
// temperature_kelvin{location="somewhere else"} 4.5
// ----------
// 2 error(s) occurred:
// * collected metric temperature_kelvin label:<name:"location" value:"outside" > gauge:<value:265.3 > was collected before with the same name and label values
// * collected metric temperature_kelvin gauge:<value:4.5 > has label dimensions inconsistent with previously collected metrics in the same metric family
// # HELP humidity_percent Humidity in %.
// # TYPE humidity_percent gauge
// humidity_percent{location="inside"} 33.2
// humidity_percent{location="outside"} 45.4
// # HELP temperature_kelvin Temperature in Kelvin.
// # TYPE temperature_kelvin gauge
// temperature_kelvin{location="inside"} 298.44
// temperature_kelvin{location="outside"} 273.14
}

View file

@ -1,97 +0,0 @@
// Copyright 2014 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 prometheus_test
import (
"expvar"
"fmt"
"sort"
"strings"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/client_golang/prometheus"
)
func ExampleExpvarCollector() {
expvarCollector := prometheus.NewExpvarCollector(map[string]*prometheus.Desc{
"memstats": prometheus.NewDesc(
"expvar_memstats",
"All numeric memstats as one metric family. Not a good role-model, actually... ;-)",
[]string{"type"}, nil,
),
"lone-int": prometheus.NewDesc(
"expvar_lone_int",
"Just an expvar int as an example.",
nil, nil,
),
"http-request-map": prometheus.NewDesc(
"expvar_http_request_total",
"How many http requests processed, partitioned by status code and http method.",
[]string{"code", "method"}, nil,
),
})
prometheus.MustRegister(expvarCollector)
// The Prometheus part is done here. But to show that this example is
// doing anything, we have to manually export something via expvar. In
// real-life use-cases, some library would already have exported via
// expvar what we want to re-export as Prometheus metrics.
expvar.NewInt("lone-int").Set(42)
expvarMap := expvar.NewMap("http-request-map")
var (
expvarMap1, expvarMap2 expvar.Map
expvarInt11, expvarInt12, expvarInt21, expvarInt22 expvar.Int
)
expvarMap1.Init()
expvarMap2.Init()
expvarInt11.Set(3)
expvarInt12.Set(13)
expvarInt21.Set(11)
expvarInt22.Set(212)
expvarMap1.Set("POST", &expvarInt11)
expvarMap1.Set("GET", &expvarInt12)
expvarMap2.Set("POST", &expvarInt21)
expvarMap2.Set("GET", &expvarInt22)
expvarMap.Set("404", &expvarMap1)
expvarMap.Set("200", &expvarMap2)
// Results in the following expvar map:
// "http-request-count": {"200": {"POST": 11, "GET": 212}, "404": {"POST": 3, "GET": 13}}
// Let's see what the scrape would yield, but exclude the memstats metrics.
metricStrings := []string{}
metric := dto.Metric{}
metricChan := make(chan prometheus.Metric)
go func() {
expvarCollector.Collect(metricChan)
close(metricChan)
}()
for m := range metricChan {
if strings.Index(m.Desc().String(), "expvar_memstats") == -1 {
metric.Reset()
m.Write(&metric)
metricStrings = append(metricStrings, metric.String())
}
}
sort.Strings(metricStrings)
for _, s := range metricStrings {
fmt.Println(strings.TrimRight(s, " "))
}
// Output:
// label:<name:"code" value:"200" > label:<name:"method" value:"GET" > untyped:<value:212 >
// label:<name:"code" value:"200" > label:<name:"method" value:"POST" > untyped:<value:11 >
// label:<name:"code" value:"404" > label:<name:"method" value:"GET" > untyped:<value:13 >
// label:<name:"code" value:"404" > label:<name:"method" value:"POST" > untyped:<value:3 >
// untyped:<value:42 >
}

View file

@ -1,182 +0,0 @@
// Copyright 2014 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 prometheus
import (
"math"
"math/rand"
"sync"
"testing"
"testing/quick"
dto "github.com/prometheus/client_model/go"
)
func listenGaugeStream(vals, result chan float64, done chan struct{}) {
var sum float64
outer:
for {
select {
case <-done:
close(vals)
for v := range vals {
sum += v
}
break outer
case v := <-vals:
sum += v
}
}
result <- sum
close(result)
}
func TestGaugeConcurrency(t *testing.T) {
it := func(n uint32) bool {
mutations := int(n % 10000)
concLevel := int(n%15 + 1)
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
sStream := make(chan float64, mutations*concLevel)
result := make(chan float64)
done := make(chan struct{})
go listenGaugeStream(sStream, result, done)
go func() {
end.Wait()
close(done)
}()
gge := NewGauge(GaugeOpts{
Name: "test_gauge",
Help: "no help can be found here",
})
for i := 0; i < concLevel; i++ {
vals := make([]float64, mutations)
for j := 0; j < mutations; j++ {
vals[j] = rand.Float64() - 0.5
}
go func(vals []float64) {
start.Wait()
for _, v := range vals {
sStream <- v
gge.Add(v)
}
end.Done()
}(vals)
}
start.Done()
if expected, got := <-result, math.Float64frombits(gge.(*value).valBits); math.Abs(expected-got) > 0.000001 {
t.Fatalf("expected approx. %f, got %f", expected, got)
return false
}
return true
}
if err := quick.Check(it, nil); err != nil {
t.Fatal(err)
}
}
func TestGaugeVecConcurrency(t *testing.T) {
it := func(n uint32) bool {
mutations := int(n % 10000)
concLevel := int(n%15 + 1)
vecLength := int(n%5 + 1)
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
sStreams := make([]chan float64, vecLength)
results := make([]chan float64, vecLength)
done := make(chan struct{})
for i := 0; i < vecLength; i++ {
sStreams[i] = make(chan float64, mutations*concLevel)
results[i] = make(chan float64)
go listenGaugeStream(sStreams[i], results[i], done)
}
go func() {
end.Wait()
close(done)
}()
gge := NewGaugeVec(
GaugeOpts{
Name: "test_gauge",
Help: "no help can be found here",
},
[]string{"label"},
)
for i := 0; i < concLevel; i++ {
vals := make([]float64, mutations)
pick := make([]int, mutations)
for j := 0; j < mutations; j++ {
vals[j] = rand.Float64() - 0.5
pick[j] = rand.Intn(vecLength)
}
go func(vals []float64) {
start.Wait()
for i, v := range vals {
sStreams[pick[i]] <- v
gge.WithLabelValues(string('A' + pick[i])).Add(v)
}
end.Done()
}(vals)
}
start.Done()
for i := range sStreams {
if expected, got := <-results[i], math.Float64frombits(gge.WithLabelValues(string('A'+i)).(*value).valBits); math.Abs(expected-got) > 0.000001 {
t.Fatalf("expected approx. %f, got %f", expected, got)
return false
}
}
return true
}
if err := quick.Check(it, nil); err != nil {
t.Fatal(err)
}
}
func TestGaugeFunc(t *testing.T) {
gf := NewGaugeFunc(
GaugeOpts{
Name: "test_name",
Help: "test help",
ConstLabels: Labels{"a": "1", "b": "2"},
},
func() float64 { return 3.1415 },
)
if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: []}`, gf.Desc().String(); expected != got {
t.Errorf("expected %q, got %q", expected, got)
}
m := &dto.Metric{}
gf.Write(m)
if expected, got := `label:<name:"a" value:"1" > label:<name:"b" value:"2" > gauge:<value:3.1415 > `, m.String(); expected != got {
t.Errorf("expected %q, got %q", expected, got)
}
}

View file

@ -1,123 +0,0 @@
package prometheus
import (
"runtime"
"testing"
"time"
dto "github.com/prometheus/client_model/go"
)
func TestGoCollector(t *testing.T) {
var (
c = NewGoCollector()
ch = make(chan Metric)
waitc = make(chan struct{})
closec = make(chan struct{})
old = -1
)
defer close(closec)
go func() {
c.Collect(ch)
go func(c <-chan struct{}) {
<-c
}(closec)
<-waitc
c.Collect(ch)
}()
for {
select {
case metric := <-ch:
switch m := metric.(type) {
// Attention, this also catches Counter...
case Gauge:
pb := &dto.Metric{}
m.Write(pb)
if pb.GetGauge() == nil {
continue
}
if old == -1 {
old = int(pb.GetGauge().GetValue())
close(waitc)
continue
}
if diff := int(pb.GetGauge().GetValue()) - old; diff != 1 {
// TODO: This is flaky in highly concurrent situations.
t.Errorf("want 1 new goroutine, got %d", diff)
}
// GoCollector performs two sends per call.
// On line 27 we need to receive the second send
// to shut down cleanly.
<-ch
return
}
case <-time.After(1 * time.Second):
t.Fatalf("expected collect timed out")
}
}
}
func TestGCCollector(t *testing.T) {
var (
c = NewGoCollector()
ch = make(chan Metric)
waitc = make(chan struct{})
closec = make(chan struct{})
oldGC uint64
oldPause float64
)
defer close(closec)
go func() {
c.Collect(ch)
// force GC
runtime.GC()
<-waitc
c.Collect(ch)
}()
first := true
for {
select {
case metric := <-ch:
switch m := metric.(type) {
case *constSummary, *value:
pb := &dto.Metric{}
m.Write(pb)
if pb.GetSummary() == nil {
continue
}
if len(pb.GetSummary().Quantile) != 5 {
t.Errorf("expected 4 buckets, got %d", len(pb.GetSummary().Quantile))
}
for idx, want := range []float64{0.0, 0.25, 0.5, 0.75, 1.0} {
if *pb.GetSummary().Quantile[idx].Quantile != want {
t.Errorf("bucket #%d is off, got %f, want %f", idx, *pb.GetSummary().Quantile[idx].Quantile, want)
}
}
if first {
first = false
oldGC = *pb.GetSummary().SampleCount
oldPause = *pb.GetSummary().SampleSum
close(waitc)
continue
}
if diff := *pb.GetSummary().SampleCount - oldGC; diff != 1 {
t.Errorf("want 1 new garbage collection run, got %d", diff)
}
if diff := *pb.GetSummary().SampleSum - oldPause; diff <= 0 {
t.Errorf("want moar pause, got %f", diff)
}
return
}
case <-time.After(1 * time.Second):
t.Fatalf("expected collect timed out")
}
}
}

View file

@ -1,326 +0,0 @@
// Copyright 2015 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 prometheus
import (
"math"
"math/rand"
"reflect"
"sort"
"sync"
"testing"
"testing/quick"
dto "github.com/prometheus/client_model/go"
)
func benchmarkHistogramObserve(w int, b *testing.B) {
b.StopTimer()
wg := new(sync.WaitGroup)
wg.Add(w)
g := new(sync.WaitGroup)
g.Add(1)
s := NewHistogram(HistogramOpts{})
for i := 0; i < w; i++ {
go func() {
g.Wait()
for i := 0; i < b.N; i++ {
s.Observe(float64(i))
}
wg.Done()
}()
}
b.StartTimer()
g.Done()
wg.Wait()
}
func BenchmarkHistogramObserve1(b *testing.B) {
benchmarkHistogramObserve(1, b)
}
func BenchmarkHistogramObserve2(b *testing.B) {
benchmarkHistogramObserve(2, b)
}
func BenchmarkHistogramObserve4(b *testing.B) {
benchmarkHistogramObserve(4, b)
}
func BenchmarkHistogramObserve8(b *testing.B) {
benchmarkHistogramObserve(8, b)
}
func benchmarkHistogramWrite(w int, b *testing.B) {
b.StopTimer()
wg := new(sync.WaitGroup)
wg.Add(w)
g := new(sync.WaitGroup)
g.Add(1)
s := NewHistogram(HistogramOpts{})
for i := 0; i < 1000000; i++ {
s.Observe(float64(i))
}
for j := 0; j < w; j++ {
outs := make([]dto.Metric, b.N)
go func(o []dto.Metric) {
g.Wait()
for i := 0; i < b.N; i++ {
s.Write(&o[i])
}
wg.Done()
}(outs)
}
b.StartTimer()
g.Done()
wg.Wait()
}
func BenchmarkHistogramWrite1(b *testing.B) {
benchmarkHistogramWrite(1, b)
}
func BenchmarkHistogramWrite2(b *testing.B) {
benchmarkHistogramWrite(2, b)
}
func BenchmarkHistogramWrite4(b *testing.B) {
benchmarkHistogramWrite(4, b)
}
func BenchmarkHistogramWrite8(b *testing.B) {
benchmarkHistogramWrite(8, b)
}
// Intentionally adding +Inf here to test if that case is handled correctly.
// Also, getCumulativeCounts depends on it.
var testBuckets = []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)}
func TestHistogramConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}
rand.Seed(42)
it := func(n uint32) bool {
mutations := int(n%1e4 + 1e4)
concLevel := int(n%5 + 1)
total := mutations * concLevel
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
sum := NewHistogram(HistogramOpts{
Name: "test_histogram",
Help: "helpless",
Buckets: testBuckets,
})
allVars := make([]float64, total)
var sampleSum float64
for i := 0; i < concLevel; i++ {
vals := make([]float64, mutations)
for j := 0; j < mutations; j++ {
v := rand.NormFloat64()
vals[j] = v
allVars[i*mutations+j] = v
sampleSum += v
}
go func(vals []float64) {
start.Wait()
for _, v := range vals {
sum.Observe(v)
}
end.Done()
}(vals)
}
sort.Float64s(allVars)
start.Done()
end.Wait()
m := &dto.Metric{}
sum.Write(m)
if got, want := int(*m.Histogram.SampleCount), total; got != want {
t.Errorf("got sample count %d, want %d", got, want)
}
if got, want := *m.Histogram.SampleSum, sampleSum; math.Abs((got-want)/want) > 0.001 {
t.Errorf("got sample sum %f, want %f", got, want)
}
wantCounts := getCumulativeCounts(allVars)
if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want {
t.Errorf("got %d buckets in protobuf, want %d", got, want)
}
for i, wantBound := range testBuckets {
if i == len(testBuckets)-1 {
break // No +Inf bucket in protobuf.
}
if gotBound := *m.Histogram.Bucket[i].UpperBound; gotBound != wantBound {
t.Errorf("got bound %f, want %f", gotBound, wantBound)
}
if gotCount, wantCount := *m.Histogram.Bucket[i].CumulativeCount, wantCounts[i]; gotCount != wantCount {
t.Errorf("got count %d, want %d", gotCount, wantCount)
}
}
return true
}
if err := quick.Check(it, nil); err != nil {
t.Error(err)
}
}
func TestHistogramVecConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}
rand.Seed(42)
objectives := make([]float64, 0, len(DefObjectives))
for qu := range DefObjectives {
objectives = append(objectives, qu)
}
sort.Float64s(objectives)
it := func(n uint32) bool {
mutations := int(n%1e4 + 1e4)
concLevel := int(n%7 + 1)
vecLength := int(n%3 + 1)
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
his := NewHistogramVec(
HistogramOpts{
Name: "test_histogram",
Help: "helpless",
Buckets: []float64{-2, -1, -0.5, 0, 0.5, 1, 2, math.Inf(+1)},
},
[]string{"label"},
)
allVars := make([][]float64, vecLength)
sampleSums := make([]float64, vecLength)
for i := 0; i < concLevel; i++ {
vals := make([]float64, mutations)
picks := make([]int, mutations)
for j := 0; j < mutations; j++ {
v := rand.NormFloat64()
vals[j] = v
pick := rand.Intn(vecLength)
picks[j] = pick
allVars[pick] = append(allVars[pick], v)
sampleSums[pick] += v
}
go func(vals []float64) {
start.Wait()
for i, v := range vals {
his.WithLabelValues(string('A' + picks[i])).Observe(v)
}
end.Done()
}(vals)
}
for _, vars := range allVars {
sort.Float64s(vars)
}
start.Done()
end.Wait()
for i := 0; i < vecLength; i++ {
m := &dto.Metric{}
s := his.WithLabelValues(string('A' + i))
s.Write(m)
if got, want := len(m.Histogram.Bucket), len(testBuckets)-1; got != want {
t.Errorf("got %d buckets in protobuf, want %d", got, want)
}
if got, want := int(*m.Histogram.SampleCount), len(allVars[i]); got != want {
t.Errorf("got sample count %d, want %d", got, want)
}
if got, want := *m.Histogram.SampleSum, sampleSums[i]; math.Abs((got-want)/want) > 0.001 {
t.Errorf("got sample sum %f, want %f", got, want)
}
wantCounts := getCumulativeCounts(allVars[i])
for j, wantBound := range testBuckets {
if j == len(testBuckets)-1 {
break // No +Inf bucket in protobuf.
}
if gotBound := *m.Histogram.Bucket[j].UpperBound; gotBound != wantBound {
t.Errorf("got bound %f, want %f", gotBound, wantBound)
}
if gotCount, wantCount := *m.Histogram.Bucket[j].CumulativeCount, wantCounts[j]; gotCount != wantCount {
t.Errorf("got count %d, want %d", gotCount, wantCount)
}
}
}
return true
}
if err := quick.Check(it, nil); err != nil {
t.Error(err)
}
}
func getCumulativeCounts(vars []float64) []uint64 {
counts := make([]uint64, len(testBuckets))
for _, v := range vars {
for i := len(testBuckets) - 1; i >= 0; i-- {
if v > testBuckets[i] {
break
}
counts[i]++
}
}
return counts
}
func TestBuckets(t *testing.T) {
got := LinearBuckets(-15, 5, 6)
want := []float64{-15, -10, -5, 0, 5, 10}
if !reflect.DeepEqual(got, want) {
t.Errorf("linear buckets: got %v, want %v", got, want)
}
got = ExponentialBuckets(100, 1.2, 3)
want = []float64{100, 120, 144}
if !reflect.DeepEqual(got, want) {
t.Errorf("linear buckets: got %v, want %v", got, want)
}
}

View file

@ -1,121 +0,0 @@
// Copyright 2014 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 prometheus
import (
"net/http"
"net/http/httptest"
"testing"
"time"
dto "github.com/prometheus/client_model/go"
)
type respBody string
func (b respBody) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTeapot)
w.Write([]byte(b))
}
func TestInstrumentHandler(t *testing.T) {
defer func(n nower) {
now = n.(nower)
}(now)
instant := time.Now()
end := instant.Add(30 * time.Second)
now = nowSeries(instant, end)
respBody := respBody("Howdy there!")
hndlr := InstrumentHandler("test-handler", respBody)
opts := SummaryOpts{
Subsystem: "http",
ConstLabels: Labels{"handler": "test-handler"},
}
reqCnt := MustRegisterOrGet(NewCounterVec(
CounterOpts{
Namespace: opts.Namespace,
Subsystem: opts.Subsystem,
Name: "requests_total",
Help: "Total number of HTTP requests made.",
ConstLabels: opts.ConstLabels,
},
instLabels,
)).(*CounterVec)
opts.Name = "request_duration_microseconds"
opts.Help = "The HTTP request latencies in microseconds."
reqDur := MustRegisterOrGet(NewSummary(opts)).(Summary)
opts.Name = "request_size_bytes"
opts.Help = "The HTTP request sizes in bytes."
MustRegisterOrGet(NewSummary(opts))
opts.Name = "response_size_bytes"
opts.Help = "The HTTP response sizes in bytes."
MustRegisterOrGet(NewSummary(opts))
reqCnt.Reset()
resp := httptest.NewRecorder()
req := &http.Request{
Method: "GET",
}
hndlr.ServeHTTP(resp, req)
if resp.Code != http.StatusTeapot {
t.Fatalf("expected status %d, got %d", http.StatusTeapot, resp.Code)
}
if string(resp.Body.Bytes()) != "Howdy there!" {
t.Fatalf("expected body %s, got %s", "Howdy there!", string(resp.Body.Bytes()))
}
out := &dto.Metric{}
reqDur.Write(out)
if want, got := "test-handler", out.Label[0].GetValue(); want != got {
t.Errorf("want label value %q in reqDur, got %q", want, got)
}
if want, got := uint64(1), out.Summary.GetSampleCount(); want != got {
t.Errorf("want sample count %d in reqDur, got %d", want, got)
}
out.Reset()
if want, got := 1, len(reqCnt.children); want != got {
t.Errorf("want %d children in reqCnt, got %d", want, got)
}
cnt, err := reqCnt.GetMetricWithLabelValues("get", "418")
if err != nil {
t.Fatal(err)
}
cnt.Write(out)
if want, got := "418", out.Label[0].GetValue(); want != got {
t.Errorf("want label value %q in reqCnt, got %q", want, got)
}
if want, got := "test-handler", out.Label[1].GetValue(); want != got {
t.Errorf("want label value %q in reqCnt, got %q", want, got)
}
if want, got := "get", out.Label[2].GetValue(); want != got {
t.Errorf("want label value %q in reqCnt, got %q", want, got)
}
if out.Counter == nil {
t.Fatal("expected non-nil counter in reqCnt")
}
if want, got := 1., out.Counter.GetValue(); want != got {
t.Errorf("want reqCnt of %f, got %f", want, got)
}
}

View file

@ -1,35 +0,0 @@
// Copyright 2014 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 prometheus
import "testing"
func TestBuildFQName(t *testing.T) {
scenarios := []struct{ namespace, subsystem, name, result string }{
{"a", "b", "c", "a_b_c"},
{"", "b", "c", "b_c"},
{"a", "", "c", "a_c"},
{"", "", "c", "c"},
{"a", "b", "", ""},
{"a", "", "", ""},
{"", "b", "", ""},
{" ", "", "", ""},
}
for i, s := range scenarios {
if want, got := s.result, BuildFQName(s.namespace, s.subsystem, s.name); want != got {
t.Errorf("%d. want %s, got %s", i, want, got)
}
}
}

View file

@ -1,58 +0,0 @@
package prometheus
import (
"bytes"
"os"
"regexp"
"testing"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/procfs"
)
func TestProcessCollector(t *testing.T) {
if _, err := procfs.Self(); err != nil {
t.Skipf("skipping TestProcessCollector, procfs not available: %s", err)
}
registry := NewRegistry()
if err := registry.Register(NewProcessCollector(os.Getpid(), "")); err != nil {
t.Fatal(err)
}
if err := registry.Register(NewProcessCollectorPIDFn(
func() (int, error) { return os.Getpid(), nil }, "foobar"),
); err != nil {
t.Fatal(err)
}
mfs, err := registry.Gather()
if err != nil {
t.Fatal(err)
}
var buf bytes.Buffer
for _, mf := range mfs {
if _, err := expfmt.MetricFamilyToText(&buf, mf); err != nil {
t.Fatal(err)
}
}
for _, re := range []*regexp.Regexp{
regexp.MustCompile("process_cpu_seconds_total [0-9]"),
regexp.MustCompile("process_max_fds [1-9]"),
regexp.MustCompile("process_open_fds [1-9]"),
regexp.MustCompile("process_virtual_memory_bytes [1-9]"),
regexp.MustCompile("process_resident_memory_bytes [1-9]"),
regexp.MustCompile("process_start_time_seconds [0-9.]{10,}"),
regexp.MustCompile("foobar_process_cpu_seconds_total [0-9]"),
regexp.MustCompile("foobar_process_max_fds [1-9]"),
regexp.MustCompile("foobar_process_open_fds [1-9]"),
regexp.MustCompile("foobar_process_virtual_memory_bytes [1-9]"),
regexp.MustCompile("foobar_process_resident_memory_bytes [1-9]"),
regexp.MustCompile("foobar_process_start_time_seconds [0-9.]{10,}"),
} {
if !re.Match(buf.Bytes()) {
t.Errorf("want body to match %s\n%s", re, buf.String())
}
}
}

View file

@ -1,137 +0,0 @@
// Copyright 2016 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.
// Copyright (c) 2013, The Prometheus Authors
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package promhttp
import (
"bytes"
"errors"
"log"
"net/http"
"net/http/httptest"
"testing"
"github.com/prometheus/client_golang/prometheus"
)
type errorCollector struct{}
func (e errorCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- prometheus.NewDesc("invalid_metric", "not helpful", nil, nil)
}
func (e errorCollector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.NewInvalidMetric(
prometheus.NewDesc("invalid_metric", "not helpful", nil, nil),
errors.New("collect error"),
)
}
func TestHandlerErrorHandling(t *testing.T) {
// Create a registry that collects a MetricFamily with two elements,
// another with one, and reports an error.
reg := prometheus.NewRegistry()
cnt := prometheus.NewCounter(prometheus.CounterOpts{
Name: "the_count",
Help: "Ah-ah-ah! Thunder and lightning!",
})
reg.MustRegister(cnt)
cntVec := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "name",
Help: "docstring",
ConstLabels: prometheus.Labels{"constname": "constvalue"},
},
[]string{"labelname"},
)
cntVec.WithLabelValues("val1").Inc()
cntVec.WithLabelValues("val2").Inc()
reg.MustRegister(cntVec)
reg.MustRegister(errorCollector{})
logBuf := &bytes.Buffer{}
logger := log.New(logBuf, "", 0)
writer := httptest.NewRecorder()
request, _ := http.NewRequest("GET", "/", nil)
request.Header.Add("Accept", "test/plain")
errorHandler := HandlerFor(reg, HandlerOpts{
ErrorLog: logger,
ErrorHandling: HTTPErrorOnError,
})
continueHandler := HandlerFor(reg, HandlerOpts{
ErrorLog: logger,
ErrorHandling: ContinueOnError,
})
panicHandler := HandlerFor(reg, HandlerOpts{
ErrorLog: logger,
ErrorHandling: PanicOnError,
})
wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
`
wantErrorBody := `An error has occurred during metrics gathering:
error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
`
wantOKBody := `# HELP name docstring
# TYPE name counter
name{constname="constvalue",labelname="val1"} 1
name{constname="constvalue",labelname="val2"} 1
# HELP the_count Ah-ah-ah! Thunder and lightning!
# TYPE the_count counter
the_count 0
`
errorHandler.ServeHTTP(writer, request)
if got, want := writer.Code, http.StatusInternalServerError; got != want {
t.Errorf("got HTTP status code %d, want %d", got, want)
}
if got := logBuf.String(); got != wantMsg {
t.Errorf("got log message:\n%s\nwant log mesage:\n%s\n", got, wantMsg)
}
if got := writer.Body.String(); got != wantErrorBody {
t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody)
}
logBuf.Reset()
writer.Body.Reset()
writer.Code = http.StatusOK
continueHandler.ServeHTTP(writer, request)
if got, want := writer.Code, http.StatusOK; got != want {
t.Errorf("got HTTP status code %d, want %d", got, want)
}
if got := logBuf.String(); got != wantMsg {
t.Errorf("got log message %q, want %q", got, wantMsg)
}
if got := writer.Body.String(); got != wantOKBody {
t.Errorf("got body %q, want %q", got, wantOKBody)
}
defer func() {
if err := recover(); err == nil {
t.Error("expected panic from panicHandler")
}
}()
panicHandler.ServeHTTP(writer, request)
}

View file

@ -1,545 +0,0 @@
// Copyright 2014 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.
// Copyright (c) 2013, The Prometheus Authors
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.
package prometheus_test
import (
"bytes"
"net/http"
"net/http/httptest"
"testing"
dto "github.com/prometheus/client_model/go"
"github.com/golang/protobuf/proto"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func testHandler(t testing.TB) {
metricVec := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "name",
Help: "docstring",
ConstLabels: prometheus.Labels{"constname": "constvalue"},
},
[]string{"labelname"},
)
metricVec.WithLabelValues("val1").Inc()
metricVec.WithLabelValues("val2").Inc()
externalMetricFamily := &dto.MetricFamily{
Name: proto.String("externalname"),
Help: proto.String("externaldocstring"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
{
Label: []*dto.LabelPair{
{
Name: proto.String("externalconstname"),
Value: proto.String("externalconstvalue"),
},
{
Name: proto.String("externallabelname"),
Value: proto.String("externalval1"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(1),
},
},
},
}
externalBuf := &bytes.Buffer{}
enc := expfmt.NewEncoder(externalBuf, expfmt.FmtProtoDelim)
if err := enc.Encode(externalMetricFamily); err != nil {
t.Fatal(err)
}
externalMetricFamilyAsBytes := externalBuf.Bytes()
externalMetricFamilyAsText := []byte(`# HELP externalname externaldocstring
# TYPE externalname counter
externalname{externalconstname="externalconstvalue",externallabelname="externalval1"} 1
`)
externalMetricFamilyAsProtoText := []byte(`name: "externalname"
help: "externaldocstring"
type: COUNTER
metric: <
label: <
name: "externalconstname"
value: "externalconstvalue"
>
label: <
name: "externallabelname"
value: "externalval1"
>
counter: <
value: 1
>
>
`)
externalMetricFamilyAsProtoCompactText := []byte(`name:"externalname" help:"externaldocstring" type:COUNTER metric:<label:<name:"externalconstname" value:"externalconstvalue" > label:<name:"externallabelname" value:"externalval1" > counter:<value:1 > >
`)
expectedMetricFamily := &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("docstring"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
{
Label: []*dto.LabelPair{
{
Name: proto.String("constname"),
Value: proto.String("constvalue"),
},
{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(1),
},
},
{
Label: []*dto.LabelPair{
{
Name: proto.String("constname"),
Value: proto.String("constvalue"),
},
{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(1),
},
},
},
}
buf := &bytes.Buffer{}
enc = expfmt.NewEncoder(buf, expfmt.FmtProtoDelim)
if err := enc.Encode(expectedMetricFamily); err != nil {
t.Fatal(err)
}
expectedMetricFamilyAsBytes := buf.Bytes()
expectedMetricFamilyAsText := []byte(`# HELP name docstring
# TYPE name counter
name{constname="constvalue",labelname="val1"} 1
name{constname="constvalue",labelname="val2"} 1
`)
expectedMetricFamilyAsProtoText := []byte(`name: "name"
help: "docstring"
type: COUNTER
metric: <
label: <
name: "constname"
value: "constvalue"
>
label: <
name: "labelname"
value: "val1"
>
counter: <
value: 1
>
>
metric: <
label: <
name: "constname"
value: "constvalue"
>
label: <
name: "labelname"
value: "val2"
>
counter: <
value: 1
>
>
`)
expectedMetricFamilyAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > >
`)
externalMetricFamilyWithSameName := &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("docstring"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
{
Label: []*dto.LabelPair{
{
Name: proto.String("constname"),
Value: proto.String("constvalue"),
},
{
Name: proto.String("labelname"),
Value: proto.String("different_val"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(42),
},
},
},
}
expectedMetricFamilyMergedWithExternalAsProtoCompactText := []byte(`name:"name" help:"docstring" type:COUNTER metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"different_val" > counter:<value:42 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val1" > counter:<value:1 > > metric:<label:<name:"constname" value:"constvalue" > label:<name:"labelname" value:"val2" > counter:<value:1 > >
`)
type output struct {
headers map[string]string
body []byte
}
var scenarios = []struct {
headers map[string]string
out output
collector prometheus.Collector
externalMF []*dto.MetricFamily
}{
{ // 0
headers: map[string]string{
"Accept": "foo/bar;q=0.2, dings/bums;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: []byte{},
},
},
{ // 1
headers: map[string]string{
"Accept": "foo/bar;q=0.2, application/quark;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: []byte{},
},
},
{ // 2
headers: map[string]string{
"Accept": "foo/bar;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: []byte{},
},
},
{ // 3
headers: map[string]string{
"Accept": "text/plain;q=0.2, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.8",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
},
body: []byte{},
},
},
{ // 4
headers: map[string]string{
"Accept": "application/json",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: expectedMetricFamilyAsText,
},
collector: metricVec,
},
{ // 5
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
},
body: expectedMetricFamilyAsBytes,
},
collector: metricVec,
},
{ // 6
headers: map[string]string{
"Accept": "application/json",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: externalMetricFamilyAsText,
},
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 7
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
},
body: externalMetricFamilyAsBytes,
},
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 8
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
},
body: bytes.Join(
[][]byte{
externalMetricFamilyAsBytes,
expectedMetricFamilyAsBytes,
},
[]byte{},
),
},
collector: metricVec,
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 9
headers: map[string]string{
"Accept": "text/plain",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: []byte{},
},
},
{ // 10
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: expectedMetricFamilyAsText,
},
collector: metricVec,
},
{ // 11
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=bla;q=0.2, text/plain;q=0.5;version=0.0.4",
},
out: output{
headers: map[string]string{
"Content-Type": `text/plain; version=0.0.4`,
},
body: bytes.Join(
[][]byte{
externalMetricFamilyAsText,
expectedMetricFamilyAsText,
},
[]byte{},
),
},
collector: metricVec,
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 12
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.2, text/plain;q=0.5;version=0.0.2",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited`,
},
body: bytes.Join(
[][]byte{
externalMetricFamilyAsBytes,
expectedMetricFamilyAsBytes,
},
[]byte{},
),
},
collector: metricVec,
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 13
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=text;q=0.5, application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.4",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text`,
},
body: bytes.Join(
[][]byte{
externalMetricFamilyAsProtoText,
expectedMetricFamilyAsProtoText,
},
[]byte{},
),
},
collector: metricVec,
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 14
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`,
},
body: bytes.Join(
[][]byte{
externalMetricFamilyAsProtoCompactText,
expectedMetricFamilyAsProtoCompactText,
},
[]byte{},
),
},
collector: metricVec,
externalMF: []*dto.MetricFamily{externalMetricFamily},
},
{ // 15
headers: map[string]string{
"Accept": "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=compact-text",
},
out: output{
headers: map[string]string{
"Content-Type": `application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text`,
},
body: bytes.Join(
[][]byte{
externalMetricFamilyAsProtoCompactText,
expectedMetricFamilyMergedWithExternalAsProtoCompactText,
},
[]byte{},
),
},
collector: metricVec,
externalMF: []*dto.MetricFamily{
externalMetricFamily,
externalMetricFamilyWithSameName,
},
},
}
for i, scenario := range scenarios {
registry := prometheus.NewPedanticRegistry()
gatherer := prometheus.Gatherer(registry)
if scenario.externalMF != nil {
gatherer = prometheus.Gatherers{
registry,
prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) {
return scenario.externalMF, nil
}),
}
}
if scenario.collector != nil {
registry.Register(scenario.collector)
}
writer := httptest.NewRecorder()
handler := prometheus.InstrumentHandler("prometheus", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{}))
request, _ := http.NewRequest("GET", "/", nil)
for key, value := range scenario.headers {
request.Header.Add(key, value)
}
handler(writer, request)
for key, value := range scenario.out.headers {
if writer.HeaderMap.Get(key) != value {
t.Errorf(
"%d. expected %q for header %q, got %q",
i, value, key, writer.Header().Get(key),
)
}
}
if !bytes.Equal(scenario.out.body, writer.Body.Bytes()) {
t.Errorf(
"%d. expected body:\n%s\ngot body:\n%s\n",
i, scenario.out.body, writer.Body.Bytes(),
)
}
}
}
func TestHandler(t *testing.T) {
testHandler(t)
}
func BenchmarkHandler(b *testing.B) {
for i := 0; i < b.N; i++ {
testHandler(b)
}
}
func TestRegisterWithOrGet(t *testing.T) {
// Replace the default registerer just to be sure. This is bad, but this
// whole test will go away once RegisterOrGet is removed.
oldRegisterer := prometheus.DefaultRegisterer
defer func() {
prometheus.DefaultRegisterer = oldRegisterer
}()
prometheus.DefaultRegisterer = prometheus.NewRegistry()
original := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "test",
Help: "help",
},
[]string{"foo", "bar"},
)
equalButNotSame := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "test",
Help: "help",
},
[]string{"foo", "bar"},
)
if err := prometheus.Register(original); err != nil {
t.Fatal(err)
}
if err := prometheus.Register(equalButNotSame); err == nil {
t.Fatal("expected error when registringe equal collector")
}
existing, err := prometheus.RegisterOrGet(equalButNotSame)
if err != nil {
t.Fatal(err)
}
if existing != original {
t.Error("expected original collector but got something else")
}
if existing == equalButNotSame {
t.Error("expected original callector but got new one")
}
}

View file

@ -1,347 +0,0 @@
// Copyright 2014 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 prometheus
import (
"math"
"math/rand"
"sort"
"sync"
"testing"
"testing/quick"
"time"
dto "github.com/prometheus/client_model/go"
)
func benchmarkSummaryObserve(w int, b *testing.B) {
b.StopTimer()
wg := new(sync.WaitGroup)
wg.Add(w)
g := new(sync.WaitGroup)
g.Add(1)
s := NewSummary(SummaryOpts{})
for i := 0; i < w; i++ {
go func() {
g.Wait()
for i := 0; i < b.N; i++ {
s.Observe(float64(i))
}
wg.Done()
}()
}
b.StartTimer()
g.Done()
wg.Wait()
}
func BenchmarkSummaryObserve1(b *testing.B) {
benchmarkSummaryObserve(1, b)
}
func BenchmarkSummaryObserve2(b *testing.B) {
benchmarkSummaryObserve(2, b)
}
func BenchmarkSummaryObserve4(b *testing.B) {
benchmarkSummaryObserve(4, b)
}
func BenchmarkSummaryObserve8(b *testing.B) {
benchmarkSummaryObserve(8, b)
}
func benchmarkSummaryWrite(w int, b *testing.B) {
b.StopTimer()
wg := new(sync.WaitGroup)
wg.Add(w)
g := new(sync.WaitGroup)
g.Add(1)
s := NewSummary(SummaryOpts{})
for i := 0; i < 1000000; i++ {
s.Observe(float64(i))
}
for j := 0; j < w; j++ {
outs := make([]dto.Metric, b.N)
go func(o []dto.Metric) {
g.Wait()
for i := 0; i < b.N; i++ {
s.Write(&o[i])
}
wg.Done()
}(outs)
}
b.StartTimer()
g.Done()
wg.Wait()
}
func BenchmarkSummaryWrite1(b *testing.B) {
benchmarkSummaryWrite(1, b)
}
func BenchmarkSummaryWrite2(b *testing.B) {
benchmarkSummaryWrite(2, b)
}
func BenchmarkSummaryWrite4(b *testing.B) {
benchmarkSummaryWrite(4, b)
}
func BenchmarkSummaryWrite8(b *testing.B) {
benchmarkSummaryWrite(8, b)
}
func TestSummaryConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}
rand.Seed(42)
it := func(n uint32) bool {
mutations := int(n%1e4 + 1e4)
concLevel := int(n%5 + 1)
total := mutations * concLevel
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
sum := NewSummary(SummaryOpts{
Name: "test_summary",
Help: "helpless",
})
allVars := make([]float64, total)
var sampleSum float64
for i := 0; i < concLevel; i++ {
vals := make([]float64, mutations)
for j := 0; j < mutations; j++ {
v := rand.NormFloat64()
vals[j] = v
allVars[i*mutations+j] = v
sampleSum += v
}
go func(vals []float64) {
start.Wait()
for _, v := range vals {
sum.Observe(v)
}
end.Done()
}(vals)
}
sort.Float64s(allVars)
start.Done()
end.Wait()
m := &dto.Metric{}
sum.Write(m)
if got, want := int(*m.Summary.SampleCount), total; got != want {
t.Errorf("got sample count %d, want %d", got, want)
}
if got, want := *m.Summary.SampleSum, sampleSum; math.Abs((got-want)/want) > 0.001 {
t.Errorf("got sample sum %f, want %f", got, want)
}
objectives := make([]float64, 0, len(DefObjectives))
for qu := range DefObjectives {
objectives = append(objectives, qu)
}
sort.Float64s(objectives)
for i, wantQ := range objectives {
ε := DefObjectives[wantQ]
gotQ := *m.Summary.Quantile[i].Quantile
gotV := *m.Summary.Quantile[i].Value
min, max := getBounds(allVars, wantQ, ε)
if gotQ != wantQ {
t.Errorf("got quantile %f, want %f", gotQ, wantQ)
}
if gotV < min || gotV > max {
t.Errorf("got %f for quantile %f, want [%f,%f]", gotV, gotQ, min, max)
}
}
return true
}
if err := quick.Check(it, nil); err != nil {
t.Error(err)
}
}
func TestSummaryVecConcurrency(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
}
rand.Seed(42)
objectives := make([]float64, 0, len(DefObjectives))
for qu := range DefObjectives {
objectives = append(objectives, qu)
}
sort.Float64s(objectives)
it := func(n uint32) bool {
mutations := int(n%1e4 + 1e4)
concLevel := int(n%7 + 1)
vecLength := int(n%3 + 1)
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
sum := NewSummaryVec(
SummaryOpts{
Name: "test_summary",
Help: "helpless",
},
[]string{"label"},
)
allVars := make([][]float64, vecLength)
sampleSums := make([]float64, vecLength)
for i := 0; i < concLevel; i++ {
vals := make([]float64, mutations)
picks := make([]int, mutations)
for j := 0; j < mutations; j++ {
v := rand.NormFloat64()
vals[j] = v
pick := rand.Intn(vecLength)
picks[j] = pick
allVars[pick] = append(allVars[pick], v)
sampleSums[pick] += v
}
go func(vals []float64) {
start.Wait()
for i, v := range vals {
sum.WithLabelValues(string('A' + picks[i])).Observe(v)
}
end.Done()
}(vals)
}
for _, vars := range allVars {
sort.Float64s(vars)
}
start.Done()
end.Wait()
for i := 0; i < vecLength; i++ {
m := &dto.Metric{}
s := sum.WithLabelValues(string('A' + i))
s.Write(m)
if got, want := int(*m.Summary.SampleCount), len(allVars[i]); got != want {
t.Errorf("got sample count %d for label %c, want %d", got, 'A'+i, want)
}
if got, want := *m.Summary.SampleSum, sampleSums[i]; math.Abs((got-want)/want) > 0.001 {
t.Errorf("got sample sum %f for label %c, want %f", got, 'A'+i, want)
}
for j, wantQ := range objectives {
ε := DefObjectives[wantQ]
gotQ := *m.Summary.Quantile[j].Quantile
gotV := *m.Summary.Quantile[j].Value
min, max := getBounds(allVars[i], wantQ, ε)
if gotQ != wantQ {
t.Errorf("got quantile %f for label %c, want %f", gotQ, 'A'+i, wantQ)
}
if gotV < min || gotV > max {
t.Errorf("got %f for quantile %f for label %c, want [%f,%f]", gotV, gotQ, 'A'+i, min, max)
}
}
}
return true
}
if err := quick.Check(it, nil); err != nil {
t.Error(err)
}
}
func TestSummaryDecay(t *testing.T) {
if testing.Short() {
t.Skip("Skipping test in short mode.")
// More because it depends on timing than because it is particularly long...
}
sum := NewSummary(SummaryOpts{
Name: "test_summary",
Help: "helpless",
MaxAge: 100 * time.Millisecond,
Objectives: map[float64]float64{0.1: 0.001},
AgeBuckets: 10,
})
m := &dto.Metric{}
i := 0
tick := time.NewTicker(time.Millisecond)
for _ = range tick.C {
i++
sum.Observe(float64(i))
if i%10 == 0 {
sum.Write(m)
if got, want := *m.Summary.Quantile[0].Value, math.Max(float64(i)/10, float64(i-90)); math.Abs(got-want) > 20 {
t.Errorf("%d. got %f, want %f", i, got, want)
}
m.Reset()
}
if i >= 1000 {
break
}
}
tick.Stop()
// Wait for MaxAge without observations and make sure quantiles are NaN.
time.Sleep(100 * time.Millisecond)
sum.Write(m)
if got := *m.Summary.Quantile[0].Value; !math.IsNaN(got) {
t.Errorf("got %f, want NaN after expiration", got)
}
}
func getBounds(vars []float64, q, ε float64) (min, max float64) {
// TODO(beorn7): This currently tolerates an error of up to 2*ε. The
// error must be at most ε, but for some reason, it's sometimes slightly
// higher. That's a bug.
n := float64(len(vars))
lower := int((q - 2*ε) * n)
upper := int(math.Ceil((q + 2*ε) * n))
min = vars[0]
if lower > 1 {
min = vars[lower-1]
}
max = vars[len(vars)-1]
if upper < len(vars) {
max = vars[upper-1]
}
return
}

View file

@ -1,312 +0,0 @@
// Copyright 2014 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 prometheus
import (
"fmt"
"testing"
dto "github.com/prometheus/client_model/go"
)
func TestDelete(t *testing.T) {
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
[]string{"l1", "l2"},
)
testDelete(t, vec)
}
func TestDeleteWithCollisions(t *testing.T) {
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
[]string{"l1", "l2"},
)
vec.hashAdd = func(h uint64, s string) uint64 { return 1 }
vec.hashAddByte = func(h uint64, b byte) uint64 { return 1 }
testDelete(t, vec)
}
func testDelete(t *testing.T, vec *UntypedVec) {
if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
if got, want := vec.Delete(Labels{"l1": "v1", "l2": "v2"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
if got, want := vec.Delete(Labels{"l2": "v2", "l1": "v1"}), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
if got, want := vec.Delete(Labels{"l2": "v2", "l1": "v1"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
if got, want := vec.Delete(Labels{"l2": "v1", "l1": "v2"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
if got, want := vec.Delete(Labels{"l1": "v1"}), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestDeleteLabelValues(t *testing.T) {
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
[]string{"l1", "l2"},
)
testDeleteLabelValues(t, vec)
}
func TestDeleteLabelValuesWithCollisions(t *testing.T) {
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
[]string{"l1", "l2"},
)
vec.hashAdd = func(h uint64, s string) uint64 { return 1 }
vec.hashAddByte = func(h uint64, b byte) uint64 { return 1 }
testDeleteLabelValues(t, vec)
}
func testDeleteLabelValues(t *testing.T, vec *UntypedVec) {
if got, want := vec.DeleteLabelValues("v1", "v2"), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
vec.With(Labels{"l1": "v1", "l2": "v3"}).(Untyped).Set(42) // Add junk data for collision.
if got, want := vec.DeleteLabelValues("v1", "v2"), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
if got, want := vec.DeleteLabelValues("v1", "v2"), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
if got, want := vec.DeleteLabelValues("v1", "v3"), true; got != want {
t.Errorf("got %v, want %v", got, want)
}
vec.With(Labels{"l1": "v1", "l2": "v2"}).(Untyped).Set(42)
// Delete out of order.
if got, want := vec.DeleteLabelValues("v2", "v1"), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
if got, want := vec.DeleteLabelValues("v1"), false; got != want {
t.Errorf("got %v, want %v", got, want)
}
}
func TestMetricVec(t *testing.T) {
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
[]string{"l1", "l2"},
)
testMetricVec(t, vec)
}
func TestMetricVecWithCollisions(t *testing.T) {
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
[]string{"l1", "l2"},
)
vec.hashAdd = func(h uint64, s string) uint64 { return 1 }
vec.hashAddByte = func(h uint64, b byte) uint64 { return 1 }
testMetricVec(t, vec)
}
func testMetricVec(t *testing.T, vec *UntypedVec) {
vec.Reset() // Actually test Reset now!
var pair [2]string
// Keep track of metrics.
expected := map[[2]string]int{}
for i := 0; i < 1000; i++ {
pair[0], pair[1] = fmt.Sprint(i%4), fmt.Sprint(i%5) // Varying combinations multiples.
expected[pair]++
vec.WithLabelValues(pair[0], pair[1]).Inc()
expected[[2]string{"v1", "v2"}]++
vec.WithLabelValues("v1", "v2").(Untyped).Inc()
}
var total int
for _, metrics := range vec.children {
for _, metric := range metrics {
total++
copy(pair[:], metric.values)
var metricOut dto.Metric
if err := metric.metric.Write(&metricOut); err != nil {
t.Fatal(err)
}
actual := *metricOut.Untyped.Value
var actualPair [2]string
for i, label := range metricOut.Label {
actualPair[i] = *label.Value
}
// Test output pair against metric.values to ensure we've selected
// the right one. We check this to ensure the below check means
// anything at all.
if actualPair != pair {
t.Fatalf("unexpected pair association in metric map: %v != %v", actualPair, pair)
}
if actual != float64(expected[pair]) {
t.Fatalf("incorrect counter value for %v: %v != %v", pair, actual, expected[pair])
}
}
}
if total != len(expected) {
t.Fatalf("unexpected number of metrics: %v != %v", total, len(expected))
}
vec.Reset()
if len(vec.children) > 0 {
t.Fatalf("reset failed")
}
}
func TestCounterVecEndToEndWithCollision(t *testing.T) {
vec := NewCounterVec(
CounterOpts{
Name: "test",
Help: "helpless",
},
[]string{"labelname"},
)
vec.WithLabelValues("77kepQFQ8Kl").Inc()
vec.WithLabelValues("!0IC=VloaY").Add(2)
m := &dto.Metric{}
if err := vec.WithLabelValues("77kepQFQ8Kl").Write(m); err != nil {
t.Fatal(err)
}
if got, want := m.GetLabel()[0].GetValue(), "77kepQFQ8Kl"; got != want {
t.Errorf("got label value %q, want %q", got, want)
}
if got, want := m.GetCounter().GetValue(), 1.; got != want {
t.Errorf("got value %f, want %f", got, want)
}
m.Reset()
if err := vec.WithLabelValues("!0IC=VloaY").Write(m); err != nil {
t.Fatal(err)
}
if got, want := m.GetLabel()[0].GetValue(), "!0IC=VloaY"; got != want {
t.Errorf("got label value %q, want %q", got, want)
}
if got, want := m.GetCounter().GetValue(), 2.; got != want {
t.Errorf("got value %f, want %f", got, want)
}
}
func BenchmarkMetricVecWithLabelValuesBasic(b *testing.B) {
benchmarkMetricVecWithLabelValues(b, map[string][]string{
"l1": []string{"onevalue"},
"l2": []string{"twovalue"},
})
}
func BenchmarkMetricVecWithLabelValues2Keys10ValueCardinality(b *testing.B) {
benchmarkMetricVecWithLabelValuesCardinality(b, 2, 10)
}
func BenchmarkMetricVecWithLabelValues4Keys10ValueCardinality(b *testing.B) {
benchmarkMetricVecWithLabelValuesCardinality(b, 4, 10)
}
func BenchmarkMetricVecWithLabelValues2Keys100ValueCardinality(b *testing.B) {
benchmarkMetricVecWithLabelValuesCardinality(b, 2, 100)
}
func BenchmarkMetricVecWithLabelValues10Keys100ValueCardinality(b *testing.B) {
benchmarkMetricVecWithLabelValuesCardinality(b, 10, 100)
}
func BenchmarkMetricVecWithLabelValues10Keys1000ValueCardinality(b *testing.B) {
benchmarkMetricVecWithLabelValuesCardinality(b, 10, 1000)
}
func benchmarkMetricVecWithLabelValuesCardinality(b *testing.B, nkeys, nvalues int) {
labels := map[string][]string{}
for i := 0; i < nkeys; i++ {
var (
k = fmt.Sprintf("key-%v", i)
vs = make([]string, 0, nvalues)
)
for j := 0; j < nvalues; j++ {
vs = append(vs, fmt.Sprintf("value-%v", j))
}
labels[k] = vs
}
benchmarkMetricVecWithLabelValues(b, labels)
}
func benchmarkMetricVecWithLabelValues(b *testing.B, labels map[string][]string) {
var keys []string
for k := range labels { // Map order dependent, who cares though.
keys = append(keys, k)
}
values := make([]string, len(labels)) // Value cache for permutations.
vec := NewUntypedVec(
UntypedOpts{
Name: "test",
Help: "helpless",
},
keys,
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
// Varies input across provide map entries based on key size.
for j, k := range keys {
candidates := labels[k]
values[j] = candidates[i%len(candidates)]
}
vec.WithLabelValues(values...)
}
}

View file

@ -39,6 +39,7 @@ src/main/java/io/prometheus/client/Metrics.java: metrics.proto
python: python/prometheus/client/model/metrics_pb2.py
python/prometheus/client/model/metrics_pb2.py: metrics.proto
mkdir -p python/prometheus/client/model
protoc $< --python_out=python/prometheus/client/model
ruby:

View file

@ -9,4 +9,4 @@ components and libraries.
* **log**: A logging wrapper around [logrus](https://github.com/sirupsen/logrus)
* **model**: Shared data structures
* **route**: A routing wrapper around [httprouter](https://github.com/julienschmidt/httprouter) using `context.Context`
* **version**: Version informations and metric
* **version**: Version information and metrics

View file

@ -1,167 +0,0 @@
// Copyright 2015 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 expfmt
import (
"bytes"
"compress/gzip"
"io"
"io/ioutil"
"testing"
"github.com/matttproud/golang_protobuf_extensions/pbutil"
dto "github.com/prometheus/client_model/go"
)
var parser TextParser
// Benchmarks to show how much penalty text format parsing actually inflicts.
//
// Example results on Linux 3.13.0, Intel(R) Core(TM) i7-4700MQ CPU @ 2.40GHz, go1.4.
//
// BenchmarkParseText 1000 1188535 ns/op 205085 B/op 6135 allocs/op
// BenchmarkParseTextGzip 1000 1376567 ns/op 246224 B/op 6151 allocs/op
// BenchmarkParseProto 10000 172790 ns/op 52258 B/op 1160 allocs/op
// BenchmarkParseProtoGzip 5000 324021 ns/op 94931 B/op 1211 allocs/op
// BenchmarkParseProtoMap 10000 187946 ns/op 58714 B/op 1203 allocs/op
//
// CONCLUSION: The overhead for the map is negligible. Text format needs ~5x more allocations.
// Without compression, it needs ~7x longer, but with compression (the more relevant scenario),
// the difference becomes less relevant, only ~4x.
//
// The test data contains 248 samples.
// BenchmarkParseText benchmarks the parsing of a text-format scrape into metric
// family DTOs.
func BenchmarkParseText(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/text")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
if _, err := parser.TextToMetricFamilies(bytes.NewReader(data)); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkParseTextGzip benchmarks the parsing of a gzipped text-format scrape
// into metric family DTOs.
func BenchmarkParseTextGzip(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/text.gz")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
in, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
b.Fatal(err)
}
if _, err := parser.TextToMetricFamilies(in); err != nil {
b.Fatal(err)
}
}
}
// BenchmarkParseProto benchmarks the parsing of a protobuf-format scrape into
// metric family DTOs. Note that this does not build a map of metric families
// (as the text version does), because it is not required for Prometheus
// ingestion either. (However, it is required for the text-format parsing, as
// the metric family might be sprinkled all over the text, while the
// protobuf-format guarantees bundling at one place.)
func BenchmarkParseProto(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/protobuf")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
family := &dto.MetricFamily{}
in := bytes.NewReader(data)
for {
family.Reset()
if _, err := pbutil.ReadDelimited(in, family); err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
}
}
}
// BenchmarkParseProtoGzip is like BenchmarkParseProto above, but parses gzipped
// protobuf format.
func BenchmarkParseProtoGzip(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/protobuf.gz")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
family := &dto.MetricFamily{}
in, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
b.Fatal(err)
}
for {
family.Reset()
if _, err := pbutil.ReadDelimited(in, family); err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
}
}
}
// BenchmarkParseProtoMap is like BenchmarkParseProto but DOES put the parsed
// metric family DTOs into a map. This is not happening during Prometheus
// ingestion. It is just here to measure the overhead of that map creation and
// separate it from the overhead of the text format parsing.
func BenchmarkParseProtoMap(b *testing.B) {
b.StopTimer()
data, err := ioutil.ReadFile("testdata/protobuf")
if err != nil {
b.Fatal(err)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
families := map[string]*dto.MetricFamily{}
in := bytes.NewReader(data)
for {
family := &dto.MetricFamily{}
if _, err := pbutil.ReadDelimited(in, family); err != nil {
if err == io.EOF {
break
}
b.Fatal(err)
}
families[family.GetName()] = family
}
}
}

View file

@ -164,9 +164,9 @@ func (sd *SampleDecoder) Decode(s *model.Vector) error {
}
// ExtractSamples builds a slice of samples from the provided metric
// families. If an error occurs during sample extraction, it continues to
// families. If an error occurrs during sample extraction, it continues to
// extract from the remaining metric families. The returned error is the last
// error that has occured.
// error that has occurred.
func ExtractSamples(o *DecodeOptions, fams ...*dto.MetricFamily) (model.Vector, error) {
var (
all model.Vector

View file

@ -1,435 +0,0 @@
// Copyright 2015 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 expfmt
import (
"io"
"net/http"
"reflect"
"sort"
"strings"
"testing"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/common/model"
)
func TestTextDecoder(t *testing.T) {
var (
ts = model.Now()
in = `
# Only a quite simple scenario with two metric families.
# More complicated tests of the parser itself can be found in the text package.
# TYPE mf2 counter
mf2 3
mf1{label="value1"} -3.14 123456
mf1{label="value2"} 42
mf2 4
`
out = model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf1",
"label": "value1",
},
Value: -3.14,
Timestamp: 123456,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf1",
"label": "value2",
},
Value: 42,
Timestamp: ts,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf2",
},
Value: 3,
Timestamp: ts,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "mf2",
},
Value: 4,
Timestamp: ts,
},
}
)
dec := &SampleDecoder{
Dec: &textDecoder{r: strings.NewReader(in)},
Opts: &DecodeOptions{
Timestamp: ts,
},
}
var all model.Vector
for {
var smpls model.Vector
err := dec.Decode(&smpls)
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
all = append(all, smpls...)
}
sort.Sort(all)
sort.Sort(out)
if !reflect.DeepEqual(all, out) {
t.Fatalf("output does not match")
}
}
func TestProtoDecoder(t *testing.T) {
var testTime = model.Now()
scenarios := []struct {
in string
expected model.Vector
fail bool
}{
{
in: "",
},
{
in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_!abel_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
fail: true,
},
{
in: "\x8f\x01\n\rrequest_count\x12\x12Number of requests\x18\x00\"0\n#\n\x0fsome_label_name\x12\x10some_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00E\xc0\"6\n)\n\x12another_label_name\x12\x13another_label_value\x1a\t\t\x00\x00\x00\x00\x00\x00U@",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"some_label_name": "some_label_value",
},
Value: -42,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"another_label_name": "another_label_value",
},
Value: 84,
Timestamp: testTime,
},
},
},
{
in: "\xb9\x01\n\rrequest_count\x12\x12Number of requests\x18\x02\"O\n#\n\x0fsome_label_name\x12\x10some_label_value\"(\x1a\x12\t\xaeG\xe1z\x14\xae\xef?\x11\x00\x00\x00\x00\x00\x00E\xc0\x1a\x12\t+\x87\x16\xd9\xce\xf7\xef?\x11\x00\x00\x00\x00\x00\x00U\xc0\"A\n)\n\x12another_label_name\x12\x13another_label_value\"\x14\x1a\x12\t\x00\x00\x00\x00\x00\x00\xe0?\x11\x00\x00\x00\x00\x00\x00$@",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_count",
"some_label_name": "some_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_sum",
"some_label_name": "some_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"some_label_name": "some_label_value",
"quantile": "0.99",
},
Value: -42,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"some_label_name": "some_label_value",
"quantile": "0.999",
},
Value: -84,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_count",
"another_label_name": "another_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count_sum",
"another_label_name": "another_label_value",
},
Value: 0,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
"another_label_name": "another_label_value",
"quantile": "0.5",
},
Value: 10,
Timestamp: testTime,
},
},
},
{
in: "\x8d\x01\n\x1drequest_duration_microseconds\x12\x15The response latency.\x18\x04\"S:Q\b\x85\x15\x11\xcd\xcc\xccL\x8f\xcb:A\x1a\v\b{\x11\x00\x00\x00\x00\x00\x00Y@\x1a\f\b\x9c\x03\x11\x00\x00\x00\x00\x00\x00^@\x1a\f\b\xd0\x04\x11\x00\x00\x00\x00\x00\x00b@\x1a\f\b\xf4\v\x11\x9a\x99\x99\x99\x99\x99e@\x1a\f\b\x85\x15\x11\x00\x00\x00\x00\x00\x00\xf0\u007f",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "100",
},
Value: 123,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "120",
},
Value: 412,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "144",
},
Value: 592,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "172.8",
},
Value: 1524,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_bucket",
"le": "+Inf",
},
Value: 2693,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_sum",
},
Value: 1756047.3,
Timestamp: testTime,
},
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_duration_microseconds_count",
},
Value: 2693,
Timestamp: testTime,
},
},
},
{
// The metric type is unset in this protobuf, which needs to be handled
// correctly by the decoder.
in: "\x1c\n\rrequest_count\"\v\x1a\t\t\x00\x00\x00\x00\x00\x00\xf0?",
expected: model.Vector{
&model.Sample{
Metric: model.Metric{
model.MetricNameLabel: "request_count",
},
Value: 1,
Timestamp: testTime,
},
},
},
}
for i, scenario := range scenarios {
dec := &SampleDecoder{
Dec: &protoDecoder{r: strings.NewReader(scenario.in)},
Opts: &DecodeOptions{
Timestamp: testTime,
},
}
var all model.Vector
for {
var smpls model.Vector
err := dec.Decode(&smpls)
if err == io.EOF {
break
}
if scenario.fail {
if err == nil {
t.Fatal("Expected error but got none")
}
break
}
if err != nil {
t.Fatal(err)
}
all = append(all, smpls...)
}
sort.Sort(all)
sort.Sort(scenario.expected)
if !reflect.DeepEqual(all, scenario.expected) {
t.Fatalf("%d. output does not match, want: %#v, got %#v", i, scenario.expected, all)
}
}
}
func testDiscriminatorHTTPHeader(t testing.TB) {
var scenarios = []struct {
input map[string]string
output Format
err error
}{
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="delimited"`},
output: FmtProtoDelim,
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="illegal"; encoding="delimited"`},
output: FmtUnknown,
},
{
input: map[string]string{"Content-Type": `application/vnd.google.protobuf; proto="io.prometheus.client.MetricFamily"; encoding="illegal"`},
output: FmtUnknown,
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.4`},
output: FmtText,
},
{
input: map[string]string{"Content-Type": `text/plain`},
output: FmtText,
},
{
input: map[string]string{"Content-Type": `text/plain; version=0.0.3`},
output: FmtUnknown,
},
}
for i, scenario := range scenarios {
var header http.Header
if len(scenario.input) > 0 {
header = http.Header{}
}
for key, value := range scenario.input {
header.Add(key, value)
}
actual := ResponseFormat(header)
if scenario.output != actual {
t.Errorf("%d. expected %s, got %s", i, scenario.output, actual)
}
}
}
func TestDiscriminatorHTTPHeader(t *testing.T) {
testDiscriminatorHTTPHeader(t)
}
func BenchmarkDiscriminatorHTTPHeader(b *testing.B) {
for i := 0; i < b.N; i++ {
testDiscriminatorHTTPHeader(b)
}
}
func TestExtractSamples(t *testing.T) {
var (
goodMetricFamily1 = &dto.MetricFamily{
Name: proto.String("foo"),
Help: proto.String("Help for foo."),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Counter: &dto.Counter{
Value: proto.Float64(4711),
},
},
},
}
goodMetricFamily2 = &dto.MetricFamily{
Name: proto.String("bar"),
Help: proto.String("Help for bar."),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Gauge: &dto.Gauge{
Value: proto.Float64(3.14),
},
},
},
}
badMetricFamily = &dto.MetricFamily{
Name: proto.String("bad"),
Help: proto.String("Help for bad."),
Type: dto.MetricType(42).Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Gauge: &dto.Gauge{
Value: proto.Float64(2.7),
},
},
},
}
opts = &DecodeOptions{
Timestamp: 42,
}
)
got, err := ExtractSamples(opts, goodMetricFamily1, goodMetricFamily2)
if err != nil {
t.Error("Unexpected error from ExtractSamples:", err)
}
want := model.Vector{
&model.Sample{Metric: model.Metric{model.MetricNameLabel: "foo"}, Value: 4711, Timestamp: 42},
&model.Sample{Metric: model.Metric{model.MetricNameLabel: "bar"}, Value: 3.14, Timestamp: 42},
}
if !reflect.DeepEqual(got, want) {
t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
}
got, err = ExtractSamples(opts, goodMetricFamily1, badMetricFamily, goodMetricFamily2)
if err == nil {
t.Error("Expected error from ExtractSamples")
}
if !reflect.DeepEqual(got, want) {
t.Errorf("unexpected samples extracted, got: %v, want: %v", got, want)
}
}

View file

@ -26,7 +26,7 @@ const (
// The Content-Type values for the different wire protocols.
FmtUnknown Format = `<unknown>`
FmtText Format = `text/plain; version=` + TextVersion
FmtText Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
FmtProtoDelim Format = ProtoFmt + ` encoding=delimited`
FmtProtoText Format = ProtoFmt + ` encoding=text`
FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`

View file

@ -1,443 +0,0 @@
// Copyright 2014 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 expfmt
import (
"bytes"
"math"
"strings"
"testing"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
func testCreate(t testing.TB) {
var scenarios = []struct {
in *dto.MetricFamily
out string
}{
// 0: Counter, NaN as value, timestamp given.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("two-line\n doc str\\ing"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(math.NaN()),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(.23),
},
TimestampMs: proto.Int64(1234567890),
},
},
},
out: `# HELP name two-line\n doc str\\ing
# TYPE name counter
name{labelname="val1",basename="basevalue"} NaN
name{labelname="val2",basename="basevalue"} 0.23 1234567890
`,
},
// 1: Gauge, some escaping required, +Inf as value, multi-byte characters in label values.
{
in: &dto.MetricFamily{
Name: proto.String("gauge_name"),
Help: proto.String("gauge\ndoc\nstr\"ing"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("val with\nnew line"),
},
&dto.LabelPair{
Name: proto.String("name_2"),
Value: proto.String("val with \\backslash and \"quotes\""),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(math.Inf(+1)),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("Björn"),
},
&dto.LabelPair{
Name: proto.String("name_2"),
Value: proto.String("佖佥"),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(3.14E42),
},
},
},
},
out: `# HELP gauge_name gauge\ndoc\nstr"ing
# TYPE gauge_name gauge
gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf
gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42
`,
},
// 2: Untyped, no help, one sample with no labels and -Inf as value, another sample with one label.
{
in: &dto.MetricFamily{
Name: proto.String("untyped_name"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("value 1"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(-1.23e-45),
},
},
},
},
out: `# TYPE untyped_name untyped
untyped_name -Inf
untyped_name{name_1="value 1"} -1.23e-45
`,
},
// 3: Summary.
{
in: &dto.MetricFamily{
Name: proto.String("summary_name"),
Help: proto.String("summary docstring"),
Type: dto.MetricType_SUMMARY.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Summary: &dto.Summary{
SampleCount: proto.Uint64(42),
SampleSum: proto.Float64(-3.4567),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.5),
Value: proto.Float64(-1.23),
},
&dto.Quantile{
Quantile: proto.Float64(0.9),
Value: proto.Float64(.2342354),
},
&dto.Quantile{
Quantile: proto.Float64(0.99),
Value: proto.Float64(0),
},
},
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("name_1"),
Value: proto.String("value 1"),
},
&dto.LabelPair{
Name: proto.String("name_2"),
Value: proto.String("value 2"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(4711),
SampleSum: proto.Float64(2010.1971),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.5),
Value: proto.Float64(1),
},
&dto.Quantile{
Quantile: proto.Float64(0.9),
Value: proto.Float64(2),
},
&dto.Quantile{
Quantile: proto.Float64(0.99),
Value: proto.Float64(3),
},
},
},
},
},
},
out: `# HELP summary_name summary docstring
# TYPE summary_name summary
summary_name{quantile="0.5"} -1.23
summary_name{quantile="0.9"} 0.2342354
summary_name{quantile="0.99"} 0
summary_name_sum -3.4567
summary_name_count 42
summary_name{name_1="value 1",name_2="value 2",quantile="0.5"} 1
summary_name{name_1="value 1",name_2="value 2",quantile="0.9"} 2
summary_name{name_1="value 1",name_2="value 2",quantile="0.99"} 3
summary_name_sum{name_1="value 1",name_2="value 2"} 2010.1971
summary_name_count{name_1="value 1",name_2="value 2"} 4711
`,
},
// 4: Histogram
{
in: &dto.MetricFamily{
Name: proto.String("request_duration_microseconds"),
Help: proto.String("The response latency."),
Type: dto.MetricType_HISTOGRAM.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Histogram: &dto.Histogram{
SampleCount: proto.Uint64(2693),
SampleSum: proto.Float64(1756047.3),
Bucket: []*dto.Bucket{
&dto.Bucket{
UpperBound: proto.Float64(100),
CumulativeCount: proto.Uint64(123),
},
&dto.Bucket{
UpperBound: proto.Float64(120),
CumulativeCount: proto.Uint64(412),
},
&dto.Bucket{
UpperBound: proto.Float64(144),
CumulativeCount: proto.Uint64(592),
},
&dto.Bucket{
UpperBound: proto.Float64(172.8),
CumulativeCount: proto.Uint64(1524),
},
&dto.Bucket{
UpperBound: proto.Float64(math.Inf(+1)),
CumulativeCount: proto.Uint64(2693),
},
},
},
},
},
},
out: `# HELP request_duration_microseconds The response latency.
# TYPE request_duration_microseconds histogram
request_duration_microseconds_bucket{le="100"} 123
request_duration_microseconds_bucket{le="120"} 412
request_duration_microseconds_bucket{le="144"} 592
request_duration_microseconds_bucket{le="172.8"} 1524
request_duration_microseconds_bucket{le="+Inf"} 2693
request_duration_microseconds_sum 1.7560473e+06
request_duration_microseconds_count 2693
`,
},
// 5: Histogram with missing +Inf bucket.
{
in: &dto.MetricFamily{
Name: proto.String("request_duration_microseconds"),
Help: proto.String("The response latency."),
Type: dto.MetricType_HISTOGRAM.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Histogram: &dto.Histogram{
SampleCount: proto.Uint64(2693),
SampleSum: proto.Float64(1756047.3),
Bucket: []*dto.Bucket{
&dto.Bucket{
UpperBound: proto.Float64(100),
CumulativeCount: proto.Uint64(123),
},
&dto.Bucket{
UpperBound: proto.Float64(120),
CumulativeCount: proto.Uint64(412),
},
&dto.Bucket{
UpperBound: proto.Float64(144),
CumulativeCount: proto.Uint64(592),
},
&dto.Bucket{
UpperBound: proto.Float64(172.8),
CumulativeCount: proto.Uint64(1524),
},
},
},
},
},
},
out: `# HELP request_duration_microseconds The response latency.
# TYPE request_duration_microseconds histogram
request_duration_microseconds_bucket{le="100"} 123
request_duration_microseconds_bucket{le="120"} 412
request_duration_microseconds_bucket{le="144"} 592
request_duration_microseconds_bucket{le="172.8"} 1524
request_duration_microseconds_bucket{le="+Inf"} 2693
request_duration_microseconds_sum 1.7560473e+06
request_duration_microseconds_count 2693
`,
},
// 6: No metric type, should result in default type Counter.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("doc string"),
Metric: []*dto.Metric{
&dto.Metric{
Counter: &dto.Counter{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
out: `# HELP name doc string
# TYPE name counter
name -Inf
`,
},
}
for i, scenario := range scenarios {
out := bytes.NewBuffer(make([]byte, 0, len(scenario.out)))
n, err := MetricFamilyToText(out, scenario.in)
if err != nil {
t.Errorf("%d. error: %s", i, err)
continue
}
if expected, got := len(scenario.out), n; expected != got {
t.Errorf(
"%d. expected %d bytes written, got %d",
i, expected, got,
)
}
if expected, got := scenario.out, out.String(); expected != got {
t.Errorf(
"%d. expected out=%q, got %q",
i, expected, got,
)
}
}
}
func TestCreate(t *testing.T) {
testCreate(t)
}
func BenchmarkCreate(b *testing.B) {
for i := 0; i < b.N; i++ {
testCreate(b)
}
}
func testCreateError(t testing.TB) {
var scenarios = []struct {
in *dto.MetricFamily
err string
}{
// 0: No metric.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("doc string"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{},
},
err: "MetricFamily has no metrics",
},
// 1: No metric name.
{
in: &dto.MetricFamily{
Help: proto.String("doc string"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
err: "MetricFamily has no name",
},
// 2: Wrong type.
{
in: &dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("doc string"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
err: "expected counter in metric",
},
}
for i, scenario := range scenarios {
var out bytes.Buffer
_, err := MetricFamilyToText(&out, scenario.in)
if err == nil {
t.Errorf("%d. expected error, got nil", i)
continue
}
if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
t.Errorf(
"%d. expected error starting with %q, got %q",
i, expected, got,
)
}
}
}
func TestCreateError(t *testing.T) {
testCreateError(t)
}
func BenchmarkCreateError(b *testing.B) {
for i := 0; i < b.N; i++ {
testCreateError(b)
}
}

View file

@ -556,8 +556,8 @@ func (p *TextParser) readTokenUntilWhitespace() {
// byte considered is the byte already read (now in p.currentByte). The first
// newline byte encountered is still copied into p.currentByte, but not into
// p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
// recognized: '\\' tranlates into '\', and '\n' into a line-feed character. All
// other escape sequences are invalid and cause an error.
// recognized: '\\' translates into '\', and '\n' into a line-feed character.
// All other escape sequences are invalid and cause an error.
func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
p.currentToken.Reset()
escaped := false

View file

@ -1,593 +0,0 @@
// Copyright 2014 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 expfmt
import (
"math"
"strings"
"testing"
"github.com/golang/protobuf/proto"
dto "github.com/prometheus/client_model/go"
)
func testTextParse(t testing.TB) {
var scenarios = []struct {
in string
out []*dto.MetricFamily
}{
// 0: Empty lines as input.
{
in: `
`,
out: []*dto.MetricFamily{},
},
// 1: Minimal case.
{
in: `
minimal_metric 1.234
another_metric -3e3 103948
# Even that:
no_labels{} 3
# HELP line for non-existing metric will be ignored.
`,
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("minimal_metric"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(1.234),
},
},
},
},
&dto.MetricFamily{
Name: proto.String("another_metric"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(-3e3),
},
TimestampMs: proto.Int64(103948),
},
},
},
&dto.MetricFamily{
Name: proto.String("no_labels"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(3),
},
},
},
},
},
},
// 2: Counters & gauges, docstrings, various whitespace, escape sequences.
{
in: `
# A normal comment.
#
# TYPE name counter
name{labelname="val1",basename="basevalue"} NaN
name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1234567890
# HELP name two-line\n doc str\\ing
# HELP name2 doc str"ing 2
# TYPE name2 gauge
name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321
name2{ labelname = "val1" , }-Inf
`,
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("name"),
Help: proto.String("two-line\n doc str\\ing"),
Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(math.NaN()),
},
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("base\"v\\al\nue"),
},
},
Counter: &dto.Counter{
Value: proto.Float64(.23),
},
TimestampMs: proto.Int64(1234567890),
},
},
},
&dto.MetricFamily{
Name: proto.String("name2"),
Help: proto.String("doc str\"ing 2"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("basename"),
Value: proto.String("basevalue2"),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(math.Inf(+1)),
},
TimestampMs: proto.Int64(54321),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("labelname"),
Value: proto.String("val1"),
},
},
Gauge: &dto.Gauge{
Value: proto.Float64(math.Inf(-1)),
},
},
},
},
},
},
// 3: The evil summary, mixed with other types and funny comments.
{
in: `
# TYPE my_summary summary
my_summary{n1="val1",quantile="0.5"} 110
decoy -1 -2
my_summary{n1="val1",quantile="0.9"} 140 1
my_summary_count{n1="val1"} 42
# Latest timestamp wins in case of a summary.
my_summary_sum{n1="val1"} 4711 2
fake_sum{n1="val1"} 2001
# TYPE another_summary summary
another_summary_count{n2="val2",n1="val1"} 20
my_summary_count{n2="val2",n1="val1"} 5 5
another_summary{n1="val1",n2="val2",quantile=".3"} -1.2
my_summary_sum{n1="val2"} 08 15
my_summary{n1="val3", quantile="0.2"} 4711
my_summary{n1="val1",n2="val2",quantile="-12.34",} NaN
# some
# funny comments
# HELP
# HELP
# HELP my_summary
# HELP my_summary
`,
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("fake_sum"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Untyped: &dto.Untyped{
Value: proto.Float64(2001),
},
},
},
},
&dto.MetricFamily{
Name: proto.String("decoy"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(-1),
},
TimestampMs: proto.Int64(-2),
},
},
},
&dto.MetricFamily{
Name: proto.String("my_summary"),
Type: dto.MetricType_SUMMARY.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(42),
SampleSum: proto.Float64(4711),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.5),
Value: proto.Float64(110),
},
&dto.Quantile{
Quantile: proto.Float64(0.9),
Value: proto.Float64(140),
},
},
},
TimestampMs: proto.Int64(2),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n2"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(5),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(-12.34),
Value: proto.Float64(math.NaN()),
},
},
},
TimestampMs: proto.Int64(5),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val2"),
},
},
Summary: &dto.Summary{
SampleSum: proto.Float64(8),
},
TimestampMs: proto.Int64(15),
},
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val3"),
},
},
Summary: &dto.Summary{
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.2),
Value: proto.Float64(4711),
},
},
},
},
},
},
&dto.MetricFamily{
Name: proto.String("another_summary"),
Type: dto.MetricType_SUMMARY.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Label: []*dto.LabelPair{
&dto.LabelPair{
Name: proto.String("n2"),
Value: proto.String("val2"),
},
&dto.LabelPair{
Name: proto.String("n1"),
Value: proto.String("val1"),
},
},
Summary: &dto.Summary{
SampleCount: proto.Uint64(20),
Quantile: []*dto.Quantile{
&dto.Quantile{
Quantile: proto.Float64(0.3),
Value: proto.Float64(-1.2),
},
},
},
},
},
},
},
},
// 4: The histogram.
{
in: `
# HELP request_duration_microseconds The response latency.
# TYPE request_duration_microseconds histogram
request_duration_microseconds_bucket{le="100"} 123
request_duration_microseconds_bucket{le="120"} 412
request_duration_microseconds_bucket{le="144"} 592
request_duration_microseconds_bucket{le="172.8"} 1524
request_duration_microseconds_bucket{le="+Inf"} 2693
request_duration_microseconds_sum 1.7560473e+06
request_duration_microseconds_count 2693
`,
out: []*dto.MetricFamily{
{
Name: proto.String("request_duration_microseconds"),
Help: proto.String("The response latency."),
Type: dto.MetricType_HISTOGRAM.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Histogram: &dto.Histogram{
SampleCount: proto.Uint64(2693),
SampleSum: proto.Float64(1756047.3),
Bucket: []*dto.Bucket{
&dto.Bucket{
UpperBound: proto.Float64(100),
CumulativeCount: proto.Uint64(123),
},
&dto.Bucket{
UpperBound: proto.Float64(120),
CumulativeCount: proto.Uint64(412),
},
&dto.Bucket{
UpperBound: proto.Float64(144),
CumulativeCount: proto.Uint64(592),
},
&dto.Bucket{
UpperBound: proto.Float64(172.8),
CumulativeCount: proto.Uint64(1524),
},
&dto.Bucket{
UpperBound: proto.Float64(math.Inf(+1)),
CumulativeCount: proto.Uint64(2693),
},
},
},
},
},
},
},
},
}
for i, scenario := range scenarios {
out, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in))
if err != nil {
t.Errorf("%d. error: %s", i, err)
continue
}
if expected, got := len(scenario.out), len(out); expected != got {
t.Errorf(
"%d. expected %d MetricFamilies, got %d",
i, expected, got,
)
}
for _, expected := range scenario.out {
got, ok := out[expected.GetName()]
if !ok {
t.Errorf(
"%d. expected MetricFamily %q, found none",
i, expected.GetName(),
)
continue
}
if expected.String() != got.String() {
t.Errorf(
"%d. expected MetricFamily %s, got %s",
i, expected, got,
)
}
}
}
}
func TestTextParse(t *testing.T) {
testTextParse(t)
}
func BenchmarkTextParse(b *testing.B) {
for i := 0; i < b.N; i++ {
testTextParse(b)
}
}
func testTextParseError(t testing.TB) {
var scenarios = []struct {
in string
err string
}{
// 0: No new-line at end of input.
{
in: `
bla 3.14
blubber 42`,
err: "text format parsing error in line 3: unexpected end of input stream",
},
// 1: Invalid escape sequence in label value.
{
in: `metric{label="\t"} 3.14`,
err: "text format parsing error in line 1: invalid escape sequence",
},
// 2: Newline in label value.
{
in: `
metric{label="new
line"} 3.14
`,
err: `text format parsing error in line 2: label value "new" contains unescaped new-line`,
},
// 3:
{
in: `metric{@="bla"} 3.14`,
err: "text format parsing error in line 1: invalid label name for metric",
},
// 4:
{
in: `metric{__name__="bla"} 3.14`,
err: `text format parsing error in line 1: label name "__name__" is reserved`,
},
// 5:
{
in: `metric{label+="bla"} 3.14`,
err: "text format parsing error in line 1: expected '=' after label name",
},
// 6:
{
in: `metric{label=bla} 3.14`,
err: "text format parsing error in line 1: expected '\"' at start of label value",
},
// 7:
{
in: `
# TYPE metric summary
metric{quantile="bla"} 3.14
`,
err: "text format parsing error in line 3: expected float as value for 'quantile' label",
},
// 8:
{
in: `metric{label="bla"+} 3.14`,
err: "text format parsing error in line 1: unexpected end of label value",
},
// 9:
{
in: `metric{label="bla"} 3.14 2.72
`,
err: "text format parsing error in line 1: expected integer as timestamp",
},
// 10:
{
in: `metric{label="bla"} 3.14 2 3
`,
err: "text format parsing error in line 1: spurious string after timestamp",
},
// 11:
{
in: `metric{label="bla"} blubb
`,
err: "text format parsing error in line 1: expected float as value",
},
// 12:
{
in: `
# HELP metric one
# HELP metric two
`,
err: "text format parsing error in line 3: second HELP line for metric name",
},
// 13:
{
in: `
# TYPE metric counter
# TYPE metric untyped
`,
err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`,
},
// 14:
{
in: `
metric 4.12
# TYPE metric counter
`,
err: `text format parsing error in line 3: second TYPE line for metric name "metric", or TYPE reported after samples`,
},
// 14:
{
in: `
# TYPE metric bla
`,
err: "text format parsing error in line 2: unknown metric type",
},
// 15:
{
in: `
# TYPE met-ric
`,
err: "text format parsing error in line 2: invalid metric name in comment",
},
// 16:
{
in: `@invalidmetric{label="bla"} 3.14 2`,
err: "text format parsing error in line 1: invalid metric name",
},
// 17:
{
in: `{label="bla"} 3.14 2`,
err: "text format parsing error in line 1: invalid metric name",
},
// 18:
{
in: `
# TYPE metric histogram
metric_bucket{le="bla"} 3.14
`,
err: "text format parsing error in line 3: expected float as value for 'le' label",
},
// 19: Invalid UTF-8 in label value.
{
in: "metric{l=\"\xbd\"} 3.14\n",
err: "text format parsing error in line 1: invalid label value \"\\xbd\"",
},
}
for i, scenario := range scenarios {
_, err := parser.TextToMetricFamilies(strings.NewReader(scenario.in))
if err == nil {
t.Errorf("%d. expected error, got nil", i)
continue
}
if expected, got := scenario.err, err.Error(); strings.Index(got, expected) != 0 {
t.Errorf(
"%d. expected error starting with %q, got %q",
i, expected, got,
)
}
}
}
func TestTextParseError(t *testing.T) {
testTextParseError(t)
}
func BenchmarkParseError(b *testing.B) {
for i := 0; i < b.N; i++ {
testTextParseError(b)
}
}

View file

@ -1,33 +0,0 @@
package goautoneg
import (
"testing"
)
var chrome = "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
func TestParseAccept(t *testing.T) {
alternatives := []string{"text/html", "image/png"}
content_type := Negotiate(chrome, alternatives)
if content_type != "image/png" {
t.Errorf("got %s expected image/png", content_type)
}
alternatives = []string{"text/html", "text/plain", "text/n3"}
content_type = Negotiate(chrome, alternatives)
if content_type != "text/html" {
t.Errorf("got %s expected text/html", content_type)
}
alternatives = []string{"text/n3", "text/plain"}
content_type = Negotiate(chrome, alternatives)
if content_type != "text/plain" {
t.Errorf("got %s expected text/plain", content_type)
}
alternatives = []string{"text/n3", "application/rdf+xml"}
content_type = Negotiate(chrome, alternatives)
if content_type != "text/n3" {
t.Errorf("got %s expected text/n3", content_type)
}
}

View file

@ -1,118 +0,0 @@
// 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 model
import (
"strings"
"testing"
"time"
)
func TestAlertValidate(t *testing.T) {
ts := time.Now()
var cases = []struct {
alert *Alert
err string
}{
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
},
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
},
err: "start time missing",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
EndsAt: ts,
},
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
EndsAt: ts.Add(1 * time.Minute),
},
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
StartsAt: ts,
EndsAt: ts.Add(-1 * time.Minute),
},
err: "start time must be before end time",
},
{
alert: &Alert{
StartsAt: ts,
},
err: "at least one label pair required",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b", "!bad": "label"},
StartsAt: ts,
},
err: "invalid label set: invalid name",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b", "bad": "\xfflabel"},
StartsAt: ts,
},
err: "invalid label set: invalid value",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
Annotations: LabelSet{"!bad": "label"},
StartsAt: ts,
},
err: "invalid annotations: invalid name",
},
{
alert: &Alert{
Labels: LabelSet{"a": "b"},
Annotations: LabelSet{"bad": "\xfflabel"},
StartsAt: ts,
},
err: "invalid annotations: invalid value",
},
}
for i, c := range cases {
err := c.alert.Validate()
if err == nil {
if c.err == "" {
continue
}
t.Errorf("%d. Expected error %q but got none", i, c.err)
continue
}
if c.err == "" && err != nil {
t.Errorf("%d. Expected no error but got %q", i, err)
continue
}
if !strings.Contains(err.Error(), c.err) {
t.Errorf("%d. Expected error to contain %q but got %q", i, c.err, err)
}
}
}

View file

@ -1,140 +0,0 @@
// 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 model
import (
"sort"
"testing"
)
func testLabelNames(t testing.TB) {
var scenarios = []struct {
in LabelNames
out LabelNames
}{
{
in: LabelNames{"ZZZ", "zzz"},
out: LabelNames{"ZZZ", "zzz"},
},
{
in: LabelNames{"aaa", "AAA"},
out: LabelNames{"AAA", "aaa"},
},
}
for i, scenario := range scenarios {
sort.Sort(scenario.in)
for j, expected := range scenario.out {
if expected != scenario.in[j] {
t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j])
}
}
}
}
func TestLabelNames(t *testing.T) {
testLabelNames(t)
}
func BenchmarkLabelNames(b *testing.B) {
for i := 0; i < b.N; i++ {
testLabelNames(b)
}
}
func testLabelValues(t testing.TB) {
var scenarios = []struct {
in LabelValues
out LabelValues
}{
{
in: LabelValues{"ZZZ", "zzz"},
out: LabelValues{"ZZZ", "zzz"},
},
{
in: LabelValues{"aaa", "AAA"},
out: LabelValues{"AAA", "aaa"},
},
}
for i, scenario := range scenarios {
sort.Sort(scenario.in)
for j, expected := range scenario.out {
if expected != scenario.in[j] {
t.Errorf("%d.%d expected %s, got %s", i, j, expected, scenario.in[j])
}
}
}
}
func TestLabelValues(t *testing.T) {
testLabelValues(t)
}
func BenchmarkLabelValues(b *testing.B) {
for i := 0; i < b.N; i++ {
testLabelValues(b)
}
}
func TestLabelNameIsValid(t *testing.T) {
var scenarios = []struct {
ln LabelName
valid bool
}{
{
ln: "Avalid_23name",
valid: true,
},
{
ln: "_Avalid_23name",
valid: true,
},
{
ln: "1valid_23name",
valid: false,
},
{
ln: "avalid_23name",
valid: true,
},
{
ln: "Ava:lid_23name",
valid: false,
},
{
ln: "a lid_23name",
valid: false,
},
{
ln: ":leading_colon",
valid: false,
},
{
ln: "colon:in:the:middle",
valid: false,
},
}
for _, s := range scenarios {
if s.ln.IsValid() != s.valid {
t.Errorf("Expected %v for %q using IsValid method", s.valid, s.ln)
}
if LabelNameRE.MatchString(string(s.ln)) != s.valid {
t.Errorf("Expected %v for %q using regexp match", s.valid, s.ln)
}
}
}

View file

@ -1,132 +0,0 @@
// 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 model
import "testing"
func testMetric(t testing.TB) {
var scenarios = []struct {
input LabelSet
fingerprint Fingerprint
fastFingerprint Fingerprint
}{
{
input: LabelSet{},
fingerprint: 14695981039346656037,
fastFingerprint: 14695981039346656037,
},
{
input: LabelSet{
"first_name": "electro",
"occupation": "robot",
"manufacturer": "westinghouse",
},
fingerprint: 5911716720268894962,
fastFingerprint: 11310079640881077873,
},
{
input: LabelSet{
"x": "y",
},
fingerprint: 8241431561484471700,
fastFingerprint: 13948396922932177635,
},
{
input: LabelSet{
"a": "bb",
"b": "c",
},
fingerprint: 3016285359649981711,
fastFingerprint: 3198632812309449502,
},
{
input: LabelSet{
"a": "b",
"bb": "c",
},
fingerprint: 7122421792099404749,
fastFingerprint: 5774953389407657638,
},
}
for i, scenario := range scenarios {
input := Metric(scenario.input)
if scenario.fingerprint != input.Fingerprint() {
t.Errorf("%d. expected %d, got %d", i, scenario.fingerprint, input.Fingerprint())
}
if scenario.fastFingerprint != input.FastFingerprint() {
t.Errorf("%d. expected %d, got %d", i, scenario.fastFingerprint, input.FastFingerprint())
}
}
}
func TestMetric(t *testing.T) {
testMetric(t)
}
func BenchmarkMetric(b *testing.B) {
for i := 0; i < b.N; i++ {
testMetric(b)
}
}
func TestMetricNameIsValid(t *testing.T) {
var scenarios = []struct {
mn LabelValue
valid bool
}{
{
mn: "Avalid_23name",
valid: true,
},
{
mn: "_Avalid_23name",
valid: true,
},
{
mn: "1valid_23name",
valid: false,
},
{
mn: "avalid_23name",
valid: true,
},
{
mn: "Ava:lid_23name",
valid: true,
},
{
mn: "a lid_23name",
valid: false,
},
{
mn: ":leading_colon",
valid: true,
},
{
mn: "colon:in:the:middle",
valid: true,
},
}
for _, s := range scenarios {
if IsValidMetricName(s.mn) != s.valid {
t.Errorf("Expected %v for %q using IsValidMetricName function", s.valid, s.mn)
}
if MetricNameRE.MatchString(string(s.mn)) != s.valid {
t.Errorf("Expected %v for %q using regexp matching", s.valid, s.mn)
}
}
}

View file

@ -1,314 +0,0 @@
// Copyright 2014 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 model
import (
"runtime"
"sync"
"testing"
)
func TestLabelsToSignature(t *testing.T) {
var scenarios = []struct {
in map[string]string
out uint64
}{
{
in: map[string]string{},
out: 14695981039346656037,
},
{
in: map[string]string{"name": "garland, briggs", "fear": "love is not enough"},
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := LabelsToSignature(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestMetricToFingerprint(t *testing.T) {
var scenarios = []struct {
in LabelSet
out Fingerprint
}{
{
in: LabelSet{},
out: 14695981039346656037,
},
{
in: LabelSet{"name": "garland, briggs", "fear": "love is not enough"},
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := labelSetToFingerprint(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestMetricToFastFingerprint(t *testing.T) {
var scenarios = []struct {
in LabelSet
out Fingerprint
}{
{
in: LabelSet{},
out: 14695981039346656037,
},
{
in: LabelSet{"name": "garland, briggs", "fear": "love is not enough"},
out: 12952432476264840823,
},
}
for i, scenario := range scenarios {
actual := labelSetToFastFingerprint(scenario.in)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestSignatureForLabels(t *testing.T) {
var scenarios = []struct {
in Metric
labels LabelNames
out uint64
}{
{
in: Metric{},
labels: nil,
out: 14695981039346656037,
},
{
in: Metric{},
labels: LabelNames{"empty"},
out: 7187873163539638612,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: LabelNames{"empty"},
out: 7187873163539638612,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: LabelNames{"fear", "name"},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"},
labels: LabelNames{"fear", "name"},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: LabelNames{},
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: nil,
out: 14695981039346656037,
},
}
for i, scenario := range scenarios {
actual := SignatureForLabels(scenario.in, scenario.labels...)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func TestSignatureWithoutLabels(t *testing.T) {
var scenarios = []struct {
in Metric
labels map[LabelName]struct{}
out uint64
}{
{
in: Metric{},
labels: nil,
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: map[LabelName]struct{}{"fear": struct{}{}, "name": struct{}{}},
out: 14695981039346656037,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough", "foo": "bar"},
labels: map[LabelName]struct{}{"foo": struct{}{}},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: map[LabelName]struct{}{},
out: 5799056148416392346,
},
{
in: Metric{"name": "garland, briggs", "fear": "love is not enough"},
labels: nil,
out: 5799056148416392346,
},
}
for i, scenario := range scenarios {
actual := SignatureWithoutLabels(scenario.in, scenario.labels)
if actual != scenario.out {
t.Errorf("%d. expected %d, got %d", i, scenario.out, actual)
}
}
}
func benchmarkLabelToSignature(b *testing.B, l map[string]string, e uint64) {
for i := 0; i < b.N; i++ {
if a := LabelsToSignature(l); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, l, a)
}
}
}
func BenchmarkLabelToSignatureScalar(b *testing.B) {
benchmarkLabelToSignature(b, nil, 14695981039346656037)
}
func BenchmarkLabelToSignatureSingle(b *testing.B) {
benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value"}, 5146282821936882169)
}
func BenchmarkLabelToSignatureDouble(b *testing.B) {
benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value"}, 3195800080984914717)
}
func BenchmarkLabelToSignatureTriple(b *testing.B) {
benchmarkLabelToSignature(b, map[string]string{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 13843036195897128121)
}
func benchmarkMetricToFingerprint(b *testing.B, ls LabelSet, e Fingerprint) {
for i := 0; i < b.N; i++ {
if a := labelSetToFingerprint(ls); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, ls, a)
}
}
}
func BenchmarkMetricToFingerprintScalar(b *testing.B) {
benchmarkMetricToFingerprint(b, nil, 14695981039346656037)
}
func BenchmarkMetricToFingerprintSingle(b *testing.B) {
benchmarkMetricToFingerprint(b, LabelSet{"first-label": "first-label-value"}, 5146282821936882169)
}
func BenchmarkMetricToFingerprintDouble(b *testing.B) {
benchmarkMetricToFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value"}, 3195800080984914717)
}
func BenchmarkMetricToFingerprintTriple(b *testing.B) {
benchmarkMetricToFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 13843036195897128121)
}
func benchmarkMetricToFastFingerprint(b *testing.B, ls LabelSet, e Fingerprint) {
for i := 0; i < b.N; i++ {
if a := labelSetToFastFingerprint(ls); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, ls, a)
}
}
}
func BenchmarkMetricToFastFingerprintScalar(b *testing.B) {
benchmarkMetricToFastFingerprint(b, nil, 14695981039346656037)
}
func BenchmarkMetricToFastFingerprintSingle(b *testing.B) {
benchmarkMetricToFastFingerprint(b, LabelSet{"first-label": "first-label-value"}, 5147259542624943964)
}
func BenchmarkMetricToFastFingerprintDouble(b *testing.B) {
benchmarkMetricToFastFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value"}, 18269973311206963528)
}
func BenchmarkMetricToFastFingerprintTriple(b *testing.B) {
benchmarkMetricToFastFingerprint(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676)
}
func BenchmarkEmptyLabelSignature(b *testing.B) {
input := []map[string]string{nil, {}}
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
alloc := ms.Alloc
for _, labels := range input {
LabelsToSignature(labels)
}
runtime.ReadMemStats(&ms)
if got := ms.Alloc; alloc != got {
b.Fatal("expected LabelsToSignature with empty labels not to perform allocations")
}
}
func benchmarkMetricToFastFingerprintConc(b *testing.B, ls LabelSet, e Fingerprint, concLevel int) {
var start, end sync.WaitGroup
start.Add(1)
end.Add(concLevel)
for i := 0; i < concLevel; i++ {
go func() {
start.Wait()
for j := b.N / concLevel; j >= 0; j-- {
if a := labelSetToFastFingerprint(ls); a != e {
b.Fatalf("expected signature of %d for %s, got %d", e, ls, a)
}
}
end.Done()
}()
}
b.ResetTimer()
start.Done()
end.Wait()
}
func BenchmarkMetricToFastFingerprintTripleConc1(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 1)
}
func BenchmarkMetricToFastFingerprintTripleConc2(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 2)
}
func BenchmarkMetricToFastFingerprintTripleConc4(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 4)
}
func BenchmarkMetricToFastFingerprintTripleConc8(b *testing.B) {
benchmarkMetricToFastFingerprintConc(b, LabelSet{"first-label": "first-label-value", "second-label": "second-label-value", "third-label": "third-label-value"}, 15738406913934009676, 8)
}

View file

@ -59,8 +59,8 @@ func (m *Matcher) Validate() error {
return nil
}
// Silence defines the representation of a silence definiton
// in the Prometheus eco-system.
// Silence defines the representation of a silence definition in the Prometheus
// eco-system.
type Silence struct {
ID uint64 `json:"id,omitempty"`

View file

@ -1,228 +0,0 @@
// Copyright 2015 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 model
import (
"strings"
"testing"
"time"
)
func TestMatcherValidate(t *testing.T) {
var cases = []struct {
matcher *Matcher
err string
}{
{
matcher: &Matcher{
Name: "name",
Value: "value",
},
},
{
matcher: &Matcher{
Name: "name",
Value: "value",
IsRegex: true,
},
},
{
matcher: &Matcher{
Name: "name!",
Value: "value",
},
err: "invalid name",
},
{
matcher: &Matcher{
Name: "",
Value: "value",
},
err: "invalid name",
},
{
matcher: &Matcher{
Name: "name",
Value: "value\xff",
},
err: "invalid value",
},
{
matcher: &Matcher{
Name: "name",
Value: "",
},
err: "invalid value",
},
}
for i, c := range cases {
err := c.matcher.Validate()
if err == nil {
if c.err == "" {
continue
}
t.Errorf("%d. Expected error %q but got none", i, c.err)
continue
}
if c.err == "" && err != nil {
t.Errorf("%d. Expected no error but got %q", i, err)
continue
}
if !strings.Contains(err.Error(), c.err) {
t.Errorf("%d. Expected error to contain %q but got %q", i, c.err, err)
}
}
}
func TestSilenceValidate(t *testing.T) {
ts := time.Now()
var cases = []struct {
sil *Silence
err string
}{
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
StartsAt: ts,
EndsAt: ts,
CreatedAt: ts,
CreatedBy: "name",
Comment: "comment",
},
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
{Name: "name", Value: "value"},
{Name: "name", Value: "value"},
{Name: "name", Value: "value", IsRegex: true},
},
StartsAt: ts,
EndsAt: ts,
CreatedAt: ts,
CreatedBy: "name",
Comment: "comment",
},
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
StartsAt: ts,
EndsAt: ts.Add(-1 * time.Minute),
CreatedAt: ts,
CreatedBy: "name",
Comment: "comment",
},
err: "start time must be before end time",
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
StartsAt: ts,
CreatedAt: ts,
CreatedBy: "name",
Comment: "comment",
},
err: "end time missing",
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
EndsAt: ts,
CreatedAt: ts,
CreatedBy: "name",
Comment: "comment",
},
err: "start time missing",
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "!name", Value: "value"},
},
StartsAt: ts,
EndsAt: ts,
CreatedAt: ts,
CreatedBy: "name",
Comment: "comment",
},
err: "invalid matcher",
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
StartsAt: ts,
EndsAt: ts,
CreatedAt: ts,
CreatedBy: "name",
},
err: "comment missing",
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
StartsAt: ts,
EndsAt: ts,
CreatedBy: "name",
Comment: "comment",
},
err: "creation timestamp missing",
},
{
sil: &Silence{
Matchers: []*Matcher{
{Name: "name", Value: "value"},
},
StartsAt: ts,
EndsAt: ts,
CreatedAt: ts,
Comment: "comment",
},
err: "creator information missing",
},
}
for i, c := range cases {
err := c.sil.Validate()
if err == nil {
if c.err == "" {
continue
}
t.Errorf("%d. Expected error %q but got none", i, c.err)
continue
}
if c.err == "" && err != nil {
t.Errorf("%d. Expected no error but got %q", i, err)
continue
}
if !strings.Contains(err.Error(), c.err) {
t.Errorf("%d. Expected error to contain %q but got %q", i, c.err, err)
}
}
}

View file

@ -214,6 +214,9 @@ func (d Duration) String() string {
ms = int64(time.Duration(d) / time.Millisecond)
unit = "ms"
)
if ms == 0 {
return "0s"
}
factors := map[string]int64{
"y": 1000 * 60 * 60 * 24 * 365,
"w": 1000 * 60 * 60 * 24 * 7,

View file

@ -1,129 +0,0 @@
// 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 model
import (
"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 TestParseDuration(t *testing.T) {
var cases = []struct {
in string
out time.Duration
}{
{
in: "324ms",
out: 324 * time.Millisecond,
}, {
in: "3s",
out: 3 * time.Second,
}, {
in: "5m",
out: 5 * time.Minute,
}, {
in: "1h",
out: time.Hour,
}, {
in: "4d",
out: 4 * 24 * time.Hour,
}, {
in: "3w",
out: 3 * 7 * 24 * time.Hour,
}, {
in: "10y",
out: 10 * 365 * 24 * time.Hour,
},
}
for _, c := range cases {
d, err := ParseDuration(c.in)
if err != nil {
t.Errorf("Unexpected error on input %q", c.in)
}
if time.Duration(d) != c.out {
t.Errorf("Expected %v but got %v", c.out, d)
}
if d.String() != c.in {
t.Errorf("Expected duration string %q but got %q", c.in, d.String())
}
}
}

View file

@ -100,7 +100,7 @@ func (s *SamplePair) UnmarshalJSON(b []byte) error {
}
// Equal returns true if this SamplePair and o have equal Values and equal
// Timestamps. The sematics of Value equality is defined by SampleValue.Equal.
// Timestamps. The semantics of Value equality is defined by SampleValue.Equal.
func (s *SamplePair) Equal(o *SamplePair) bool {
return s == o || (s.Value.Equal(o.Value) && s.Timestamp.Equal(o.Timestamp))
}
@ -117,7 +117,7 @@ type Sample struct {
}
// Equal compares first the metrics, then the timestamp, then the value. The
// sematics of value equality is defined by SampleValue.Equal.
// semantics of value equality is defined by SampleValue.Equal.
func (s *Sample) Equal(o *Sample) bool {
if s == o {
return true

View file

@ -1,468 +0,0 @@
// 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 model
import (
"encoding/json"
"math"
"reflect"
"sort"
"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 TestEqualSamples(t *testing.T) {
testSample := &Sample{}
tests := map[string]struct {
in1, in2 *Sample
want bool
}{
"equal pointers": {
in1: testSample,
in2: testSample,
want: true,
},
"different metrics": {
in1: &Sample{Metric: Metric{"foo": "bar"}},
in2: &Sample{Metric: Metric{"foo": "biz"}},
want: false,
},
"different timestamp": {
in1: &Sample{Timestamp: 0},
in2: &Sample{Timestamp: 1},
want: false,
},
"different value": {
in1: &Sample{Value: 0},
in2: &Sample{Value: 1},
want: false,
},
"equal samples": {
in1: &Sample{
Metric: Metric{"foo": "bar"},
Timestamp: 0,
Value: 1,
},
in2: &Sample{
Metric: Metric{"foo": "bar"},
Timestamp: 0,
Value: 1,
},
want: true,
},
}
for name, test := range tests {
got := test.in1.Equal(test.in2)
if got != test.want {
t.Errorf("Comparing %s, %v and %v: 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)
}
}
}
func TestSampleJSON(t *testing.T) {
input := []struct {
plain string
value Sample
}{
{
plain: `{"metric":{"__name__":"test_metric"},"value":[1234.567,"123.1"]}`,
value: Sample{
Metric: Metric{
MetricNameLabel: "test_metric",
},
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 sv Sample
err = json.Unmarshal(b, &sv)
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(sv, test.value) {
t.Errorf("decoding error: expected %v, got %v", test.value, sv)
}
}
}
func TestVectorJSON(t *testing.T) {
input := []struct {
plain string
value Vector
}{
{
plain: `[]`,
value: Vector{},
},
{
plain: `[{"metric":{"__name__":"test_metric"},"value":[1234.567,"123.1"]}]`,
value: Vector{&Sample{
Metric: Metric{
MetricNameLabel: "test_metric",
},
Value: 123.1,
Timestamp: 1234567,
}},
},
{
plain: `[{"metric":{"__name__":"test_metric"},"value":[1234.567,"123.1"]},{"metric":{"foo":"bar"},"value":[1.234,"+Inf"]}]`,
value: Vector{
&Sample{
Metric: Metric{
MetricNameLabel: "test_metric",
},
Value: 123.1,
Timestamp: 1234567,
},
&Sample{
Metric: Metric{
"foo": "bar",
},
Value: SampleValue(math.Inf(1)),
Timestamp: 1234,
},
},
},
}
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 vec Vector
err = json.Unmarshal(b, &vec)
if err != nil {
t.Error(err)
continue
}
if !reflect.DeepEqual(vec, test.value) {
t.Errorf("decoding error: expected %v, got %v", test.value, vec)
}
}
}
func TestScalarJSON(t *testing.T) {
input := []struct {
plain string
value Scalar
}{
{
plain: `[123.456,"456"]`,
value: Scalar{
Timestamp: 123456,
Value: 456,
},
},
{
plain: `[123123.456,"+Inf"]`,
value: Scalar{
Timestamp: 123123456,
Value: SampleValue(math.Inf(1)),
},
},
{
plain: `[123123.456,"-Inf"]`,
value: Scalar{
Timestamp: 123123456,
Value: SampleValue(math.Inf(-1)),
},
},
}
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 sv Scalar
err = json.Unmarshal(b, &sv)
if err != nil {
t.Error(err)
continue
}
if sv != test.value {
t.Errorf("decoding error: expected %v, got %v", test.value, sv)
}
}
}
func TestStringJSON(t *testing.T) {
input := []struct {
plain string
value String
}{
{
plain: `[123.456,"test"]`,
value: String{
Timestamp: 123456,
Value: "test",
},
},
{
plain: `[123123.456,"台北"]`,
value: String{
Timestamp: 123123456,
Value: "台北",
},
},
}
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 sv String
err = json.Unmarshal(b, &sv)
if err != nil {
t.Error(err)
continue
}
if sv != test.value {
t.Errorf("decoding error: expected %v, got %v", test.value, sv)
}
}
}
func TestVectorSort(t *testing.T) {
input := Vector{
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 2,
},
}
expected := Vector{
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "A",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "B",
},
Timestamp: 2,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 1,
},
&Sample{
Metric: Metric{
MetricNameLabel: "C",
},
Timestamp: 2,
},
}
sort.Sort(input)
for i, actual := range input {
actualFp := actual.Metric.Fingerprint()
expectedFp := expected[i].Metric.Fingerprint()
if actualFp != expectedFp {
t.Fatalf("%d. Incorrect fingerprint. Got %s; want %s", i, actualFp.String(), expectedFp.String())
}
if actual.Timestamp != expected[i].Timestamp {
t.Fatalf("%d. Incorrect timestamp. Got %s; want %s", i, actual.Timestamp, expected[i].Timestamp)
}
}
}

1
vendor/github.com/prometheus/procfs/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
/fixtures/

View file

@ -1,5 +1,15 @@
sudo: false
language: go
go:
- 1.7.6
- 1.8.3
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.x
go_import_path: github.com/prometheus/procfs
script:
- make style check_license vet test staticcheck

View file

@ -1,18 +1,71 @@
ci: fmt lint test
# Copyright 2018 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.
fmt:
! gofmt -l *.go | read nothing
go vet
# Ensure GOBIN is not set during build so that promu is installed to the correct path
unexport GOBIN
lint:
go get github.com/golang/lint/golint
golint *.go
GO ?= go
GOFMT ?= $(GO)fmt
FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH)))
STATICCHECK := $(FIRST_GOPATH)/bin/staticcheck
pkgs = $(shell $(GO) list ./... | grep -v /vendor/)
test: sysfs/fixtures/.unpacked
go test -v ./...
PREFIX ?= $(shell pwd)
BIN_DIR ?= $(shell pwd)
sysfs/fixtures/.unpacked: sysfs/fixtures.ttar
./ttar -C sysfs -x -f sysfs/fixtures.ttar
ifdef DEBUG
bindata_flags = -debug
endif
STATICCHECK_IGNORE =
all: format staticcheck build test
style:
@echo ">> checking code style"
@! $(GOFMT) -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^'
check_license:
@echo ">> checking license header"
@./scripts/check_license.sh
test: fixtures/.unpacked sysfs/fixtures/.unpacked
@echo ">> running all tests"
@$(GO) test -race $(shell $(GO) list ./... | grep -v /vendor/ | grep -v examples)
format:
@echo ">> formatting code"
@$(GO) fmt $(pkgs)
vet:
@echo ">> vetting code"
@$(GO) vet $(pkgs)
staticcheck: $(STATICCHECK)
@echo ">> running staticcheck"
@$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs)
%/.unpacked: %.ttar
./ttar -C $(dir $*) -x -f $*.ttar
touch $@
.PHONY: fmt lint test ci
$(FIRST_GOPATH)/bin/staticcheck:
@GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck
.PHONY: all style check_license format test vet staticcheck
# Declaring the binaries at their default locations as PHONY targets is a hack
# to ensure the latest version is downloaded on every make execution.
# If this is not desired, copy/symlink these binaries to a different path and
# set the respective environment variables.
.PHONY: $(GOPATH)/bin/staticcheck

View file

@ -62,7 +62,7 @@ func parseBuddyInfo(r io.Reader) ([]BuddyInfo, error) {
for scanner.Scan() {
var err error
line := scanner.Text()
parts := strings.Fields(string(line))
parts := strings.Fields(line)
if len(parts) < 4 {
return nil, fmt.Errorf("invalid number of fields when parsing buddyinfo")

View file

@ -1,64 +0,0 @@
// Copyright 2017 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 procfs
import (
"strings"
"testing"
)
func TestBuddyInfo(t *testing.T) {
buddyInfo, err := FS("fixtures/buddyinfo/valid").NewBuddyInfo()
if err != nil {
t.Fatal(err)
}
if want, got := "DMA", buddyInfo[0].Zone; want != got {
t.Errorf("want Node 0, Zone %s, got %s", want, got)
}
if want, got := "Normal", buddyInfo[2].Zone; want != got {
t.Errorf("want Node 0, Zone %s, got %s", want, got)
}
if want, got := 4381.0, buddyInfo[2].Sizes[0]; want != got {
t.Errorf("want Node 0, Zone Normal %f, got %f", want, got)
}
if want, got := 572.0, buddyInfo[1].Sizes[1]; want != got {
t.Errorf("want Node 0, Zone DMA32 %f, got %f", want, got)
}
}
func TestBuddyInfoShort(t *testing.T) {
_, err := FS("fixtures/buddyinfo/short").NewBuddyInfo()
if err == nil {
t.Errorf("expected error, but none occurred")
}
if want, got := "invalid number of fields when parsing buddyinfo", err.Error(); want != got {
t.Errorf("wrong error returned, wanted %q, got %q", want, got)
}
}
func TestBuddyInfoSizeMismatch(t *testing.T) {
_, err := FS("fixtures/buddyinfo/sizemismatch").NewBuddyInfo()
if err == nil {
t.Errorf("expected error, but none occurred")
}
if want, got := "mismatch in number of buddyinfo buckets", err.Error(); !strings.HasPrefix(got, want) {
t.Errorf("wrong error returned, wanted prefix %q, got %q", want, got)
}
}

446
vendor/github.com/prometheus/procfs/fixtures.ttar generated vendored Normal file
View file

@ -0,0 +1,446 @@
# Archive created by ttar -c -f fixtures.ttar fixtures/
Directory: fixtures
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26231
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/cmdline
Lines: 1
vimNULLBYTEtest.goNULLBYTE+10NULLBYTEEOF
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/comm
Lines: 1
vim
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/exe
SymlinkTo: /usr/bin/vim
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26231/fd
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/fd/0
SymlinkTo: ../../symlinktargets/abc
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/fd/1
SymlinkTo: ../../symlinktargets/def
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/fd/10
SymlinkTo: ../../symlinktargets/xyz
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/fd/2
SymlinkTo: ../../symlinktargets/ghi
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/fd/3
SymlinkTo: ../../symlinktargets/uvw
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/io
Lines: 7
rchar: 750339
wchar: 818609
syscr: 7405
syscw: 5245
read_bytes: 1024
write_bytes: 2048
cancelled_write_bytes: -1024
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/limits
Lines: 17
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 62898 62898 processes
Max open files 2048 4096 files
Max locked memory 65536 65536 bytes
Max address space 8589934592 unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 62898 62898 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/mountstats
Lines: 19
device rootfs mounted on / with fstype rootfs
device sysfs mounted on /sys with fstype sysfs
device proc mounted on /proc with fstype proc
device /dev/sda1 mounted on / with fstype ext4
device 192.168.1.1:/srv/test mounted on /mnt/nfs/test with fstype nfs4 statvers=1.1
opts: rw,vers=4.0,rsize=1048576,wsize=1048576,namlen=255,acregmin=3,acregmax=60,acdirmin=30,acdirmax=60,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.5,local_lock=none
age: 13968
caps: caps=0xfff7,wtmult=512,dtsize=32768,bsize=0,namlen=255
nfsv4: bm0=0xfdffafff,bm1=0xf9be3e,bm2=0x0,acl=0x0,pnfs=not configured
sec: flavor=1,pseudoflavor=1
events: 52 226 0 0 1 13 398 0 0 331 0 47 0 0 77 0 0 77 0 0 0 0 0 0 0 0 0
bytes: 1207640230 0 0 0 1210214218 0 295483 0
RPC iostats version: 1.0 p/v: 100003/4 (nfs)
xprt: tcp 832 0 1 0 11 6428 6428 0 12154 0 24 26 5726
per-op statistics
NULL: 0 0 0 0 0 0 0 0
READ: 1298 1298 0 207680 1210292152 6 79386 79407
WRITE: 0 0 0 0 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26231/net
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/net/dev
Lines: 4
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
lo: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
eth0: 438 5 0 0 0 0 0 0 648 8 0 0 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26231/ns
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/ns/mnt
SymlinkTo: mnt:[4026531840]
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/ns/net
SymlinkTo: net:[4026531993]
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26231/stat
Lines: 1
26231 (vim) R 5392 7446 5392 34835 7446 4218880 32533 309516 26 82 1677 44 158 99 20 0 1 0 82375 56274944 1981 18446744073709551615 4194304 6294284 140736914091744 140736914087944 139965136429984 0 0 12288 1870679807 0 0 0 17 0 0 0 31 0 0 8391624 8481048 16420864 140736914093252 140736914093279 140736914093279 140736914096107 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26232
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/cmdline
Lines: 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/comm
Lines: 1
ata_sff
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26232/fd
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/fd/0
SymlinkTo: ../../symlinktargets/abc
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/fd/1
SymlinkTo: ../../symlinktargets/def
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/fd/2
SymlinkTo: ../../symlinktargets/ghi
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/fd/3
SymlinkTo: ../../symlinktargets/uvw
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/fd/4
SymlinkTo: ../../symlinktargets/xyz
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/limits
Lines: 17
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 29436 29436 processes
Max open files 1024 4096 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 29436 29436 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26232/stat
Lines: 1
33 (ata_sff) S 2 0 0 0 -1 69238880 0 0 0 0 0 0 0 0 0 -20 1 0 5 0 0 18446744073709551615 0 0 0 0 0 0 0 2147483647 0 18446744073709551615 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/26233
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/26233/cmdline
Lines: 1
com.github.uiautomatorNULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTENULLBYTEEOF
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/584
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/584/stat
Lines: 2
1020 ((a b ) ( c d) ) R 28378 1020 28378 34842 1020 4218880 286 0 0 0 0 0 0 0 20 0 1 0 10839175 10395648 155 18446744073709551615 4194304 4238788 140736466511168 140736466511168 140609271124624 0 0 0 0 0 0 0 17 5 0 0 0 0 0 6336016 6337300 25579520 140736466515030 140736466515061 140736466515061 140736466518002 0
#!/bin/cat /proc/self/stat
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/buddyinfo
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/buddyinfo/short
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/buddyinfo/short/buddyinfo
Lines: 3
Node 0, zone
Node 0, zone
Node 0, zone
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/buddyinfo/sizemismatch
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/buddyinfo/sizemismatch/buddyinfo
Lines: 3
Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3
Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0 0
Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/buddyinfo/valid
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/buddyinfo/valid/buddyinfo
Lines: 3
Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3
Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0
Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/fs
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/fs/xfs
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/fs/xfs/stat
Lines: 23
extent_alloc 92447 97589 92448 93751
abt 0 0 0 0
blk_map 1767055 188820 184891 92447 92448 2140766 0
bmbt 0 0 0 0
dir 185039 92447 92444 136422
trans 706 944304 0
ig 185045 58807 0 126238 0 33637 22
log 2883 113448 9 17360 739
push_ail 945014 0 134260 15483 0 3940 464 159985 0 40
xstrat 92447 0
rw 107739 94045
attr 4 0 0 0
icluster 8677 7849 135802
vnodes 92601 0 0 0 92444 92444 92444 0
buf 2666287 7122 2659202 3599 2 7085 0 10297 7085
abtb2 184941 1277345 13257 13278 0 0 0 0 0 0 0 0 0 0 2746147
abtc2 345295 2416764 172637 172658 0 0 0 0 0 0 0 0 0 0 21406023
bmbt2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ibt2 343004 1358467 0 0 0 0 0 0 0 0 0 0 0 0 0
fibt2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
qm 0 0 0 0 0 0 0 0
xpc 399724544 92823103 86219234
debug 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/mdstat
Lines: 26
Personalities : [linear] [multipath] [raid0] [raid1] [raid6] [raid5] [raid4] [raid10]
md3 : active raid6 sda1[8] sdh1[7] sdg1[6] sdf1[5] sde1[11] sdd1[3] sdc1[10] sdb1[9]
5853468288 blocks super 1.2 level 6, 64k chunk, algorithm 2 [8/8] [UUUUUUUU]
md127 : active raid1 sdi2[0] sdj2[1]
312319552 blocks [2/2] [UU]
md0 : active raid1 sdk[2](S) sdi1[0] sdj1[1]
248896 blocks [2/2] [UU]
md4 : inactive raid1 sda3[0] sdb3[1]
4883648 blocks [2/2] [UU]
md6 : active raid1 sdb2[2] sda2[0]
195310144 blocks [2/1] [U_]
[=>...................] recovery = 8.5% (16775552/195310144) finish=17.0min speed=259783K/sec
md8 : active raid1 sdb1[1] sda1[0]
195310144 blocks [2/2] [UU]
[=>...................] resync = 8.5% (16775552/195310144) finish=17.0min speed=259783K/sec
md7 : active raid6 sdb1[0] sde1[3] sdd1[2] sdc1[1]
7813735424 blocks super 1.2 level 6, 512k chunk, algorithm 2 [4/3] [U_UU]
bitmap: 0/30 pages [0KB], 65536KB chunk
unused devices: <none>
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/net
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/net/dev
Lines: 6
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
vethf345468: 648 8 0 0 0 0 0 0 438 5 0 0 0 0 0 0
lo: 1664039048 1566805 0 0 0 0 0 0 1664039048 1566805 0 0 0 0 0 0
docker0: 2568 38 0 0 0 0 0 0 438 5 0 0 0 0 0 0
eth0: 874354587 1036395 0 0 0 0 0 0 563352563 732147 0 0 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/net/ip_vs
Lines: 21
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP C0A80016:0CEA wlc
-> C0A85216:0CEA Tunnel 100 248 2
-> C0A85318:0CEA Tunnel 100 248 2
-> C0A85315:0CEA Tunnel 100 248 1
TCP C0A80039:0CEA wlc
-> C0A85416:0CEA Tunnel 0 0 0
-> C0A85215:0CEA Tunnel 100 1499 0
-> C0A83215:0CEA Tunnel 100 1498 0
TCP C0A80037:0CEA wlc
-> C0A8321A:0CEA Tunnel 0 0 0
-> C0A83120:0CEA Tunnel 100 0 0
TCP [2620:0000:0000:0000:0000:0000:0000:0001]:0050 sh
-> [2620:0000:0000:0000:0000:0000:0000:0002]:0050 Route 1 0 0
-> [2620:0000:0000:0000:0000:0000:0000:0003]:0050 Route 1 0 0
-> [2620:0000:0000:0000:0000:0000:0000:0004]:0050 Route 1 1 1
FWM 10001000 wlc
-> C0A8321A:0CEA Route 0 0 1
-> C0A83215:0CEA Route 0 0 2
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/net/ip_vs_stats
Lines: 6
Total Incoming Outgoing Incoming Outgoing
Conns Packets Packets Bytes Bytes
16AA370 E33656E5 0 51D8C8883AB3 0
Conns/s Pkts/s Pkts/s Bytes/s Bytes/s
4 1FB3C 0 1282A8F 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/net/rpc
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/net/rpc/nfs
Lines: 5
net 18628 0 18628 6
rpc 4329785 0 4338291
proc2 18 2 69 0 0 4410 0 0 0 0 0 0 0 0 0 0 0 99 2
proc3 22 1 4084749 29200 94754 32580 186 47747 7981 8639 0 6356 0 6962 0 7958 0 0 241 4 4 2 39
proc4 61 1 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/net/rpc/nfsd
Lines: 11
rc 0 6 18622
fh 0 0 0 0 0
io 157286400 0
th 8 0 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
ra 32 0 0 0 0 0 0 0 0 0 0 0
net 18628 0 18628 6
rpc 18628 0 0 0 0
proc2 18 2 69 0 0 4410 0 0 0 0 0 0 0 0 0 0 0 99 2
proc3 22 2 112 0 2719 111 0 0 0 0 0 0 0 0 0 0 0 27 216 0 2 1 0
proc4 2 2 10853
proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150 1272 0 0 0 1236 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/net/xfrm_stat
Lines: 28
XfrmInError 1
XfrmInBufferError 2
XfrmInHdrError 4
XfrmInNoStates 3
XfrmInStateProtoError 40
XfrmInStateModeError 100
XfrmInStateSeqError 6000
XfrmInStateExpired 4
XfrmInStateMismatch 23451
XfrmInStateInvalid 55555
XfrmInTmplMismatch 51
XfrmInNoPols 65432
XfrmInPolBlock 100
XfrmInPolError 10000
XfrmOutError 1000000
XfrmOutBundleGenError 43321
XfrmOutBundleCheckError 555
XfrmOutNoStates 869
XfrmOutStateProtoError 4542
XfrmOutStateModeError 4
XfrmOutStateSeqError 543
XfrmOutStateExpired 565
XfrmOutPolBlock 43456
XfrmOutPolDead 7656
XfrmOutPolError 1454
XfrmFwdHdrError 6654
XfrmOutStateInvalid 28765
XfrmAcquireError 24532
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/self
SymlinkTo: 26231
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/stat
Lines: 16
cpu 301854 612 111922 8979004 3552 2 3944 0 0 0
cpu0 44490 19 21045 1087069 220 1 3410 0 0 0
cpu1 47869 23 16474 1110787 591 0 46 0 0 0
cpu2 46504 36 15916 1112321 441 0 326 0 0 0
cpu3 47054 102 15683 1113230 533 0 60 0 0 0
cpu4 28413 25 10776 1140321 217 0 8 0 0 0
cpu5 29271 101 11586 1136270 672 0 30 0 0 0
cpu6 29152 36 10276 1139721 319 0 29 0 0 0
cpu7 29098 268 10164 1139282 555 0 31 0 0 0
intr 8885917 17 0 0 0 0 0 0 0 1 79281 0 0 0 0 0 0 0 231237 0 0 0 0 250586 103 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 223424 190745 13 906 1283803 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 38014093
btime 1418183276
processes 26442
procs_running 2
procs_blocked 1
softirq 5057579 250191 1481983 1647 211099 186066 0 1783454 622196 12499 508444
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/symlinktargets
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/symlinktargets/README
Lines: 2
This directory contains some empty files that are the symlinks the files in the "fd" directory point to.
They are otherwise ignored by the tests
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/symlinktargets/abc
Lines: 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/symlinktargets/def
Lines: 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/symlinktargets/ghi
Lines: 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/symlinktargets/uvw
Lines: 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/symlinktargets/xyz
Lines: 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (
@ -5,6 +18,7 @@ import (
"os"
"path"
"github.com/prometheus/procfs/nfs"
"github.com/prometheus/procfs/xfs"
)
@ -44,3 +58,25 @@ func (fs FS) XFSStats() (*xfs.Stats, error) {
return xfs.ParseStats(f)
}
// NFSClientRPCStats retrieves NFS client RPC statistics.
func (fs FS) NFSClientRPCStats() (*nfs.ClientRPCStats, error) {
f, err := os.Open(fs.Path("net/rpc/nfs"))
if err != nil {
return nil, err
}
defer f.Close()
return nfs.ParseClientRPCStats(f)
}
// NFSdServerRPCStats retrieves NFS daemon RPC statistics.
func (fs FS) NFSdServerRPCStats() (*nfs.ServerRPCStats, error) {
f, err := os.Open(fs.Path("net/rpc/nfsd"))
if err != nil {
return nil, err
}
defer f.Close()
return nfs.ParseServerRPCStats(f)
}

View file

@ -1,26 +0,0 @@
package procfs
import "testing"
func TestNewFS(t *testing.T) {
if _, err := NewFS("foobar"); err == nil {
t.Error("want NewFS to fail for non-existing mount point")
}
if _, err := NewFS("procfs.go"); err == nil {
t.Error("want NewFS to fail if mount point is not a directory")
}
}
func TestFSXFSStats(t *testing.T) {
stats, err := FS("fixtures").XFSStats()
if err != nil {
t.Fatalf("failed to parse XFS stats: %v", err)
}
// Very lightweight test just to sanity check the path used
// to open XFS stats. Heavier tests in package xfs.
if want, got := uint32(92447), stats.ExtentAllocation.ExtentsAllocated; want != got {
t.Errorf("unexpected extents allocated:\nwant: %d\nhave: %d", want, got)
}
}

View file

@ -0,0 +1,46 @@
// Copyright 2018 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 util
import "strconv"
// ParseUint32s parses a slice of strings into a slice of uint32s.
func ParseUint32s(ss []string) ([]uint32, error) {
us := make([]uint32, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return nil, err
}
us = append(us, uint32(u))
}
return us, nil
}
// ParseUint64s parses a slice of strings into a slice of uint64s.
func ParseUint64s(ss []string) ([]uint64, error) {
us := make([]uint64, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, err
}
us = append(us, u)
}
return us, nil
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (
@ -31,16 +44,16 @@ type IPVSStats struct {
type IPVSBackendStatus struct {
// The local (virtual) IP address.
LocalAddress net.IP
// The remote (real) IP address.
RemoteAddress net.IP
// The local (virtual) port.
LocalPort uint16
// The remote (real) port.
RemotePort uint16
// The local firewall mark
LocalMark string
// The transport protocol (TCP, UDP).
Proto string
// The remote (real) IP address.
RemoteAddress net.IP
// The remote (real) port.
RemotePort uint16
// The current number of active connections for this virtual/real address pair.
ActiveConn uint64
// The current number of inactive connections for this virtual/real address pair.
@ -151,7 +164,7 @@ func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
)
for scanner.Scan() {
fields := strings.Fields(string(scanner.Text()))
fields := strings.Fields(scanner.Text())
if len(fields) == 0 {
continue
}

View file

@ -1,237 +0,0 @@
package procfs
import (
"net"
"testing"
)
var (
expectedIPVSStats = IPVSStats{
Connections: 23765872,
IncomingPackets: 3811989221,
OutgoingPackets: 0,
IncomingBytes: 89991519156915,
OutgoingBytes: 0,
}
expectedIPVSBackendStatuses = []IPVSBackendStatus{
{
LocalAddress: net.ParseIP("192.168.0.22"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.82.22"),
RemotePort: 3306,
Proto: "TCP",
Weight: 100,
ActiveConn: 248,
InactConn: 2,
},
{
LocalAddress: net.ParseIP("192.168.0.22"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.83.24"),
RemotePort: 3306,
Proto: "TCP",
Weight: 100,
ActiveConn: 248,
InactConn: 2,
},
{
LocalAddress: net.ParseIP("192.168.0.22"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.83.21"),
RemotePort: 3306,
Proto: "TCP",
Weight: 100,
ActiveConn: 248,
InactConn: 1,
},
{
LocalAddress: net.ParseIP("192.168.0.57"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.84.22"),
RemotePort: 3306,
Proto: "TCP",
Weight: 0,
ActiveConn: 0,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("192.168.0.57"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.82.21"),
RemotePort: 3306,
Proto: "TCP",
Weight: 100,
ActiveConn: 1499,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("192.168.0.57"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.50.21"),
RemotePort: 3306,
Proto: "TCP",
Weight: 100,
ActiveConn: 1498,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("192.168.0.55"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.50.26"),
RemotePort: 3306,
Proto: "TCP",
Weight: 0,
ActiveConn: 0,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("192.168.0.55"),
LocalPort: 3306,
RemoteAddress: net.ParseIP("192.168.49.32"),
RemotePort: 3306,
Proto: "TCP",
Weight: 100,
ActiveConn: 0,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("2620::1"),
LocalPort: 80,
RemoteAddress: net.ParseIP("2620::2"),
RemotePort: 80,
Proto: "TCP",
Weight: 1,
ActiveConn: 0,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("2620::1"),
LocalPort: 80,
RemoteAddress: net.ParseIP("2620::3"),
RemotePort: 80,
Proto: "TCP",
Weight: 1,
ActiveConn: 0,
InactConn: 0,
},
{
LocalAddress: net.ParseIP("2620::1"),
LocalPort: 80,
RemoteAddress: net.ParseIP("2620::4"),
RemotePort: 80,
Proto: "TCP",
Weight: 1,
ActiveConn: 1,
InactConn: 1,
},
{
LocalMark: "10001000",
RemoteAddress: net.ParseIP("192.168.50.26"),
RemotePort: 3306,
Proto: "FWM",
Weight: 0,
ActiveConn: 0,
InactConn: 1,
},
{
LocalMark: "10001000",
RemoteAddress: net.ParseIP("192.168.50.21"),
RemotePort: 3306,
Proto: "FWM",
Weight: 0,
ActiveConn: 0,
InactConn: 2,
},
}
)
func TestIPVSStats(t *testing.T) {
stats, err := FS("fixtures").NewIPVSStats()
if err != nil {
t.Fatal(err)
}
if stats != expectedIPVSStats {
t.Errorf("want %+v, have %+v", expectedIPVSStats, stats)
}
}
func TestParseIPPort(t *testing.T) {
ip := net.ParseIP("192.168.0.22")
port := uint16(3306)
gotIP, gotPort, err := parseIPPort("C0A80016:0CEA")
if err != nil {
t.Fatal(err)
}
if !(gotIP.Equal(ip) && port == gotPort) {
t.Errorf("want %s:%d, have %s:%d", ip, port, gotIP, gotPort)
}
}
func TestParseIPPortInvalid(t *testing.T) {
testcases := []string{
"",
"C0A80016",
"C0A800:1234",
"FOOBARBA:1234",
"C0A80016:0CEA:1234",
}
for _, s := range testcases {
ip, port, err := parseIPPort(s)
if ip != nil || port != uint16(0) || err == nil {
t.Errorf("Expected error for input %s, have ip = %s, port = %v, err = %v", s, ip, port, err)
}
}
}
func TestParseIPPortIPv6(t *testing.T) {
ip := net.ParseIP("dead:beef::1")
port := uint16(8080)
gotIP, gotPort, err := parseIPPort("[DEAD:BEEF:0000:0000:0000:0000:0000:0001]:1F90")
if err != nil {
t.Fatal(err)
}
if !(gotIP.Equal(ip) && port == gotPort) {
t.Errorf("want %s:%d, have %s:%d", ip, port, gotIP, gotPort)
}
}
func TestIPVSBackendStatus(t *testing.T) {
backendStats, err := FS("fixtures").NewIPVSBackendStatus()
if err != nil {
t.Fatal(err)
}
if want, have := len(expectedIPVSBackendStatuses), len(backendStats); want != have {
t.Fatalf("want %d backend statuses, have %d", want, have)
}
for idx, expect := range expectedIPVSBackendStatuses {
if !backendStats[idx].LocalAddress.Equal(expect.LocalAddress) {
t.Errorf("want LocalAddress %s, have %s", expect.LocalAddress, backendStats[idx].LocalAddress)
}
if backendStats[idx].LocalPort != expect.LocalPort {
t.Errorf("want LocalPort %d, have %d", expect.LocalPort, backendStats[idx].LocalPort)
}
if !backendStats[idx].RemoteAddress.Equal(expect.RemoteAddress) {
t.Errorf("want RemoteAddress %s, have %s", expect.RemoteAddress, backendStats[idx].RemoteAddress)
}
if backendStats[idx].RemotePort != expect.RemotePort {
t.Errorf("want RemotePort %d, have %d", expect.RemotePort, backendStats[idx].RemotePort)
}
if backendStats[idx].Proto != expect.Proto {
t.Errorf("want Proto %s, have %s", expect.Proto, backendStats[idx].Proto)
}
if backendStats[idx].Weight != expect.Weight {
t.Errorf("want Weight %d, have %d", expect.Weight, backendStats[idx].Weight)
}
if backendStats[idx].ActiveConn != expect.ActiveConn {
t.Errorf("want ActiveConn %d, have %d", expect.ActiveConn, backendStats[idx].ActiveConn)
}
if backendStats[idx].InactConn != expect.InactConn {
t.Errorf("want InactConn %d, have %d", expect.InactConn, backendStats[idx].InactConn)
}
}
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (

View file

@ -1,31 +0,0 @@
package procfs
import (
"testing"
)
func TestMDStat(t *testing.T) {
mdStates, err := FS("fixtures").ParseMDStat()
if err != nil {
t.Fatalf("parsing of reference-file failed entirely: %s", err)
}
refs := map[string]MDStat{
"md3": {"md3", "active", 8, 8, 5853468288, 5853468288},
"md127": {"md127", "active", 2, 2, 312319552, 312319552},
"md0": {"md0", "active", 2, 2, 248896, 248896},
"md4": {"md4", "inactive", 2, 2, 4883648, 4883648},
"md6": {"md6", "active", 1, 2, 195310144, 16775552},
"md8": {"md8", "active", 2, 2, 195310144, 16775552},
"md7": {"md7", "active", 3, 4, 7813735424, 7813735424},
}
if want, have := len(refs), len(mdStates); want != have {
t.Errorf("want %d parsed md-devices, have %d", want, have)
}
for _, md := range mdStates {
if want, have := refs[md.Name], md; want != have {
t.Errorf("%s: want %v, have %v", md.Name, want, have)
}
}
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
// While implementing parsing of /proc/[pid]/mountstats, this blog was used

View file

@ -1,273 +0,0 @@
package procfs
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
)
func TestMountStats(t *testing.T) {
tests := []struct {
name string
s string
mounts []*Mount
invalid bool
}{
{
name: "no devices",
s: `hello`,
},
{
name: "device has too few fields",
s: `device foo`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs BAD on / with fstype rootfs`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs mounted BAD / with fstype rootfs`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs mounted on / BAD fstype rootfs`,
invalid: true,
},
{
name: "device incorrect format",
s: `device rootfs mounted on / with BAD rootfs`,
invalid: true,
},
{
name: "device rootfs cannot have stats",
s: `device rootfs mounted on / with fstype rootfs stats`,
invalid: true,
},
{
name: "NFSv4 device with too little info",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nhello",
invalid: true,
},
{
name: "NFSv4 device with bad bytes",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nbytes: 0",
invalid: true,
},
{
name: "NFSv4 device with bad events",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nevents: 0",
invalid: true,
},
{
name: "NFSv4 device with bad per-op stats",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nper-op statistics\nFOO 0",
invalid: true,
},
{
name: "NFSv4 device with bad transport stats",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp",
invalid: true,
},
{
name: "NFSv4 device with bad transport version",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=foo\nxprt: tcp 0",
invalid: true,
},
{
name: "NFSv4 device with bad transport stats version 1.0",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.0\nxprt: tcp 0 0 0 0 0 0 0 0 0 0 0 0 0",
invalid: true,
},
{
name: "NFSv4 device with bad transport stats version 1.1",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs4 statvers=1.1\nxprt: tcp 0 0 0 0 0 0 0 0 0 0",
invalid: true,
},
{
name: "NFSv3 device with transport stats version 1.0 OK",
s: "device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.0\nxprt: tcp 1 2 3 4 5 6 7 8 9 10",
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.0",
Transport: NFSTransportStats{
Port: 1,
Bind: 2,
Connect: 3,
ConnectIdleTime: 4,
IdleTime: 5 * time.Second,
Sends: 6,
Receives: 7,
BadTransactionIDs: 8,
CumulativeActiveRequests: 9,
CumulativeBacklog: 10,
},
},
}},
},
{
name: "device rootfs OK",
s: `device rootfs mounted on / with fstype rootfs`,
mounts: []*Mount{{
Device: "rootfs",
Mount: "/",
Type: "rootfs",
}},
},
{
name: "NFSv3 device with minimal stats OK",
s: `device 192.168.1.1:/srv mounted on /mnt/nfs with fstype nfs statvers=1.1`,
mounts: []*Mount{{
Device: "192.168.1.1:/srv",
Mount: "/mnt/nfs",
Type: "nfs",
Stats: &MountStatsNFS{
StatVersion: "1.1",
},
}},
},
{
name: "fixtures OK",
mounts: []*Mount{
{
Device: "rootfs",
Mount: "/",
Type: "rootfs",
},
{
Device: "sysfs",
Mount: "/sys",
Type: "sysfs",
},
{
Device: "proc",
Mount: "/proc",
Type: "proc",
},
{
Device: "/dev/sda1",
Mount: "/",
Type: "ext4",
},
{
Device: "192.168.1.1:/srv/test",
Mount: "/mnt/nfs/test",
Type: "nfs4",
Stats: &MountStatsNFS{
StatVersion: "1.1",
Age: 13968 * time.Second,
Bytes: NFSBytesStats{
Read: 1207640230,
ReadTotal: 1210214218,
ReadPages: 295483,
},
Events: NFSEventsStats{
InodeRevalidate: 52,
DnodeRevalidate: 226,
VFSOpen: 1,
VFSLookup: 13,
VFSAccess: 398,
VFSReadPages: 331,
VFSWritePages: 47,
VFSFlush: 77,
VFSFileRelease: 77,
},
Operations: []NFSOperationStats{
{
Operation: "NULL",
},
{
Operation: "READ",
Requests: 1298,
Transmissions: 1298,
BytesSent: 207680,
BytesReceived: 1210292152,
CumulativeQueueTime: 6 * time.Millisecond,
CumulativeTotalResponseTime: 79386 * time.Millisecond,
CumulativeTotalRequestTime: 79407 * time.Millisecond,
},
{
Operation: "WRITE",
},
},
Transport: NFSTransportStats{
Port: 832,
Connect: 1,
IdleTime: 11 * time.Second,
Sends: 6428,
Receives: 6428,
CumulativeActiveRequests: 12154,
MaximumRPCSlotsUsed: 24,
CumulativeSendingQueue: 26,
CumulativePendingQueue: 5726,
},
},
},
},
},
}
for i, tt := range tests {
t.Logf("[%02d] test %q", i, tt.name)
var mounts []*Mount
var err error
if tt.s != "" {
mounts, err = parseMountStats(strings.NewReader(tt.s))
} else {
proc, e := FS("fixtures").NewProc(26231)
if e != nil {
t.Fatalf("failed to create proc: %v", err)
}
mounts, err = proc.MountStats()
}
if tt.invalid && err == nil {
t.Error("expected an error, but none occurred")
}
if !tt.invalid && err != nil {
t.Errorf("unexpected error: %v", err)
}
if want, have := tt.mounts, mounts; !reflect.DeepEqual(want, have) {
t.Errorf("mounts:\nwant:\n%v\nhave:\n%v", mountsStr(want), mountsStr(have))
}
}
}
func mountsStr(mounts []*Mount) string {
var out string
for i, m := range mounts {
out += fmt.Sprintf("[%d] %q on %q (%q)", i, m.Device, m.Mount, m.Type)
stats, ok := m.Stats.(*MountStatsNFS)
if !ok {
out += "\n"
continue
}
out += fmt.Sprintf("\n\t- v%s, age: %s", stats.StatVersion, stats.Age)
out += fmt.Sprintf("\n\t- bytes: %v", stats.Bytes)
out += fmt.Sprintf("\n\t- events: %v", stats.Events)
out += fmt.Sprintf("\n\t- transport: %v", stats.Transport)
out += fmt.Sprintf("\n\t- per-operation stats:")
for _, o := range stats.Operations {
out += fmt.Sprintf("\n\t\t- %v", o)
}
out += "\n"
}
return out
}

216
vendor/github.com/prometheus/procfs/net_dev.go generated vendored Normal file
View file

@ -0,0 +1,216 @@
// Copyright 2018 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 procfs
import (
"bufio"
"errors"
"os"
"sort"
"strconv"
"strings"
)
// NetDevLine is single line parsed from /proc/net/dev or /proc/[pid]/net/dev.
type NetDevLine struct {
Name string `json:"name"` // The name of the interface.
RxBytes uint64 `json:"rx_bytes"` // Cumulative count of bytes received.
RxPackets uint64 `json:"rx_packets"` // Cumulative count of packets received.
RxErrors uint64 `json:"rx_errors"` // Cumulative count of receive errors encountered.
RxDropped uint64 `json:"rx_dropped"` // Cumulative count of packets dropped while receiving.
RxFIFO uint64 `json:"rx_fifo"` // Cumulative count of FIFO buffer errors.
RxFrame uint64 `json:"rx_frame"` // Cumulative count of packet framing errors.
RxCompressed uint64 `json:"rx_compressed"` // Cumulative count of compressed packets received by the device driver.
RxMulticast uint64 `json:"rx_multicast"` // Cumulative count of multicast frames received by the device driver.
TxBytes uint64 `json:"tx_bytes"` // Cumulative count of bytes transmitted.
TxPackets uint64 `json:"tx_packets"` // Cumulative count of packets transmitted.
TxErrors uint64 `json:"tx_errors"` // Cumulative count of transmit errors encountered.
TxDropped uint64 `json:"tx_dropped"` // Cumulative count of packets dropped while transmitting.
TxFIFO uint64 `json:"tx_fifo"` // Cumulative count of FIFO buffer errors.
TxCollisions uint64 `json:"tx_collisions"` // Cumulative count of collisions detected on the interface.
TxCarrier uint64 `json:"tx_carrier"` // Cumulative count of carrier losses detected by the device driver.
TxCompressed uint64 `json:"tx_compressed"` // Cumulative count of compressed packets transmitted by the device driver.
}
// NetDev is parsed from /proc/net/dev or /proc/[pid]/net/dev. The map keys
// are interface names.
type NetDev map[string]NetDevLine
// NewNetDev returns kernel/system statistics read from /proc/net/dev.
func NewNetDev() (NetDev, error) {
fs, err := NewFS(DefaultMountPoint)
if err != nil {
return nil, err
}
return fs.NewNetDev()
}
// NewNetDev returns kernel/system statistics read from /proc/net/dev.
func (fs FS) NewNetDev() (NetDev, error) {
return newNetDev(fs.Path("net/dev"))
}
// NewNetDev returns kernel/system statistics read from /proc/[pid]/net/dev.
func (p Proc) NewNetDev() (NetDev, error) {
return newNetDev(p.path("net/dev"))
}
// newNetDev creates a new NetDev from the contents of the given file.
func newNetDev(file string) (NetDev, error) {
f, err := os.Open(file)
if err != nil {
return NetDev{}, err
}
defer f.Close()
nd := NetDev{}
s := bufio.NewScanner(f)
for n := 0; s.Scan(); n++ {
// Skip the 2 header lines.
if n < 2 {
continue
}
line, err := nd.parseLine(s.Text())
if err != nil {
return nd, err
}
nd[line.Name] = *line
}
return nd, s.Err()
}
// parseLine parses a single line from the /proc/net/dev file. Header lines
// must be filtered prior to calling this method.
func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
parts := strings.SplitN(rawLine, ":", 2)
if len(parts) != 2 {
return nil, errors.New("invalid net/dev line, missing colon")
}
fields := strings.Fields(strings.TrimSpace(parts[1]))
var err error
line := &NetDevLine{}
// Interface Name
line.Name = strings.TrimSpace(parts[0])
if line.Name == "" {
return nil, errors.New("invalid net/dev line, empty interface name")
}
// RX
line.RxBytes, err = strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return nil, err
}
line.RxPackets, err = strconv.ParseUint(fields[1], 10, 64)
if err != nil {
return nil, err
}
line.RxErrors, err = strconv.ParseUint(fields[2], 10, 64)
if err != nil {
return nil, err
}
line.RxDropped, err = strconv.ParseUint(fields[3], 10, 64)
if err != nil {
return nil, err
}
line.RxFIFO, err = strconv.ParseUint(fields[4], 10, 64)
if err != nil {
return nil, err
}
line.RxFrame, err = strconv.ParseUint(fields[5], 10, 64)
if err != nil {
return nil, err
}
line.RxCompressed, err = strconv.ParseUint(fields[6], 10, 64)
if err != nil {
return nil, err
}
line.RxMulticast, err = strconv.ParseUint(fields[7], 10, 64)
if err != nil {
return nil, err
}
// TX
line.TxBytes, err = strconv.ParseUint(fields[8], 10, 64)
if err != nil {
return nil, err
}
line.TxPackets, err = strconv.ParseUint(fields[9], 10, 64)
if err != nil {
return nil, err
}
line.TxErrors, err = strconv.ParseUint(fields[10], 10, 64)
if err != nil {
return nil, err
}
line.TxDropped, err = strconv.ParseUint(fields[11], 10, 64)
if err != nil {
return nil, err
}
line.TxFIFO, err = strconv.ParseUint(fields[12], 10, 64)
if err != nil {
return nil, err
}
line.TxCollisions, err = strconv.ParseUint(fields[13], 10, 64)
if err != nil {
return nil, err
}
line.TxCarrier, err = strconv.ParseUint(fields[14], 10, 64)
if err != nil {
return nil, err
}
line.TxCompressed, err = strconv.ParseUint(fields[15], 10, 64)
if err != nil {
return nil, err
}
return line, nil
}
// Total aggregates the values across interfaces and returns a new NetDevLine.
// The Name field will be a sorted comma separated list of interface names.
func (nd NetDev) Total() NetDevLine {
total := NetDevLine{}
names := make([]string, 0, len(nd))
for _, ifc := range nd {
names = append(names, ifc.Name)
total.RxBytes += ifc.RxBytes
total.RxPackets += ifc.RxPackets
total.RxPackets += ifc.RxPackets
total.RxErrors += ifc.RxErrors
total.RxDropped += ifc.RxDropped
total.RxFIFO += ifc.RxFIFO
total.RxFrame += ifc.RxFrame
total.RxCompressed += ifc.RxCompressed
total.RxMulticast += ifc.RxMulticast
total.TxBytes += ifc.TxBytes
total.TxPackets += ifc.TxPackets
total.TxErrors += ifc.TxErrors
total.TxDropped += ifc.TxDropped
total.TxFIFO += ifc.TxFIFO
total.TxCollisions += ifc.TxCollisions
total.TxCarrier += ifc.TxCarrier
total.TxCompressed += ifc.TxCompressed
}
sort.Strings(names)
total.Name = strings.Join(names, ", ")
return total
}

263
vendor/github.com/prometheus/procfs/nfs/nfs.go generated vendored Normal file
View file

@ -0,0 +1,263 @@
// Copyright 2018 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 nfs implements parsing of /proc/net/rpc/nfsd.
// Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/
package nfs
// ReplyCache models the "rc" line.
type ReplyCache struct {
Hits uint64
Misses uint64
NoCache uint64
}
// FileHandles models the "fh" line.
type FileHandles struct {
Stale uint64
TotalLookups uint64
AnonLookups uint64
DirNoCache uint64
NoDirNoCache uint64
}
// InputOutput models the "io" line.
type InputOutput struct {
Read uint64
Write uint64
}
// Threads models the "th" line.
type Threads struct {
Threads uint64
FullCnt uint64
}
// ReadAheadCache models the "ra" line.
type ReadAheadCache struct {
CacheSize uint64
CacheHistogram []uint64
NotFound uint64
}
// Network models the "net" line.
type Network struct {
NetCount uint64
UDPCount uint64
TCPCount uint64
TCPConnect uint64
}
// ClientRPC models the nfs "rpc" line.
type ClientRPC struct {
RPCCount uint64
Retransmissions uint64
AuthRefreshes uint64
}
// ServerRPC models the nfsd "rpc" line.
type ServerRPC struct {
RPCCount uint64
BadCnt uint64
BadFmt uint64
BadAuth uint64
BadcInt uint64
}
// V2Stats models the "proc2" line.
type V2Stats struct {
Null uint64
GetAttr uint64
SetAttr uint64
Root uint64
Lookup uint64
ReadLink uint64
Read uint64
WrCache uint64
Write uint64
Create uint64
Remove uint64
Rename uint64
Link uint64
SymLink uint64
MkDir uint64
RmDir uint64
ReadDir uint64
FsStat uint64
}
// V3Stats models the "proc3" line.
type V3Stats struct {
Null uint64
GetAttr uint64
SetAttr uint64
Lookup uint64
Access uint64
ReadLink uint64
Read uint64
Write uint64
Create uint64
MkDir uint64
SymLink uint64
MkNod uint64
Remove uint64
RmDir uint64
Rename uint64
Link uint64
ReadDir uint64
ReadDirPlus uint64
FsStat uint64
FsInfo uint64
PathConf uint64
Commit uint64
}
// ClientV4Stats models the nfs "proc4" line.
type ClientV4Stats struct {
Null uint64
Read uint64
Write uint64
Commit uint64
Open uint64
OpenConfirm uint64
OpenNoattr uint64
OpenDowngrade uint64
Close uint64
Setattr uint64
FsInfo uint64
Renew uint64
SetClientID uint64
SetClientIDConfirm uint64
Lock uint64
Lockt uint64
Locku uint64
Access uint64
Getattr uint64
Lookup uint64
LookupRoot uint64
Remove uint64
Rename uint64
Link uint64
Symlink uint64
Create uint64
Pathconf uint64
StatFs uint64
ReadLink uint64
ReadDir uint64
ServerCaps uint64
DelegReturn uint64
GetACL uint64
SetACL uint64
FsLocations uint64
ReleaseLockowner uint64
Secinfo uint64
FsidPresent uint64
ExchangeID uint64
CreateSession uint64
DestroySession uint64
Sequence uint64
GetLeaseTime uint64
ReclaimComplete uint64
LayoutGet uint64
GetDeviceInfo uint64
LayoutCommit uint64
LayoutReturn uint64
SecinfoNoName uint64
TestStateID uint64
FreeStateID uint64
GetDeviceList uint64
BindConnToSession uint64
DestroyClientID uint64
Seek uint64
Allocate uint64
DeAllocate uint64
LayoutStats uint64
Clone uint64
}
// ServerV4Stats models the nfsd "proc4" line.
type ServerV4Stats struct {
Null uint64
Compound uint64
}
// V4Ops models the "proc4ops" line: NFSv4 operations
// Variable list, see:
// v4.0 https://tools.ietf.org/html/rfc3010 (38 operations)
// v4.1 https://tools.ietf.org/html/rfc5661 (58 operations)
// v4.2 https://tools.ietf.org/html/draft-ietf-nfsv4-minorversion2-41 (71 operations)
type V4Ops struct {
//Values uint64 // Variable depending on v4.x sub-version. TODO: Will this always at least include the fields in this struct?
Op0Unused uint64
Op1Unused uint64
Op2Future uint64
Access uint64
Close uint64
Commit uint64
Create uint64
DelegPurge uint64
DelegReturn uint64
GetAttr uint64
GetFH uint64
Link uint64
Lock uint64
Lockt uint64
Locku uint64
Lookup uint64
LookupRoot uint64
Nverify uint64
Open uint64
OpenAttr uint64
OpenConfirm uint64
OpenDgrd uint64
PutFH uint64
PutPubFH uint64
PutRootFH uint64
Read uint64
ReadDir uint64
ReadLink uint64
Remove uint64
Rename uint64
Renew uint64
RestoreFH uint64
SaveFH uint64
SecInfo uint64
SetAttr uint64
Verify uint64
Write uint64
RelLockOwner uint64
}
// ClientRPCStats models all stats from /proc/net/rpc/nfs.
type ClientRPCStats struct {
Network Network
ClientRPC ClientRPC
V2Stats V2Stats
V3Stats V3Stats
ClientV4Stats ClientV4Stats
}
// ServerRPCStats models all stats from /proc/net/rpc/nfsd.
type ServerRPCStats struct {
ReplyCache ReplyCache
FileHandles FileHandles
InputOutput InputOutput
Threads Threads
ReadAheadCache ReadAheadCache
Network Network
ServerRPC ServerRPC
V2Stats V2Stats
V3Stats V3Stats
ServerV4Stats ServerV4Stats
V4Ops V4Ops
}

317
vendor/github.com/prometheus/procfs/nfs/parse.go generated vendored Normal file
View file

@ -0,0 +1,317 @@
// Copyright 2018 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 nfs
import (
"fmt"
)
func parseReplyCache(v []uint64) (ReplyCache, error) {
if len(v) != 3 {
return ReplyCache{}, fmt.Errorf("invalid ReplyCache line %q", v)
}
return ReplyCache{
Hits: v[0],
Misses: v[1],
NoCache: v[2],
}, nil
}
func parseFileHandles(v []uint64) (FileHandles, error) {
if len(v) != 5 {
return FileHandles{}, fmt.Errorf("invalid FileHandles, line %q", v)
}
return FileHandles{
Stale: v[0],
TotalLookups: v[1],
AnonLookups: v[2],
DirNoCache: v[3],
NoDirNoCache: v[4],
}, nil
}
func parseInputOutput(v []uint64) (InputOutput, error) {
if len(v) != 2 {
return InputOutput{}, fmt.Errorf("invalid InputOutput line %q", v)
}
return InputOutput{
Read: v[0],
Write: v[1],
}, nil
}
func parseThreads(v []uint64) (Threads, error) {
if len(v) != 2 {
return Threads{}, fmt.Errorf("invalid Threads line %q", v)
}
return Threads{
Threads: v[0],
FullCnt: v[1],
}, nil
}
func parseReadAheadCache(v []uint64) (ReadAheadCache, error) {
if len(v) != 12 {
return ReadAheadCache{}, fmt.Errorf("invalid ReadAheadCache line %q", v)
}
return ReadAheadCache{
CacheSize: v[0],
CacheHistogram: v[1:11],
NotFound: v[11],
}, nil
}
func parseNetwork(v []uint64) (Network, error) {
if len(v) != 4 {
return Network{}, fmt.Errorf("invalid Network line %q", v)
}
return Network{
NetCount: v[0],
UDPCount: v[1],
TCPCount: v[2],
TCPConnect: v[3],
}, nil
}
func parseServerRPC(v []uint64) (ServerRPC, error) {
if len(v) != 5 {
return ServerRPC{}, fmt.Errorf("invalid RPC line %q", v)
}
return ServerRPC{
RPCCount: v[0],
BadCnt: v[1],
BadFmt: v[2],
BadAuth: v[3],
BadcInt: v[4],
}, nil
}
func parseClientRPC(v []uint64) (ClientRPC, error) {
if len(v) != 3 {
return ClientRPC{}, fmt.Errorf("invalid RPC line %q", v)
}
return ClientRPC{
RPCCount: v[0],
Retransmissions: v[1],
AuthRefreshes: v[2],
}, nil
}
func parseV2Stats(v []uint64) (V2Stats, error) {
values := int(v[0])
if len(v[1:]) != values || values != 18 {
return V2Stats{}, fmt.Errorf("invalid V2Stats line %q", v)
}
return V2Stats{
Null: v[1],
GetAttr: v[2],
SetAttr: v[3],
Root: v[4],
Lookup: v[5],
ReadLink: v[6],
Read: v[7],
WrCache: v[8],
Write: v[9],
Create: v[10],
Remove: v[11],
Rename: v[12],
Link: v[13],
SymLink: v[14],
MkDir: v[15],
RmDir: v[16],
ReadDir: v[17],
FsStat: v[18],
}, nil
}
func parseV3Stats(v []uint64) (V3Stats, error) {
values := int(v[0])
if len(v[1:]) != values || values != 22 {
return V3Stats{}, fmt.Errorf("invalid V3Stats line %q", v)
}
return V3Stats{
Null: v[1],
GetAttr: v[2],
SetAttr: v[3],
Lookup: v[4],
Access: v[5],
ReadLink: v[6],
Read: v[7],
Write: v[8],
Create: v[9],
MkDir: v[10],
SymLink: v[11],
MkNod: v[12],
Remove: v[13],
RmDir: v[14],
Rename: v[15],
Link: v[16],
ReadDir: v[17],
ReadDirPlus: v[18],
FsStat: v[19],
FsInfo: v[20],
PathConf: v[21],
Commit: v[22],
}, nil
}
func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
values := int(v[0])
if len(v[1:]) != values {
return ClientV4Stats{}, fmt.Errorf("invalid ClientV4Stats line %q", v)
}
// This function currently supports mapping 59 NFS v4 client stats. Older
// kernels may emit fewer stats, so we must detect this and pad out the
// values to match the expected slice size.
if values < 59 {
newValues := make([]uint64, 60)
copy(newValues, v)
v = newValues
}
return ClientV4Stats{
Null: v[1],
Read: v[2],
Write: v[3],
Commit: v[4],
Open: v[5],
OpenConfirm: v[6],
OpenNoattr: v[7],
OpenDowngrade: v[8],
Close: v[9],
Setattr: v[10],
FsInfo: v[11],
Renew: v[12],
SetClientID: v[13],
SetClientIDConfirm: v[14],
Lock: v[15],
Lockt: v[16],
Locku: v[17],
Access: v[18],
Getattr: v[19],
Lookup: v[20],
LookupRoot: v[21],
Remove: v[22],
Rename: v[23],
Link: v[24],
Symlink: v[25],
Create: v[26],
Pathconf: v[27],
StatFs: v[28],
ReadLink: v[29],
ReadDir: v[30],
ServerCaps: v[31],
DelegReturn: v[32],
GetACL: v[33],
SetACL: v[34],
FsLocations: v[35],
ReleaseLockowner: v[36],
Secinfo: v[37],
FsidPresent: v[38],
ExchangeID: v[39],
CreateSession: v[40],
DestroySession: v[41],
Sequence: v[42],
GetLeaseTime: v[43],
ReclaimComplete: v[44],
LayoutGet: v[45],
GetDeviceInfo: v[46],
LayoutCommit: v[47],
LayoutReturn: v[48],
SecinfoNoName: v[49],
TestStateID: v[50],
FreeStateID: v[51],
GetDeviceList: v[52],
BindConnToSession: v[53],
DestroyClientID: v[54],
Seek: v[55],
Allocate: v[56],
DeAllocate: v[57],
LayoutStats: v[58],
Clone: v[59],
}, nil
}
func parseServerV4Stats(v []uint64) (ServerV4Stats, error) {
values := int(v[0])
if len(v[1:]) != values || values != 2 {
return ServerV4Stats{}, fmt.Errorf("invalid V4Stats line %q", v)
}
return ServerV4Stats{
Null: v[1],
Compound: v[2],
}, nil
}
func parseV4Ops(v []uint64) (V4Ops, error) {
values := int(v[0])
if len(v[1:]) != values || values < 39 {
return V4Ops{}, fmt.Errorf("invalid V4Ops line %q", v)
}
stats := V4Ops{
Op0Unused: v[1],
Op1Unused: v[2],
Op2Future: v[3],
Access: v[4],
Close: v[5],
Commit: v[6],
Create: v[7],
DelegPurge: v[8],
DelegReturn: v[9],
GetAttr: v[10],
GetFH: v[11],
Link: v[12],
Lock: v[13],
Lockt: v[14],
Locku: v[15],
Lookup: v[16],
LookupRoot: v[17],
Nverify: v[18],
Open: v[19],
OpenAttr: v[20],
OpenConfirm: v[21],
OpenDgrd: v[22],
PutFH: v[23],
PutPubFH: v[24],
PutRootFH: v[25],
Read: v[26],
ReadDir: v[27],
ReadLink: v[28],
Remove: v[29],
Rename: v[30],
Renew: v[31],
RestoreFH: v[32],
SaveFH: v[33],
SecInfo: v[34],
SetAttr: v[35],
Verify: v[36],
Write: v[37],
RelLockOwner: v[38],
}
return stats, nil
}

67
vendor/github.com/prometheus/procfs/nfs/parse_nfs.go generated vendored Normal file
View file

@ -0,0 +1,67 @@
// Copyright 2018 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 nfs
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// ParseClientRPCStats returns stats read from /proc/net/rpc/nfs
func ParseClientRPCStats(r io.Reader) (*ClientRPCStats, error) {
stats := &ClientRPCStats{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(scanner.Text())
// require at least <key> <value>
if len(parts) < 2 {
return nil, fmt.Errorf("invalid NFS metric line %q", line)
}
values, err := util.ParseUint64s(parts[1:])
if err != nil {
return nil, fmt.Errorf("error parsing NFS metric line: %s", err)
}
switch metricLine := parts[0]; metricLine {
case "net":
stats.Network, err = parseNetwork(values)
case "rpc":
stats.ClientRPC, err = parseClientRPC(values)
case "proc2":
stats.V2Stats, err = parseV2Stats(values)
case "proc3":
stats.V3Stats, err = parseV3Stats(values)
case "proc4":
stats.ClientV4Stats, err = parseClientV4Stats(values)
default:
return nil, fmt.Errorf("unknown NFS metric line %q", metricLine)
}
if err != nil {
return nil, fmt.Errorf("errors parsing NFS metric line: %s", err)
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning NFS file: %s", err)
}
return stats, nil
}

89
vendor/github.com/prometheus/procfs/nfs/parse_nfsd.go generated vendored Normal file
View file

@ -0,0 +1,89 @@
// Copyright 2018 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 nfs
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// ParseServerRPCStats returns stats read from /proc/net/rpc/nfsd
func ParseServerRPCStats(r io.Reader) (*ServerRPCStats, error) {
stats := &ServerRPCStats{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(scanner.Text())
// require at least <key> <value>
if len(parts) < 2 {
return nil, fmt.Errorf("invalid NFSd metric line %q", line)
}
label := parts[0]
var values []uint64
var err error
if label == "th" {
if len(parts) < 3 {
return nil, fmt.Errorf("invalid NFSd th metric line %q", line)
}
values, err = util.ParseUint64s(parts[1:3])
} else {
values, err = util.ParseUint64s(parts[1:])
}
if err != nil {
return nil, fmt.Errorf("error parsing NFSd metric line: %s", err)
}
switch metricLine := parts[0]; metricLine {
case "rc":
stats.ReplyCache, err = parseReplyCache(values)
case "fh":
stats.FileHandles, err = parseFileHandles(values)
case "io":
stats.InputOutput, err = parseInputOutput(values)
case "th":
stats.Threads, err = parseThreads(values)
case "ra":
stats.ReadAheadCache, err = parseReadAheadCache(values)
case "net":
stats.Network, err = parseNetwork(values)
case "rpc":
stats.ServerRPC, err = parseServerRPC(values)
case "proc2":
stats.V2Stats, err = parseV2Stats(values)
case "proc3":
stats.V3Stats, err = parseV3Stats(values)
case "proc4":
stats.ServerV4Stats, err = parseServerV4Stats(values)
case "proc4ops":
stats.V4Ops, err = parseV4Ops(values)
default:
return nil, fmt.Errorf("unknown NFSd metric line %q", metricLine)
}
if err != nil {
return nil, fmt.Errorf("errors parsing NFSd metric line: %s", err)
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error scanning NFSd file: %s", err)
}
return stats, nil
}

View file

@ -1,6 +1,20 @@
// Copyright 2018 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 procfs
import (
"bytes"
"fmt"
"io/ioutil"
"os"
@ -113,7 +127,7 @@ func (p Proc) CmdLine() ([]string, error) {
return []string{}, nil
}
return strings.Split(string(data[:len(data)-1]), string(byte(0))), nil
return strings.Split(string(bytes.TrimRight(data, string("\x00"))), string(byte(0))), nil
}
// Comm returns the command name of a process.

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (
@ -47,9 +60,6 @@ func (p Proc) NewIO() (ProcIO, error) {
_, err = fmt.Sscanf(string(data), ioFormat, &pio.RChar, &pio.WChar, &pio.SyscR,
&pio.SyscW, &pio.ReadBytes, &pio.WriteBytes, &pio.CancelledWriteBytes)
if err != nil {
return pio, err
}
return pio, nil
return pio, err
}

View file

@ -1,33 +0,0 @@
package procfs
import "testing"
func TestProcIO(t *testing.T) {
p, err := FS("fixtures").NewProc(26231)
if err != nil {
t.Fatal(err)
}
s, err := p.NewIO()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
want int64
have int64
}{
{name: "RChar", want: 750339, have: int64(s.RChar)},
{name: "WChar", want: 818609, have: int64(s.WChar)},
{name: "SyscR", want: 7405, have: int64(s.SyscR)},
{name: "SyscW", want: 5245, have: int64(s.SyscW)},
{name: "ReadBytes", want: 1024, have: int64(s.ReadBytes)},
{name: "WriteBytes", want: 2048, have: int64(s.WriteBytes)},
{name: "CancelledWriteBytes", want: -1024, have: s.CancelledWriteBytes},
} {
if test.want != test.have {
t.Errorf("want %s %d, have %d", test.name, test.want, test.have)
}
}
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (

View file

@ -1,31 +0,0 @@
package procfs
import "testing"
func TestNewLimits(t *testing.T) {
p, err := FS("fixtures").NewProc(26231)
if err != nil {
t.Fatal(err)
}
l, err := p.NewLimits()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
want int64
have int64
}{
{name: "cpu time", want: -1, have: l.CPUTime},
{name: "open files", want: 2048, have: l.OpenFiles},
{name: "msgqueue size", want: 819200, have: l.MsqqueueSize},
{name: "nice priority", want: 0, have: l.NicePriority},
{name: "address space", want: 8589934592, have: l.AddressSpace},
} {
if test.want != test.have {
t.Errorf("want %s %d, have %d", test.name, test.want, test.have)
}
}
}

68
vendor/github.com/prometheus/procfs/proc_ns.go generated vendored Normal file
View file

@ -0,0 +1,68 @@
// Copyright 2018 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 procfs
import (
"fmt"
"os"
"strconv"
"strings"
)
// Namespace represents a single namespace of a process.
type Namespace struct {
Type string // Namespace type.
Inode uint32 // Inode number of the namespace. If two processes are in the same namespace their inodes will match.
}
// Namespaces contains all of the namespaces that the process is contained in.
type Namespaces map[string]Namespace
// NewNamespaces reads from /proc/[pid/ns/* to get the namespaces of which the
// process is a member.
func (p Proc) NewNamespaces() (Namespaces, error) {
d, err := os.Open(p.path("ns"))
if err != nil {
return nil, err
}
defer d.Close()
names, err := d.Readdirnames(-1)
if err != nil {
return nil, fmt.Errorf("failed to read contents of ns dir: %v", err)
}
ns := make(Namespaces, len(names))
for _, name := range names {
target, err := os.Readlink(p.path("ns", name))
if err != nil {
return nil, err
}
fields := strings.SplitN(target, ":", 2)
if len(fields) != 2 {
return nil, fmt.Errorf("failed to parse namespace type and inode from '%v'", target)
}
typ := fields[0]
inode, err := strconv.ParseUint(strings.Trim(fields[1], "[]"), 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse inode from '%v': %v", fields[1], err)
}
ns[name] = Namespace{typ, uint32(inode)}
}
return ns, nil
}

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (

View file

@ -1,110 +0,0 @@
package procfs
import (
"os"
"testing"
)
func TestProcStat(t *testing.T) {
p, err := FS("fixtures").NewProc(26231)
if err != nil {
t.Fatal(err)
}
s, err := p.NewStat()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
want int
have int
}{
{name: "pid", want: 26231, have: s.PID},
{name: "user time", want: 1677, have: int(s.UTime)},
{name: "system time", want: 44, have: int(s.STime)},
{name: "start time", want: 82375, have: int(s.Starttime)},
{name: "virtual memory size", want: 56274944, have: s.VSize},
{name: "resident set size", want: 1981, have: s.RSS},
} {
if test.want != test.have {
t.Errorf("want %s %d, have %d", test.name, test.want, test.have)
}
}
}
func TestProcStatComm(t *testing.T) {
s1, err := testProcStat(26231)
if err != nil {
t.Fatal(err)
}
if want, have := "vim", s1.Comm; want != have {
t.Errorf("want comm %s, have %s", want, have)
}
s2, err := testProcStat(584)
if err != nil {
t.Fatal(err)
}
if want, have := "(a b ) ( c d) ", s2.Comm; want != have {
t.Errorf("want comm %s, have %s", want, have)
}
}
func TestProcStatVirtualMemory(t *testing.T) {
s, err := testProcStat(26231)
if err != nil {
t.Fatal(err)
}
if want, have := 56274944, s.VirtualMemory(); want != have {
t.Errorf("want virtual memory %d, have %d", want, have)
}
}
func TestProcStatResidentMemory(t *testing.T) {
s, err := testProcStat(26231)
if err != nil {
t.Fatal(err)
}
if want, have := 1981*os.Getpagesize(), s.ResidentMemory(); want != have {
t.Errorf("want resident memory %d, have %d", want, have)
}
}
func TestProcStatStartTime(t *testing.T) {
s, err := testProcStat(26231)
if err != nil {
t.Fatal(err)
}
time, err := s.StartTime()
if err != nil {
t.Fatal(err)
}
if want, have := 1418184099.75, time; want != have {
t.Errorf("want start time %f, have %f", want, have)
}
}
func TestProcStatCPUTime(t *testing.T) {
s, err := testProcStat(26231)
if err != nil {
t.Fatal(err)
}
if want, have := 17.21, s.CPUTime(); want != have {
t.Errorf("want cpu time %f, have %f", want, have)
}
}
func testProcStat(pid int) (ProcStat, error) {
p, err := FS("fixtures").NewProc(pid)
if err != nil {
return ProcStat{}, err
}
return p.NewStat()
}

View file

@ -1,160 +0,0 @@
package procfs
import (
"reflect"
"sort"
"testing"
)
func TestSelf(t *testing.T) {
fs := FS("fixtures")
p1, err := fs.NewProc(26231)
if err != nil {
t.Fatal(err)
}
p2, err := fs.Self()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(p1, p2) {
t.Errorf("want process %v, have %v", p1, p2)
}
}
func TestAllProcs(t *testing.T) {
procs, err := FS("fixtures").AllProcs()
if err != nil {
t.Fatal(err)
}
sort.Sort(procs)
for i, p := range []*Proc{{PID: 584}, {PID: 26231}} {
if want, have := p.PID, procs[i].PID; want != have {
t.Errorf("want processes %d, have %d", want, have)
}
}
}
func TestCmdLine(t *testing.T) {
for _, tt := range []struct {
process int
want []string
}{
{process: 26231, want: []string{"vim", "test.go", "+10"}},
{process: 26232, want: []string{}},
} {
p1, err := FS("fixtures").NewProc(tt.process)
if err != nil {
t.Fatal(err)
}
c1, err := p1.CmdLine()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tt.want, c1) {
t.Errorf("want cmdline %v, have %v", tt.want, c1)
}
}
}
func TestComm(t *testing.T) {
for _, tt := range []struct {
process int
want string
}{
{process: 26231, want: "vim"},
{process: 26232, want: "ata_sff"},
} {
p1, err := FS("fixtures").NewProc(tt.process)
if err != nil {
t.Fatal(err)
}
c1, err := p1.Comm()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tt.want, c1) {
t.Errorf("want comm %v, have %v", tt.want, c1)
}
}
}
func TestExecutable(t *testing.T) {
for _, tt := range []struct {
process int
want string
}{
{process: 26231, want: "/usr/bin/vim"},
{process: 26232, want: ""},
} {
p, err := FS("fixtures").NewProc(tt.process)
if err != nil {
t.Fatal(err)
}
exe, err := p.Executable()
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(tt.want, exe) {
t.Errorf("want absolute path to cmdline %v, have %v", tt.want, exe)
}
}
}
func TestFileDescriptors(t *testing.T) {
p1, err := FS("fixtures").NewProc(26231)
if err != nil {
t.Fatal(err)
}
fds, err := p1.FileDescriptors()
if err != nil {
t.Fatal(err)
}
sort.Sort(byUintptr(fds))
if want := []uintptr{0, 1, 2, 3, 10}; !reflect.DeepEqual(want, fds) {
t.Errorf("want fds %v, have %v", want, fds)
}
}
func TestFileDescriptorTargets(t *testing.T) {
p1, err := FS("fixtures").NewProc(26231)
if err != nil {
t.Fatal(err)
}
fds, err := p1.FileDescriptorTargets()
if err != nil {
t.Fatal(err)
}
sort.Strings(fds)
var want = []string{
"../../symlinktargets/abc",
"../../symlinktargets/def",
"../../symlinktargets/ghi",
"../../symlinktargets/uvw",
"../../symlinktargets/xyz",
}
if !reflect.DeepEqual(want, fds) {
t.Errorf("want fds %v, have %v", want, fds)
}
}
func TestFileDescriptorsLen(t *testing.T) {
p1, err := FS("fixtures").NewProc(26231)
if err != nil {
t.Fatal(err)
}
l, err := p1.FileDescriptorsLen()
if err != nil {
t.Fatal(err)
}
if want, have := 5, l; want != have {
t.Errorf("want fds %d, have %d", want, have)
}
}
type byUintptr []uintptr
func (a byUintptr) Len() int { return len(a) }
func (a byUintptr) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byUintptr) Less(i, j int) bool { return a[i] < a[j] }

View file

@ -1,3 +1,16 @@
// Copyright 2018 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 procfs
import (

View file

@ -1,61 +0,0 @@
package procfs
import "testing"
func TestStat(t *testing.T) {
s, err := FS("fixtures").NewStat()
if err != nil {
t.Fatal(err)
}
// cpu
if want, have := float64(301854)/userHZ, s.CPUTotal.User; want != have {
t.Errorf("want cpu/user %v, have %v", want, have)
}
if want, have := float64(31)/userHZ, s.CPU[7].SoftIRQ; want != have {
t.Errorf("want cpu7/softirq %v, have %v", want, have)
}
// intr
if want, have := uint64(8885917), s.IRQTotal; want != have {
t.Errorf("want irq/total %d, have %d", want, have)
}
if want, have := uint64(1), s.IRQ[8]; want != have {
t.Errorf("want irq8 %d, have %d", want, have)
}
// ctxt
if want, have := uint64(38014093), s.ContextSwitches; want != have {
t.Errorf("want context switches (ctxt) %d, have %d", want, have)
}
// btime
if want, have := uint64(1418183276), s.BootTime; want != have {
t.Errorf("want boot time (btime) %d, have %d", want, have)
}
// processes
if want, have := uint64(26442), s.ProcessCreated; want != have {
t.Errorf("want process created (processes) %d, have %d", want, have)
}
// procs_running
if want, have := uint64(2), s.ProcessesRunning; want != have {
t.Errorf("want processes running (procs_running) %d, have %d", want, have)
}
// procs_blocked
if want, have := uint64(1), s.ProcessesBlocked; want != have {
t.Errorf("want processes blocked (procs_blocked) %d, have %d", want, have)
}
// softirq
if want, have := uint64(5057579), s.SoftIRQTotal; want != have {
t.Errorf("want softirq total %d, have %d", want, have)
}
if want, have := uint64(508444), s.SoftIRQ.Rcu; want != have {
t.Errorf("want softirq RCU %d, have %d", want, have)
}
}

View file

@ -1,11 +1,26 @@
#!/usr/bin/env bash
# Purpose: plain text tar format
# Limitations: - only suitable for text files, directories, and symlinks
# - stores only filename, content, and mode
# - not designed for untrusted input
#
# Note: must work with bash version 3.2 (macOS)
# Copyright 2017 Roger Luethi
#
# 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.
set -o errexit -o nounset
# Sanitize environment (for instance, standard sorting of glob matches)
@ -13,6 +28,55 @@ export LC_ALL=C
path=""
CMD=""
ARG_STRING="$*"
#------------------------------------------------------------------------------
# Not all sed implementations can work on null bytes. In order to make ttar
# work out of the box on macOS, use Python as a stream editor.
USE_PYTHON=0
PYTHON_CREATE_FILTER=$(cat << 'PCF'
#!/usr/bin/env python
import re
import sys
for line in sys.stdin:
line = re.sub(r'EOF', r'\EOF', line)
line = re.sub(r'NULLBYTE', r'\NULLBYTE', line)
line = re.sub('\x00', r'NULLBYTE', line)
sys.stdout.write(line)
PCF
)
PYTHON_EXTRACT_FILTER=$(cat << 'PEF'
#!/usr/bin/env python
import re
import sys
for line in sys.stdin:
line = re.sub(r'(?<!\\)NULLBYTE', '\x00', line)
line = re.sub(r'\\NULLBYTE', 'NULLBYTE', line)
line = re.sub(r'([^\\])EOF', r'\1', line)
line = re.sub(r'\\EOF', 'EOF', line)
sys.stdout.write(line)
PEF
)
function test_environment {
if [[ "$(echo "a" | sed 's/a/\x0/' | wc -c)" -ne 2 ]]; then
echo "WARNING sed unable to handle null bytes, using Python (slow)."
if ! which python >/dev/null; then
echo "ERROR Python not found. Aborting."
exit 2
fi
USE_PYTHON=1
fi
}
#------------------------------------------------------------------------------
function usage {
bname=$(basename "$0")
@ -23,6 +87,7 @@ Usage: $bname [-C <DIR>] -c -f <ARCHIVE> <FILE...> (create archive)
Options:
-C <DIR> (change directory)
-v (verbose)
Example: Change to sysfs directory, create ttar file from fixtures directory
$bname -C sysfs -c -f sysfs/fixtures.ttar fixtures/
@ -45,6 +110,8 @@ function set_cmd {
CMD=$1
}
unset VERBOSE
while getopts :cf:htxvC: opt; do
case $opt in
c)
@ -142,8 +209,37 @@ function extract {
fi
while IFS= read -r line; do
line_no=$(( line_no + 1 ))
local eof_without_newline
if [ "$size" -gt 0 ]; then
echo "$line" >> "$path"
if [[ "$line" =~ [^\\]EOF ]]; then
# An EOF not preceeded by a backslash indicates that the line
# does not end with a newline
eof_without_newline=1
else
eof_without_newline=0
fi
# Replace NULLBYTE with null byte if at beginning of line
# Replace NULLBYTE with null byte unless preceeded by backslash
# Remove one backslash in front of NULLBYTE (if any)
# Remove EOF unless preceeded by backslash
# Remove one backslash in front of EOF
if [ $USE_PYTHON -eq 1 ]; then
echo -n "$line" | python -c "$PYTHON_EXTRACT_FILTER" >> "$path"
else
# The repeated pattern makes up for sed's lack of negative
# lookbehind assertions (for consecutive null bytes).
echo -n "$line" | \
sed -e 's/^NULLBYTE/\x0/g;
s/\([^\\]\)NULLBYTE/\1\x0/g;
s/\([^\\]\)NULLBYTE/\1\x0/g;
s/\\NULLBYTE/NULLBYTE/g;
s/\([^\\]\)EOF/\1/g;
s/\\EOF/EOF/g;
' >> "$path"
fi
if [[ "$eof_without_newline" -eq 0 ]]; then
echo >> "$path"
fi
size=$(( size - 1 ))
continue
fi
@ -187,11 +283,14 @@ function get_mode {
local mfile=$1
if [ -z "${STAT_OPTION:-}" ]; then
if stat -c '%a' "$mfile" >/dev/null 2>&1; then
# GNU stat
STAT_OPTION='-c'
STAT_FORMAT='%a'
else
# BSD stat
STAT_OPTION='-f'
STAT_FORMAT='%A'
# Octal output, user/group/other (omit file type, sticky bit)
STAT_FORMAT='%OLp'
fi
fi
stat "${STAT_OPTION}" "${STAT_FORMAT}" "$mfile"
@ -200,6 +299,7 @@ function get_mode {
function _create {
shopt -s nullglob
local mode
local eof_without_newline
while (( "$#" )); do
file=$1
if [ -L "$file" ]; then
@ -223,8 +323,30 @@ function _create {
elif [ -f "$file" ]; then
echo "Path: $file"
lines=$(wc -l "$file"|awk '{print $1}')
eof_without_newline=0
if [[ "$(wc -c "$file"|awk '{print $1}')" -gt 0 ]] && \
[[ "$(tail -c 1 "$file" | wc -l)" -eq 0 ]]; then
eof_without_newline=1
lines=$((lines+1))
fi
echo "Lines: $lines"
cat "$file"
# Add backslash in front of EOF
# Add backslash in front of NULLBYTE
# Replace null byte with NULLBYTE
if [ $USE_PYTHON -eq 1 ]; then
< "$file" python -c "$PYTHON_CREATE_FILTER"
else
< "$file" \
sed 's/EOF/\\EOF/g;
s/NULLBYTE/\\NULLBYTE/g;
s/\x0/NULLBYTE/g;
'
fi
if [[ "$eof_without_newline" -eq 1 ]]; then
# Finish line with EOF to indicate that the original line did
# not end with a linefeed
echo "EOF"
fi
mode=$(get_mode "$file")
echo "Mode: $mode"
vecho "$mode $file"
@ -249,9 +371,12 @@ function create {
rm "$ttar_file"
fi
exec > "$ttar_file"
echo "# Archive created by ttar $ARG_STRING"
_create "$@"
}
test_environment
if [ -n "${CDIR:-}" ]; then
if [[ "$ARCHIVE" != /* ]]; then
# Relative path: preserve the archive's location before changing

View file

@ -1,66 +0,0 @@
// Copyright 2017 Prometheus Team
// 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 procfs
import (
"testing"
)
func TestXfrmStats(t *testing.T) {
xfrmStats, err := FS("fixtures").NewXfrmStat()
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
want int
got int
}{
{name: "XfrmInError", want: 1, got: xfrmStats.XfrmInError},
{name: "XfrmInBufferError", want: 2, got: xfrmStats.XfrmInBufferError},
{name: "XfrmInHdrError", want: 4, got: xfrmStats.XfrmInHdrError},
{name: "XfrmInNoStates", want: 3, got: xfrmStats.XfrmInNoStates},
{name: "XfrmInStateProtoError", want: 40, got: xfrmStats.XfrmInStateProtoError},
{name: "XfrmInStateModeError", want: 100, got: xfrmStats.XfrmInStateModeError},
{name: "XfrmInStateSeqError", want: 6000, got: xfrmStats.XfrmInStateSeqError},
{name: "XfrmInStateExpired", want: 4, got: xfrmStats.XfrmInStateExpired},
{name: "XfrmInStateMismatch", want: 23451, got: xfrmStats.XfrmInStateMismatch},
{name: "XfrmInStateInvalid", want: 55555, got: xfrmStats.XfrmInStateInvalid},
{name: "XfrmInTmplMismatch", want: 51, got: xfrmStats.XfrmInTmplMismatch},
{name: "XfrmInNoPols", want: 65432, got: xfrmStats.XfrmInNoPols},
{name: "XfrmInPolBlock", want: 100, got: xfrmStats.XfrmInPolBlock},
{name: "XfrmInPolError", want: 10000, got: xfrmStats.XfrmInPolError},
{name: "XfrmOutError", want: 1000000, got: xfrmStats.XfrmOutError},
{name: "XfrmOutBundleGenError", want: 43321, got: xfrmStats.XfrmOutBundleGenError},
{name: "XfrmOutBundleCheckError", want: 555, got: xfrmStats.XfrmOutBundleCheckError},
{name: "XfrmOutNoStates", want: 869, got: xfrmStats.XfrmOutNoStates},
{name: "XfrmOutStateProtoError", want: 4542, got: xfrmStats.XfrmOutStateProtoError},
{name: "XfrmOutStateModeError", want: 4, got: xfrmStats.XfrmOutStateModeError},
{name: "XfrmOutStateSeqError", want: 543, got: xfrmStats.XfrmOutStateSeqError},
{name: "XfrmOutStateExpired", want: 565, got: xfrmStats.XfrmOutStateExpired},
{name: "XfrmOutPolBlock", want: 43456, got: xfrmStats.XfrmOutPolBlock},
{name: "XfrmOutPolDead", want: 7656, got: xfrmStats.XfrmOutPolDead},
{name: "XfrmOutPolError", want: 1454, got: xfrmStats.XfrmOutPolError},
{name: "XfrmFwdHdrError", want: 6654, got: xfrmStats.XfrmFwdHdrError},
{name: "XfrmOutStateInvaliad", want: 28765, got: xfrmStats.XfrmOutStateInvalid},
{name: "XfrmAcquireError", want: 24532, got: xfrmStats.XfrmAcquireError},
{name: "XfrmInStateInvalid", want: 55555, got: xfrmStats.XfrmInStateInvalid},
{name: "XfrmOutError", want: 1000000, got: xfrmStats.XfrmOutError},
} {
if test.want != test.got {
t.Errorf("Want %s %d, have %d", test.name, test.want, test.got)
}
}
}

View file

@ -17,8 +17,9 @@ import (
"bufio"
"fmt"
"io"
"strconv"
"strings"
"github.com/prometheus/procfs/internal/util"
)
// ParseStats parses a Stats from an input io.Reader, using the format
@ -68,7 +69,7 @@ func ParseStats(r io.Reader) (*Stats, error) {
// Extended precision counters are uint64 values.
if label == fieldXpc {
us, err := parseUint64s(ss[1:])
us, err := util.ParseUint64s(ss[1:])
if err != nil {
return nil, err
}
@ -82,7 +83,7 @@ func ParseStats(r io.Reader) (*Stats, error) {
}
// All other counters are uint32 values.
us, err := parseUint32s(ss[1:])
us, err := util.ParseUint32s(ss[1:])
if err != nil {
return nil, err
}
@ -327,33 +328,3 @@ func extendedPrecisionStats(us []uint64) (ExtendedPrecisionStats, error) {
ReadBytes: us[2],
}, nil
}
// parseUint32s parses a slice of strings into a slice of uint32s.
func parseUint32s(ss []string) ([]uint32, error) {
us := make([]uint32, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return nil, err
}
us = append(us, uint32(u))
}
return us, nil
}
// parseUint64s parses a slice of strings into a slice of uint64s.
func parseUint64s(ss []string) ([]uint64, error) {
us := make([]uint64, 0, len(ss))
for _, s := range ss {
u, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return nil, err
}
us = append(us, u)
}
return us, nil
}

View file

@ -1,442 +0,0 @@
// Copyright 2017 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 xfs_test
import (
"reflect"
"strings"
"testing"
"github.com/prometheus/procfs"
"github.com/prometheus/procfs/xfs"
)
func TestParseStats(t *testing.T) {
tests := []struct {
name string
s string
fs bool
stats *xfs.Stats
invalid bool
}{
{
name: "empty file OK",
},
{
name: "short or empty lines and unknown labels ignored",
s: "one\n\ntwo 1 2 3\n",
stats: &xfs.Stats{},
},
{
name: "bad uint32",
s: "extent_alloc XXX",
invalid: true,
},
{
name: "bad uint64",
s: "xpc XXX",
invalid: true,
},
{
name: "extent_alloc bad",
s: "extent_alloc 1",
invalid: true,
},
{
name: "extent_alloc OK",
s: "extent_alloc 1 2 3 4",
stats: &xfs.Stats{
ExtentAllocation: xfs.ExtentAllocationStats{
ExtentsAllocated: 1,
BlocksAllocated: 2,
ExtentsFreed: 3,
BlocksFreed: 4,
},
},
},
{
name: "abt bad",
s: "abt 1",
invalid: true,
},
{
name: "abt OK",
s: "abt 1 2 3 4",
stats: &xfs.Stats{
AllocationBTree: xfs.BTreeStats{
Lookups: 1,
Compares: 2,
RecordsInserted: 3,
RecordsDeleted: 4,
},
},
},
{
name: "blk_map bad",
s: "blk_map 1",
invalid: true,
},
{
name: "blk_map OK",
s: "blk_map 1 2 3 4 5 6 7",
stats: &xfs.Stats{
BlockMapping: xfs.BlockMappingStats{
Reads: 1,
Writes: 2,
Unmaps: 3,
ExtentListInsertions: 4,
ExtentListDeletions: 5,
ExtentListLookups: 6,
ExtentListCompares: 7,
},
},
},
{
name: "bmbt bad",
s: "bmbt 1",
invalid: true,
},
{
name: "bmbt OK",
s: "bmbt 1 2 3 4",
stats: &xfs.Stats{
BlockMapBTree: xfs.BTreeStats{
Lookups: 1,
Compares: 2,
RecordsInserted: 3,
RecordsDeleted: 4,
},
},
},
{
name: "dir bad",
s: "dir 1",
invalid: true,
},
{
name: "dir OK",
s: "dir 1 2 3 4",
stats: &xfs.Stats{
DirectoryOperation: xfs.DirectoryOperationStats{
Lookups: 1,
Creates: 2,
Removes: 3,
Getdents: 4,
},
},
},
{
name: "trans bad",
s: "trans 1",
invalid: true,
},
{
name: "trans OK",
s: "trans 1 2 3",
stats: &xfs.Stats{
Transaction: xfs.TransactionStats{
Sync: 1,
Async: 2,
Empty: 3,
},
},
},
{
name: "ig bad",
s: "ig 1",
invalid: true,
},
{
name: "ig OK",
s: "ig 1 2 3 4 5 6 7",
stats: &xfs.Stats{
InodeOperation: xfs.InodeOperationStats{
Attempts: 1,
Found: 2,
Recycle: 3,
Missed: 4,
Duplicate: 5,
Reclaims: 6,
AttributeChange: 7,
},
},
},
{
name: "log bad",
s: "log 1",
invalid: true,
},
{
name: "log OK",
s: "log 1 2 3 4 5",
stats: &xfs.Stats{
LogOperation: xfs.LogOperationStats{
Writes: 1,
Blocks: 2,
NoInternalBuffers: 3,
Force: 4,
ForceSleep: 5,
},
},
},
{
name: "rw bad",
s: "rw 1",
invalid: true,
},
{
name: "rw OK",
s: "rw 1 2",
stats: &xfs.Stats{
ReadWrite: xfs.ReadWriteStats{
Read: 1,
Write: 2,
},
},
},
{
name: "attr bad",
s: "attr 1",
invalid: true,
},
{
name: "attr OK",
s: "attr 1 2 3 4",
stats: &xfs.Stats{
AttributeOperation: xfs.AttributeOperationStats{
Get: 1,
Set: 2,
Remove: 3,
List: 4,
},
},
},
{
name: "icluster bad",
s: "icluster 1",
invalid: true,
},
{
name: "icluster OK",
s: "icluster 1 2 3",
stats: &xfs.Stats{
InodeClustering: xfs.InodeClusteringStats{
Iflush: 1,
Flush: 2,
FlushInode: 3,
},
},
},
{
name: "vnodes bad",
s: "vnodes 1",
invalid: true,
},
{
name: "vnodes (missing free) OK",
s: "vnodes 1 2 3 4 5 6 7",
stats: &xfs.Stats{
Vnode: xfs.VnodeStats{
Active: 1,
Allocate: 2,
Get: 3,
Hold: 4,
Release: 5,
Reclaim: 6,
Remove: 7,
},
},
},
{
name: "vnodes (with free) OK",
s: "vnodes 1 2 3 4 5 6 7 8",
stats: &xfs.Stats{
Vnode: xfs.VnodeStats{
Active: 1,
Allocate: 2,
Get: 3,
Hold: 4,
Release: 5,
Reclaim: 6,
Remove: 7,
Free: 8,
},
},
},
{
name: "buf bad",
s: "buf 1",
invalid: true,
},
{
name: "buf OK",
s: "buf 1 2 3 4 5 6 7 8 9",
stats: &xfs.Stats{
Buffer: xfs.BufferStats{
Get: 1,
Create: 2,
GetLocked: 3,
GetLockedWaited: 4,
BusyLocked: 5,
MissLocked: 6,
PageRetries: 7,
PageFound: 8,
GetRead: 9,
},
},
},
{
name: "xpc bad",
s: "xpc 1",
invalid: true,
},
{
name: "xpc OK",
s: "xpc 1 2 3",
stats: &xfs.Stats{
ExtendedPrecision: xfs.ExtendedPrecisionStats{
FlushBytes: 1,
WriteBytes: 2,
ReadBytes: 3,
},
},
},
{
name: "fixtures OK",
fs: true,
stats: &xfs.Stats{
ExtentAllocation: xfs.ExtentAllocationStats{
ExtentsAllocated: 92447,
BlocksAllocated: 97589,
ExtentsFreed: 92448,
BlocksFreed: 93751,
},
AllocationBTree: xfs.BTreeStats{
Lookups: 0,
Compares: 0,
RecordsInserted: 0,
RecordsDeleted: 0,
},
BlockMapping: xfs.BlockMappingStats{
Reads: 1767055,
Writes: 188820,
Unmaps: 184891,
ExtentListInsertions: 92447,
ExtentListDeletions: 92448,
ExtentListLookups: 2140766,
ExtentListCompares: 0,
},
BlockMapBTree: xfs.BTreeStats{
Lookups: 0,
Compares: 0,
RecordsInserted: 0,
RecordsDeleted: 0,
},
DirectoryOperation: xfs.DirectoryOperationStats{
Lookups: 185039,
Creates: 92447,
Removes: 92444,
Getdents: 136422,
},
Transaction: xfs.TransactionStats{
Sync: 706,
Async: 944304,
Empty: 0,
},
InodeOperation: xfs.InodeOperationStats{
Attempts: 185045,
Found: 58807,
Recycle: 0,
Missed: 126238,
Duplicate: 0,
Reclaims: 33637,
AttributeChange: 22,
},
LogOperation: xfs.LogOperationStats{
Writes: 2883,
Blocks: 113448,
NoInternalBuffers: 9,
Force: 17360,
ForceSleep: 739,
},
ReadWrite: xfs.ReadWriteStats{
Read: 107739,
Write: 94045,
},
AttributeOperation: xfs.AttributeOperationStats{
Get: 4,
Set: 0,
Remove: 0,
List: 0,
},
InodeClustering: xfs.InodeClusteringStats{
Iflush: 8677,
Flush: 7849,
FlushInode: 135802,
},
Vnode: xfs.VnodeStats{
Active: 92601,
Allocate: 0,
Get: 0,
Hold: 0,
Release: 92444,
Reclaim: 92444,
Remove: 92444,
Free: 0,
},
Buffer: xfs.BufferStats{
Get: 2666287,
Create: 7122,
GetLocked: 2659202,
GetLockedWaited: 3599,
BusyLocked: 2,
MissLocked: 7085,
PageRetries: 0,
PageFound: 10297,
GetRead: 7085,
},
ExtendedPrecision: xfs.ExtendedPrecisionStats{
FlushBytes: 399724544,
WriteBytes: 92823103,
ReadBytes: 86219234,
},
},
},
}
for _, tt := range tests {
var (
stats *xfs.Stats
err error
)
if tt.s != "" {
stats, err = xfs.ParseStats(strings.NewReader(tt.s))
}
if tt.fs {
stats, err = procfs.FS("../fixtures").XFSStats()
}
if tt.invalid && err == nil {
t.Error("expected an error, but none occurred")
}
if !tt.invalid && err != nil {
t.Errorf("unexpected error: %v", err)
}
if want, have := tt.stats, stats; !reflect.DeepEqual(want, have) {
t.Errorf("unexpected XFS stats:\nwant:\n%v\nhave:\n%v", want, have)
}
}
}