created docker-prometheus compose file and edited the Prometheus yml file

This commit is contained in:
Brian Christner
2015-08-18 14:36:46 +02:00
parent 4b9b5b70e3
commit bf34f627cf
664 changed files with 150581 additions and 0 deletions

168
prom/util/cli/cli.go Normal file
View File

@ -0,0 +1,168 @@
// 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 cli
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"text/template"
)
// Command represents a single command within an application.
type Command struct {
Desc string
Run func(t Term, args ...string) int
}
// Term handles an application's output.
type Term interface {
Infof(format string, v ...interface{})
Errorf(format string, v ...interface{})
Out(format string)
}
type basicTerm struct {
out, err io.Writer
}
// Infof implements Term.
func (t *basicTerm) Infof(format string, v ...interface{}) {
fmt.Fprintf(t.err, format, v...)
fmt.Fprint(t.err, "\n")
}
// Errorf implements Term.
func (t *basicTerm) Errorf(format string, v ...interface{}) {
fmt.Fprintf(t.err, format, v...)
fmt.Fprint(t.err, "\n")
}
// Out implements Term.
func (t *basicTerm) Out(msg string) {
fmt.Fprint(t.out, msg)
fmt.Fprint(t.out, "\n")
}
// BasicTerm returns a Term writing Infof and Errorf to err and Out to out.
func BasicTerm(out, err io.Writer) Term {
return &basicTerm{out: out, err: err}
}
// App represents an application that may consist of multiple commands.
type App struct {
Name string
Help func() string
commands map[string]*Command
}
// NewApp creates a new application with a pre-registered help command.
func NewApp(name string) *App {
app := &App{
Name: name,
commands: map[string]*Command{},
}
app.Register("help", &Command{
Desc: "prints this help text",
Run: func(t Term, _ ...string) int {
help := app.Help
if help == nil {
help = BasicHelp(app, tmpl)
}
t.Infof(help() + "\n")
return 0
},
})
return app
}
// Register adds a new command to the application.
func (app *App) Register(name string, cmd *Command) {
name = strings.TrimSpace(name)
if name == "" {
panic("command name must not be empty")
}
if _, ok := app.commands[name]; ok {
panic("command cannot be registered twice")
}
app.commands[name] = cmd
}
// Run the application with the given arguments. Output is sent to t.
func (app *App) Run(t Term, args ...string) int {
help := app.commands["help"]
if len(args) == 0 || strings.HasPrefix(args[0], "-") {
help.Run(t)
return 2
}
cmd, ok := app.commands[args[0]]
if !ok {
help.Run(t)
return 2
}
return cmd.Run(t, args[1:]...)
}
var tmpl = `
usage: {{ .Name }} <command> [<args>]
Available commands:
{{ range .Commands }}{{ .Name }} {{ .Desc }}
{{ end }}
`
// BasicHelp returns a function that creates a basic help text for the application
// with its commands.
func BasicHelp(app *App, ts string) func() string {
t := template.Must(template.New("help").Parse(ts))
return func() string {
type command struct {
Name, Desc string
}
cmds := []command{}
var maxLen int
names := []string{}
for name := range app.commands {
names = append(names, name)
if len(name) > maxLen {
maxLen = len(name)
}
}
sort.Strings(names)
for _, name := range names {
cmds = append(cmds, command{
Name: name + strings.Repeat(" ", maxLen-len(name)),
Desc: app.commands[name].Desc,
})
}
var buf bytes.Buffer
t.Execute(&buf, struct {
Name string
Commands []command
}{
Name: app.Name,
Commands: cmds,
})
return strings.TrimSpace(buf.String())
}
}

33
prom/util/flock/flock.go Normal file
View File

@ -0,0 +1,33 @@
// Package flock provides portable file locking. It is essentially ripped out
// from the code of github.com/syndtr/goleveldb. Strange enough that the
// standard library does not provide this functionality. Once this package has
// proven to work as expected, we should probably turn it into a separate
// general purpose package for humanity.
package flock
import (
"os"
"path/filepath"
)
// Releaser provides the Release method to release a file lock.
type Releaser interface {
Release() error
}
// New locks the file with the provided name. If the file does not exist, it is
// created. The returned Releaser is used to release the lock. existed is true
// if the file to lock already existed. A non-nil error is returned if the
// locking has failed. Neither this function nor the returned Releaser is
// goroutine-safe.
func New(fileName string) (r Releaser, existed bool, err error) {
if err = os.MkdirAll(filepath.Dir(fileName), 0755); err != nil {
return
}
_, err = os.Stat(fileName)
existed = err == nil
r, err = newLock(fileName)
return
}

View File

@ -0,0 +1,19 @@
package flock
import "os"
type plan9Lock struct {
f *os.File
}
func (l *plan9Lock) Release() error {
return l.f.Close()
}
func newLock(fileName string) (Releaser, error) {
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
if err != nil {
return nil, err
}
return &plan9Lock{f}, nil
}

View File

@ -0,0 +1,46 @@
// +build solaris
package flock
import (
"os"
"syscall"
)
type unixLock struct {
f *os.File
}
func (l *unixLock) Release() error {
if err := l.set(false); err != nil {
return err
}
return l.f.Close()
}
func (l *unixLock) set(lock bool) error {
flock := syscall.Flock_t{
Type: syscall.F_UNLCK,
Start: 0,
Len: 0,
Whence: 1,
}
if lock {
flock.Type = syscall.F_WRLCK
}
return syscall.FcntlFlock(l.f.Fd(), syscall.F_SETLK, &flock)
}
func newLock(fileName string) (Releaser, error) {
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
l := &unixLock{f}
err = l.set(true)
if err != nil {
f.Close()
return nil, err
}
return l, nil
}

View File

@ -0,0 +1,67 @@
package flock
import (
"os"
"path/filepath"
"testing"
"github.com/prometheus/prometheus/util/testutil"
)
func TestLocking(t *testing.T) {
dir := testutil.NewTemporaryDirectory("test_flock", t)
defer dir.Close()
fileName := filepath.Join(dir.Path(), "LOCK")
if _, err := os.Stat(fileName); err == nil {
t.Fatalf("File %q unexpectedly exists.", fileName)
}
lock, existed, err := New(fileName)
if err != nil {
t.Fatalf("Error locking file %q: %s", fileName, err)
}
if existed {
t.Errorf("File %q reported as existing during locking.", fileName)
}
// File must now exist.
if _, err := os.Stat(fileName); err != nil {
t.Errorf("Could not stat file %q expected to exist: %s", fileName, err)
}
// Try to lock again.
lockedAgain, existed, err := New(fileName)
if err == nil {
t.Fatalf("File %q locked twice.", fileName)
}
if lockedAgain != nil {
t.Error("Unsuccessful locking did not return nil.")
}
if !existed {
t.Errorf("Existing file %q not recognized.", fileName)
}
if err := lock.Release(); err != nil {
t.Errorf("Error releasing lock for file %q: %s", fileName, err)
}
// File must still exist.
if _, err := os.Stat(fileName); err != nil {
t.Errorf("Could not stat file %q expected to exist: %s", fileName, err)
}
// Lock existing file.
lock, existed, err = New(fileName)
if err != nil {
t.Fatalf("Error locking file %q: %s", fileName, err)
}
if !existed {
t.Errorf("Existing file %q not recognized.", fileName)
}
if err := lock.Release(); err != nil {
t.Errorf("Error releasing lock for file %q: %s", fileName, err)
}
}

View File

@ -0,0 +1,41 @@
// +build darwin dragonfly freebsd linux netbsd openbsd
package flock
import (
"os"
"syscall"
)
type unixLock struct {
f *os.File
}
func (l *unixLock) Release() error {
if err := l.set(false); err != nil {
return err
}
return l.f.Close()
}
func (l *unixLock) set(lock bool) error {
how := syscall.LOCK_UN
if lock {
how = syscall.LOCK_EX
}
return syscall.Flock(int(l.f.Fd()), how|syscall.LOCK_NB)
}
func newLock(fileName string) (Releaser, error) {
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return nil, err
}
l := &unixLock{f}
err = l.set(true)
if err != nil {
f.Close()
return nil, err
}
return l, nil
}

View File

@ -0,0 +1,23 @@
package flock
import "syscall"
type windowsLock struct {
fd syscall.Handle
}
func (fl *windowsLock) Release() error {
return syscall.Close(fl.fd)
}
func newLock(fileName string) (Releaser, error) {
pathp, err := syscall.UTF16PtrFromString(fileName)
if err != nil {
return nil, err
}
fd, err := syscall.CreateFile(pathp, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.CREATE_ALWAYS, syscall.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil {
return nil, err
}
return &windowsLock{fd}, nil
}

View File

@ -0,0 +1,110 @@
// 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 httputil
import (
"net"
"net/http"
"net/url"
"time"
)
// NewClient returns a http.Client using the specified http.RoundTripper.
func NewClient(rt http.RoundTripper) *http.Client {
return &http.Client{Transport: rt}
}
// NewDeadlineClient returns a new http.Client which will time out long running
// requests.
func NewDeadlineClient(timeout time.Duration, proxyURL *url.URL) *http.Client {
return NewClient(NewDeadlineRoundTripper(timeout, proxyURL))
}
// NewDeadlineRoundTripper returns a new http.RoundTripper which will time out
// long running requests.
func NewDeadlineRoundTripper(timeout time.Duration, proxyURL *url.URL) http.RoundTripper {
return &http.Transport{
// Set proxy (if null, then becomes a direct connection)
Proxy: http.ProxyURL(proxyURL),
// We need to disable keepalive, because we set a deadline on the
// underlying connection.
DisableKeepAlives: true,
Dial: func(netw, addr string) (c net.Conn, err error) {
start := time.Now()
c, err = net.DialTimeout(netw, addr, timeout)
if err == nil {
c.SetDeadline(start.Add(timeout))
}
return
},
}
}
type bearerAuthRoundTripper struct {
bearerToken string
rt http.RoundTripper
}
// NewBearerAuthRoundTripper adds the provided bearer token to a request unless the authorization
// header has already been set.
func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
return &bearerAuthRoundTripper{bearer, rt}
}
func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Authorization")) == 0 {
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+rt.bearerToken)
}
return rt.rt.RoundTrip(req)
}
type basicAuthRoundTripper struct {
username string
password string
rt http.RoundTripper
}
// NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a request unless it has
// already been set.
func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
return &basicAuthRoundTripper{username, password, rt}
}
func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Authorization")) != 0 {
return rt.rt.RoundTrip(req)
}
req = cloneRequest(req)
req.SetBasicAuth(rt.username, rt.password)
return rt.rt.RoundTrip(req)
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// Shallow copy of the struct.
r2 := new(http.Request)
*r2 = *r
// Deep copy of the Header.
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}

View File

@ -0,0 +1,92 @@
// 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 httputil
import (
"compress/gzip"
"compress/zlib"
"io"
"net/http"
"strings"
)
const (
acceptEncodingHeader = "Accept-Encoding"
contentEncodingHeader = "Content-Encoding"
gzipEncoding = "gzip"
deflateEncoding = "deflate"
)
// Wrapper around http.Handler which adds suitable response compression based
// on the client's Accept-Encoding headers.
type compressedResponseWriter struct {
http.ResponseWriter
writer io.Writer
}
// Writes HTTP response content data.
func (c *compressedResponseWriter) Write(p []byte) (int, error) {
return c.writer.Write(p)
}
// Closes the compressedResponseWriter and ensures to flush all data before.
func (c *compressedResponseWriter) Close() {
if zlibWriter, ok := c.writer.(*zlib.Writer); ok {
zlibWriter.Flush()
}
if gzipWriter, ok := c.writer.(*gzip.Writer); ok {
gzipWriter.Flush()
}
if closer, ok := c.writer.(io.Closer); ok {
defer closer.Close()
}
}
// Constructs a new compressedResponseWriter based on client request headers.
func newCompressedResponseWriter(writer http.ResponseWriter, req *http.Request) *compressedResponseWriter {
encodings := strings.Split(req.Header.Get(acceptEncodingHeader), ",")
for _, encoding := range encodings {
switch strings.TrimSpace(encoding) {
case gzipEncoding:
writer.Header().Set(contentEncodingHeader, gzipEncoding)
return &compressedResponseWriter{
ResponseWriter: writer,
writer: gzip.NewWriter(writer),
}
case deflateEncoding:
writer.Header().Set(contentEncodingHeader, deflateEncoding)
return &compressedResponseWriter{
ResponseWriter: writer,
writer: zlib.NewWriter(writer),
}
}
}
return &compressedResponseWriter{
ResponseWriter: writer,
writer: writer,
}
}
// CompressionHandler is a wrapper around http.Handler which adds suitable
// response compression based on the client's Accept-Encoding headers.
type CompressionHandler struct {
Handler http.Handler
}
// ServeHTTP adds compression to the original http.Handler's ServeHTTP() method.
func (c CompressionHandler) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
compWriter := newCompressedResponseWriter(writer, req)
c.Handler.ServeHTTP(compWriter, req)
compWriter.Close()
}

102
prom/util/route/route.go Normal file
View File

@ -0,0 +1,102 @@
package route
import (
"net/http"
"sync"
"github.com/julienschmidt/httprouter"
"golang.org/x/net/context"
)
var (
mtx = sync.RWMutex{}
ctxts = map[*http.Request]context.Context{}
)
// Context returns the context for the request.
func Context(r *http.Request) context.Context {
mtx.RLock()
defer mtx.RUnlock()
return ctxts[r]
}
type param string
// Param returns param p for the context.
func Param(ctx context.Context, p string) string {
return ctx.Value(param(p)).(string)
}
// WithParam returns a new context with param p set to v.
func WithParam(ctx context.Context, p, v string) context.Context {
return context.WithValue(ctx, param(p), v)
}
// handle turns a Handle into httprouter.Handle
func handle(h http.HandlerFunc) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for _, p := range params {
ctx = context.WithValue(ctx, param(p.Key), p.Value)
}
mtx.Lock()
ctxts[r] = ctx
mtx.Unlock()
h(w, r)
mtx.Lock()
delete(ctxts, r)
mtx.Unlock()
}
}
// Router wraps httprouter.Router and adds support for prefixed sub-routers.
type Router struct {
rtr *httprouter.Router
prefix string
}
// New returns a new Router.
func New() *Router {
return &Router{rtr: httprouter.New()}
}
// WithPrefix returns a router that prefixes all registered routes with prefix.
func (r *Router) WithPrefix(prefix string) *Router {
return &Router{rtr: r.rtr, prefix: r.prefix + prefix}
}
// Get registers a new GET route.
func (r *Router) Get(path string, h http.HandlerFunc) {
r.rtr.GET(r.prefix+path, handle(h))
}
// Del registers a new DELETE route.
func (r *Router) Del(path string, h http.HandlerFunc) {
r.rtr.DELETE(r.prefix+path, handle(h))
}
// Post registers a new POST route.
func (r *Router) Post(path string, h http.HandlerFunc) {
r.rtr.POST(r.prefix+path, handle(h))
}
// ServeHTTP implements http.Handler.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.rtr.ServeHTTP(w, req)
}
// FileServe returns a new http.HandlerFunc that serves files from dir.
// Using routes must provide the *filepath parameter.
func FileServe(dir string) http.HandlerFunc {
fs := http.FileServer(http.Dir(dir))
return func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = Param(Context(r), "filepath")
fs.ServeHTTP(w, r)
}
}

View File

@ -0,0 +1,78 @@
// 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 stats
// QueryTiming identifies the code area or functionality in which time is spent
// during a query.
type QueryTiming int
// Query timings.
const (
TotalEvalTime QueryTiming = iota
ResultSortTime
JSONEncodeTime
PreloadTime
TotalQueryPreparationTime
InnerViewBuildingTime
InnerEvalTime
ResultAppendTime
QueryAnalysisTime
GetValueAtTimeTime
GetBoundaryValuesTime
GetRangeValuesTime
ExecQueueTime
ViewDiskPreparationTime
ViewDataExtractionTime
ViewDiskExtractionTime
)
// Return a string represenation of a QueryTiming identifier.
func (s QueryTiming) String() string {
switch s {
case TotalEvalTime:
return "Total eval time"
case ResultSortTime:
return "Result sorting time"
case JSONEncodeTime:
return "JSON encoding time"
case PreloadTime:
return "Query preloading time"
case TotalQueryPreparationTime:
return "Total query preparation time"
case InnerViewBuildingTime:
return "Inner view building time"
case InnerEvalTime:
return "Inner eval time"
case ResultAppendTime:
return "Result append time"
case QueryAnalysisTime:
return "Query analysis time"
case GetValueAtTimeTime:
return "GetValueAtTime() time"
case GetBoundaryValuesTime:
return "GetBoundaryValues() time"
case GetRangeValuesTime:
return "GetRangeValues() time"
case ExecQueueTime:
return "Exec queue wait time"
case ViewDiskPreparationTime:
return "View building disk preparation time"
case ViewDataExtractionTime:
return "Total view data extraction time"
case ViewDiskExtractionTime:
return "View disk data extraction time"
default:
return "Unknown query timing"
}
}

109
prom/util/stats/timer.go Normal file
View File

@ -0,0 +1,109 @@
// 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 stats
import (
"bytes"
"fmt"
"sort"
"time"
)
// A Timer that can be started and stopped and accumulates the total time it
// was running (the time between Start() and Stop()).
type Timer struct {
name fmt.Stringer
created time.Time
start time.Time
duration time.Duration
}
// Start the timer.
func (t *Timer) Start() *Timer {
t.start = time.Now()
return t
}
// Stop the timer.
func (t *Timer) Stop() {
t.duration += time.Since(t.start)
}
// ElapsedTime returns the time that passed since starting the timer.
func (t *Timer) ElapsedTime() time.Duration {
return time.Since(t.start)
}
// Return a string representation of the Timer.
func (t *Timer) String() string {
return fmt.Sprintf("%s: %s", t.name, t.duration)
}
// A TimerGroup represents a group of timers relevant to a single query.
type TimerGroup struct {
timers map[fmt.Stringer]*Timer
child *TimerGroup
}
// NewTimerGroup constructs a new TimerGroup.
func NewTimerGroup() *TimerGroup {
return &TimerGroup{timers: map[fmt.Stringer]*Timer{}}
}
// GetTimer gets (and creates, if necessary) the Timer for a given code section.
func (t *TimerGroup) GetTimer(name fmt.Stringer) *Timer {
if timer, exists := t.timers[name]; exists {
return timer
}
timer := &Timer{
name: name,
created: time.Now(),
}
t.timers[name] = timer
return timer
}
// Timers is a slice of Timer pointers that implements Len and Swap from
// sort.Interface.
type Timers []*Timer
type byCreationTimeSorter struct{ Timers }
// Len implements sort.Interface.
func (t Timers) Len() int {
return len(t)
}
// Swap implements sort.Interface.
func (t Timers) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
func (s byCreationTimeSorter) Less(i, j int) bool {
return s.Timers[i].created.Before(s.Timers[j].created)
}
// Return a string representation of a TimerGroup.
func (t *TimerGroup) String() string {
timers := byCreationTimeSorter{}
for _, timer := range t.timers {
timers.Timers = append(timers.Timers, timer)
}
sort.Sort(timers)
result := &bytes.Buffer{}
for _, timer := range timers.Timers {
fmt.Fprintf(result, "%s\n", timer)
}
return result.String()
}

View File

@ -0,0 +1,114 @@
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strutil
import (
"fmt"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
var (
durationRE = regexp.MustCompile("^([0-9]+)([ywdhms]+)$")
invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
)
// DurationToString formats a time.Duration as a string with the assumption that
// a year always has 365 days and a day always has 24h. (The former doesn't work
// in leap years, the latter is broken by DST switches, not to speak about leap
// seconds, but those are not even treated properly by the duration strings in
// the standard library.)
func DurationToString(duration time.Duration) string {
seconds := int64(duration / time.Second)
factors := map[string]int64{
"y": 60 * 60 * 24 * 365,
"d": 60 * 60 * 24,
"h": 60 * 60,
"m": 60,
"s": 1,
}
unit := "s"
switch int64(0) {
case seconds % factors["y"]:
unit = "y"
case seconds % factors["d"]:
unit = "d"
case seconds % factors["h"]:
unit = "h"
case seconds % factors["m"]:
unit = "m"
}
return fmt.Sprintf("%v%v", seconds/factors[unit], unit)
}
// StringToDuration parses a string into a time.Duration, assuming that a year
// always has 365d, a week 7d, a day 24h. See DurationToString for problems with
// that.
func StringToDuration(durationStr string) (duration time.Duration, err error) {
matches := durationRE.FindStringSubmatch(durationStr)
if len(matches) != 3 {
err = fmt.Errorf("not a valid duration string: %q", durationStr)
return
}
durationSeconds, _ := strconv.Atoi(matches[1])
duration = time.Duration(durationSeconds) * time.Second
unit := matches[2]
switch unit {
case "y":
duration *= 60 * 60 * 24 * 365
case "w":
duration *= 60 * 60 * 24 * 7
case "d":
duration *= 60 * 60 * 24
case "h":
duration *= 60 * 60
case "m":
duration *= 60
case "s":
duration *= 1
default:
return 0, fmt.Errorf("invalid time unit in duration string: %q", unit)
}
return
}
// TableLinkForExpression creates an escaped relative link to the table view of
// the provided expression.
func TableLinkForExpression(expr string) string {
// url.QueryEscape percent-escapes everything except spaces, for which it
// uses "+". However, in the non-query part of a URI, only percent-escaped
// spaces are legal, so we need to manually replace "+" with "%20" after
// query-escaping the string.
//
// See also:
// http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20.
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":1}]`, expr))
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
}
// GraphLinkForExpression creates an escaped relative link to the graph view of
// the provided expression.
func GraphLinkForExpression(expr string) string {
urlData := url.QueryEscape(fmt.Sprintf(`[{"expr":%q,"tab":0}]`, expr))
return fmt.Sprintf("/graph#%s", strings.Replace(urlData, "+", "%20", -1))
}
// SanitizeLabelName replaces anything that doesn't match
// client_label.LabelNameRE with an underscore.
func SanitizeLabelName(name string) string {
return invalidLabelCharRE.ReplaceAllString(name, "_")
}

View File

@ -0,0 +1,121 @@
// Copyright 2013 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"io/ioutil"
"os"
)
const (
// The base directory used for test emissions, which instructs the operating
// system to use the default temporary directory as the base or TMPDIR
// environment variable.
defaultDirectory = ""
// NilCloser is a no-op Closer.
NilCloser = nilCloser(true)
)
type (
// Closer is the interface that wraps the Close method.
Closer interface {
// Close reaps the underlying directory and its children. The directory
// could be deleted by its users already.
Close()
}
nilCloser bool
// TemporaryDirectory models a closeable path for transient POSIX disk
// activities.
TemporaryDirectory interface {
Closer
// Path returns the underlying path for access.
Path() string
}
// temporaryDirectory is kept as a private type due to private fields and
// their interactions.
temporaryDirectory struct {
path string
tester T
}
callbackCloser struct {
fn func()
}
// T implements the needed methods of testing.TB so that we do not need
// to actually import testing (which has the side affect of adding all
// the test flags, which we do not want in non-test binaries even if
// they make use of these utilities for some reason).
T interface {
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
}
)
func (c nilCloser) Close() {
}
func (c callbackCloser) Close() {
c.fn()
}
// NewCallbackCloser returns a Closer that calls the provided function upon
// closing.
func NewCallbackCloser(fn func()) *callbackCloser {
return &callbackCloser{
fn: fn,
}
}
func (t temporaryDirectory) Close() {
err := os.RemoveAll(t.path)
if err != nil {
switch {
case os.IsNotExist(err):
return
default:
t.tester.Fatal(err)
}
}
}
func (t temporaryDirectory) Path() string {
return t.path
}
// NewTemporaryDirectory creates a new temporary directory for transient POSIX
// activities.
func NewTemporaryDirectory(name string, t T) (handler TemporaryDirectory) {
var (
directory string
err error
)
directory, err = ioutil.TempDir(defaultDirectory, name)
if err != nil {
t.Fatal(err)
}
handler = temporaryDirectory{
path: directory,
tester: t,
}
return
}

View File

@ -0,0 +1,31 @@
// 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 testutil
// ErrorEqual compares Go errors for equality.
func ErrorEqual(left, right error) bool {
if left == right {
return true
}
if left != nil && right != nil {
if left.Error() == right.Error() {
return true
}
return false
}
return false
}