Fixing conflicts with commit c9b85afd93

This commit is contained in:
Yuri Nikolic 2023-03-08 17:18:22 +01:00
commit c7d730f549
31 changed files with 574 additions and 696 deletions

5
.gitignore vendored
View file

@ -19,7 +19,7 @@ benchmark.txt
!/.promu.yml
!/.golangci.yml
/documentation/examples/remote_storage/remote_storage_adapter/remote_storage_adapter
/documentation/examples/remote_storage/example_write_adapter/example_writer_adapter
/documentation/examples/remote_storage/example_write_adapter/example_write_adapter
npm_licenses.tar.bz2
/web/ui/static/react
@ -28,3 +28,6 @@ npm_licenses.tar.bz2
/.build
/**/node_modules
# Ignore parser debug
y.output

View file

@ -78,3 +78,20 @@ GO111MODULE=on go mod tidy
```
You have to commit the changes to `go.mod` and `go.sum` before submitting the pull request.
## Working with the PromQL parser
The PromQL parser grammar is located in `promql/parser/generated_parser.y` and it can be built using `make parser`.
The parser is built using [goyacc](https://pkg.go.dev/golang.org/x/tools/cmd/goyacc)
If doing some sort of debugging, then it is possible to add some verbose output. After generating the parser, then you
can modify the the `./promql/parser/generated_parser.y.go` manually.
```golang
// As of writing this was somewhere around line 600.
var (
yyDebug = 0 // This can be be a number 0 -> 5.
yyErrorVerbose = false // This can be set to true.
)
```

View file

@ -78,6 +78,17 @@ assets-tarball: assets
@echo '>> packaging assets'
scripts/package_assets.sh
# We only want to generate the parser when there's changes to the grammar.
.PHONY: parser
parser:
@echo ">> running goyacc to generate the .go file."
ifeq (, $(shell which goyacc))
@echo "goyacc not installed so skipping"
@echo "To install: go install golang.org/x/tools/cmd/goyacc@v0.6.0"
else
goyacc -o promql/parser/generated_parser.y.go promql/parser/generated_parser.y
endif
.PHONY: test
# If we only want to only test go code we have to change the test target
# which is called by all.

View file

@ -9,7 +9,7 @@ require (
github.com/influxdata/influxdb v1.11.0
github.com/prometheus/client_golang v1.14.0
github.com/prometheus/common v0.37.0
github.com/stretchr/testify v1.8.1
github.com/stretchr/testify v1.8.2
gopkg.in/alecthomas/kingpin.v2 v2.2.6
)

View file

@ -228,8 +228,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

13
go.mod
View file

@ -7,10 +7,9 @@ require (
github.com/Azure/go-autorest/autorest v0.11.28
github.com/Azure/go-autorest/autorest/adal v0.9.22
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137
github.com/aws/aws-sdk-go v1.44.187
github.com/aws/aws-sdk-go v1.44.207
github.com/cespare/xxhash/v2 v2.2.0
github.com/dennwc/varint v1.0.0
github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245
github.com/digitalocean/godo v1.95.0
github.com/docker/docker v20.10.23+incompatible
github.com/edsrzf/mmap-go v1.1.0
@ -60,11 +59,11 @@ require (
go.opentelemetry.io/otel/trace v1.11.2
go.uber.org/atomic v1.10.0
go.uber.org/automaxprocs v1.5.1
go.uber.org/goleak v1.2.0
golang.org/x/net v0.5.0
go.uber.org/goleak v1.2.1
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.4.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.4.0
golang.org/x/sys v0.5.0
golang.org/x/time v0.3.0
golang.org/x/tools v0.5.0
google.golang.org/api v0.108.0
@ -175,8 +174,8 @@ require (
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/exp v0.0.0-20230124195608-d38c7dcee874
golang.org/x/mod v0.7.0 // indirect
golang.org/x/term v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect

27
go.sum
View file

@ -97,8 +97,8 @@ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:W
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.44.187 h1:D5CsRomPnlwDHJCanL2mtaLIcbhjiWxNh5j8zvaWdJA=
github.com/aws/aws-sdk-go v1.44.187/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go v1.44.207 h1:7O0AMKxTm+/GUx6zw+3dqc+fD3tTzv8xaZPYo+ywRwE=
github.com/aws/aws-sdk-go v1.44.207/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -148,8 +148,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE=
github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245 h1:9cOfvEwjQxdwKuNDTQSaMKNRvwKwgZG+U4HrjeRKHso=
github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/digitalocean/godo v1.95.0 h1:S48/byPKui7RHZc1wYEPfRvkcEvToADNb5I3guu95xg=
github.com/digitalocean/godo v1.95.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
@ -808,8 +806,8 @@ go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
@ -857,7 +855,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@ -919,8 +916,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1011,13 +1008,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1027,8 +1024,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View file

@ -18,11 +18,11 @@ package labels
import (
"bytes"
"encoding/json"
"sort"
"strconv"
"github.com/cespare/xxhash/v2"
"github.com/prometheus/common/model"
"golang.org/x/exp/slices"
)
// Well-known label names used by Prometheus components.
@ -360,7 +360,7 @@ func EmptyLabels() Labels {
func New(ls ...Label) Labels {
set := make(Labels, 0, len(ls))
set = append(set, ls...)
sort.Sort(set)
slices.SortFunc(set, func(a, b Label) bool { return a.Name < b.Name })
return set
}
@ -384,7 +384,7 @@ func FromStrings(ss ...string) Labels {
res = append(res, Label{Name: ss[i], Value: ss[i+1]})
}
sort.Sort(res)
slices.SortFunc(res, func(a, b Label) bool { return a.Name < b.Name })
return res
}
@ -564,7 +564,7 @@ Outer:
}
if len(b.add) > 0 { // Base is already in order, so we only need to sort if we add to it.
res = append(res, b.add...)
sort.Sort(res)
slices.SortFunc(res, func(a, b Label) bool { return a.Name < b.Name })
}
return res
}
@ -591,7 +591,7 @@ func (b *ScratchBuilder) Add(name, value string) {
// Sort the labels added so far by name.
func (b *ScratchBuilder) Sort() {
sort.Sort(b.add)
slices.SortFunc(b.add, func(a, b Label) bool { return a.Name < b.Name })
}
// Asssign is for when you already have a Labels which you want this ScratchBuilder to return.

View file

@ -19,12 +19,12 @@ import (
"bytes"
"encoding/json"
"reflect"
"sort"
"strconv"
"unsafe"
"github.com/cespare/xxhash/v2"
"github.com/prometheus/common/model"
"golang.org/x/exp/slices"
)
// Well-known label names used by Prometheus components.
@ -385,7 +385,7 @@ func yoloBytes(s string) (b []byte) {
// New returns a sorted Labels from the given labels.
// The caller has to guarantee that all label names are unique.
func New(ls ...Label) Labels {
sort.Sort(labelSlice(ls))
slices.SortFunc(ls, func(a, b Label) bool { return a.Name < b.Name })
size := labelsSize(ls)
buf := make([]byte, size)
marshalLabelsToSizedBuffer(ls, buf)
@ -411,7 +411,7 @@ func FromStrings(ss ...string) Labels {
ls = append(ls, Label{Name: ss[i], Value: ss[i+1]})
}
sort.Sort(labelSlice(ls))
slices.SortFunc(ls, func(a, b Label) bool { return a.Name < b.Name })
return New(ls...)
}
@ -595,11 +595,12 @@ func (b *Builder) Labels(res Labels) Labels {
return b.base
}
sort.Sort(labelSlice(b.add))
sort.Strings(b.del)
slices.SortFunc(b.add, func(a, b Label) bool { return a.Name < b.Name })
slices.Sort(b.del)
a, d := 0, 0
buf := make([]byte, 0, len(b.base.data)) // TODO: see if we can re-use the buffer from res.
bufSize := len(b.base.data) + labelsSize(b.add)
buf := make([]byte, 0, bufSize) // TODO: see if we can re-use the buffer from res.
for pos := 0; pos < len(b.base.data); {
oldPos := pos
var lName string
@ -753,7 +754,7 @@ func (b *ScratchBuilder) Add(name, value string) {
// Sort the labels added so far by name.
func (b *ScratchBuilder) Sort() {
sort.Sort(labelSlice(b.add))
slices.SortFunc(b.add, func(a, b Label) bool { return a.Name < b.Name })
}
// Asssign is for when you already have a Labels which you want this ScratchBuilder to return.

View file

@ -696,6 +696,50 @@ func BenchmarkLabels_Hash(b *testing.B) {
}
}
func BenchmarkBuilder(b *testing.B) {
m := []Label{
{"job", "node"},
{"instance", "123.123.1.211:9090"},
{"path", "/api/v1/namespaces/<namespace>/deployments/<name>"},
{"method", "GET"},
{"namespace", "system"},
{"status", "500"},
{"prometheus", "prometheus-core-1"},
{"datacenter", "eu-west-1"},
{"pod_name", "abcdef-99999-defee"},
}
var l Labels
builder := NewBuilder(EmptyLabels())
for i := 0; i < b.N; i++ {
builder.Reset(EmptyLabels())
for _, l := range m {
builder.Set(l.Name, l.Value)
}
l = builder.Labels(EmptyLabels())
}
require.Equal(b, 9, l.Len())
}
func BenchmarkLabels_Copy(b *testing.B) {
m := map[string]string{
"job": "node",
"instance": "123.123.1.211:9090",
"path": "/api/v1/namespaces/<namespace>/deployments/<name>",
"method": "GET",
"namespace": "system",
"status": "500",
"prometheus": "prometheus-core-1",
"datacenter": "eu-west-1",
"pod_name": "abcdef-99999-defee",
}
l := FromMap(m)
for i := 0; i < b.N; i++ {
l = l.Copy()
}
}
func TestMarshaling(t *testing.T) {
lbls := FromStrings("aaa", "111", "bbb", "2222", "ccc", "33333")
expectedJSON := "{\"aaa\":\"111\",\"bbb\":\"2222\",\"ccc\":\"33333\"}"

View file

@ -15,6 +15,7 @@ package relabel
import (
"crypto/md5"
"encoding/binary"
"fmt"
"strings"
@ -268,7 +269,9 @@ func relabel(lset labels.Labels, cfg *Config, lb *labels.Builder) (ret labels.La
case Uppercase:
lb.Set(cfg.TargetLabel, strings.ToUpper(val))
case HashMod:
mod := sum64(md5.Sum([]byte(val))) % cfg.Modulus
hash := md5.Sum([]byte(val))
// Use only the last 8 bytes of the hash to give the same result as earlier versions of this code.
mod := binary.BigEndian.Uint64(hash[8:]) % cfg.Modulus
lb.Set(cfg.TargetLabel, fmt.Sprintf("%d", mod))
case LabelMap:
lset.Range(func(l labels.Label) {
@ -295,15 +298,3 @@ func relabel(lset labels.Labels, cfg *Config, lb *labels.Builder) (ret labels.La
return lb.Labels(lset), true
}
// sum64 sums the md5 hash to an uint64.
func sum64(hash [md5.Size]byte) uint64 {
var s uint64
for i, b := range hash {
shift := uint64((md5.Size - i - 1) * 8)
s |= uint64(b) << shift
}
return s
}

View file

@ -17,7 +17,6 @@
package textparse
import (
"bytes"
"errors"
"fmt"
"io"
@ -31,8 +30,6 @@ import (
"github.com/prometheus/prometheus/model/value"
)
var allowedSuffixes = [][]byte{[]byte("_total"), []byte("_bucket")}
type openMetricsLexer struct {
b []byte
i int
@ -46,13 +43,6 @@ func (l *openMetricsLexer) buf() []byte {
return l.b[l.start:l.i]
}
func (l *openMetricsLexer) cur() byte {
if l.i < len(l.b) {
return l.b[l.i]
}
return byte(' ')
}
// next advances the openMetricsLexer to the next character.
func (l *openMetricsLexer) next() byte {
l.i++
@ -223,6 +213,14 @@ func (p *OpenMetricsParser) nextToken() token {
return tok
}
func (p *OpenMetricsParser) parseError(exp string, got token) error {
e := p.l.i + 1
if len(p.l.b) < e {
e = len(p.l.b)
}
return fmt.Errorf("%s, got %q (%q) while parsing: %q", exp, p.l.b[p.l.start:e], got, p.l.b[p.start:e])
}
// Next advances the parser to the next sample. It returns false if no
// more samples were read or an error occurred.
func (p *OpenMetricsParser) Next() (Entry, error) {
@ -248,7 +246,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
case tMName:
p.offsets = append(p.offsets, p.l.start, p.l.i)
default:
return EntryInvalid, parseError("expected metric name after "+t.String(), t2)
return EntryInvalid, p.parseError("expected metric name after "+t.String(), t2)
}
switch t2 := p.nextToken(); t2 {
case tText:
@ -284,7 +282,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
}
case tHelp:
if !utf8.Valid(p.text) {
return EntryInvalid, errors.New("help text is not a valid utf8 string")
return EntryInvalid, fmt.Errorf("help text %q is not a valid utf8 string", p.text)
}
}
switch t {
@ -297,7 +295,7 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
u := yoloString(p.text)
if len(u) > 0 {
if !strings.HasSuffix(m, u) || len(m) < len(u)+1 || p.l.b[p.offsets[1]-len(u)-1] != '_' {
return EntryInvalid, fmt.Errorf("unit not a suffix of metric %q", m)
return EntryInvalid, fmt.Errorf("unit %q not a suffix of metric %q", u, m)
}
}
return EntryUnit, nil
@ -336,10 +334,10 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
var ts float64
// A float is enough to hold what we need for millisecond resolution.
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
return EntryInvalid, err
return EntryInvalid, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
}
if math.IsNaN(ts) || math.IsInf(ts, 0) {
return EntryInvalid, errors.New("invalid timestamp")
return EntryInvalid, fmt.Errorf("invalid timestamp %f", ts)
}
p.ts = int64(ts * 1000)
switch t3 := p.nextToken(); t3 {
@ -349,26 +347,20 @@ func (p *OpenMetricsParser) Next() (Entry, error) {
return EntryInvalid, err
}
default:
return EntryInvalid, parseError("expected next entry after timestamp", t3)
return EntryInvalid, p.parseError("expected next entry after timestamp", t3)
}
default:
return EntryInvalid, parseError("expected timestamp or # symbol", t2)
return EntryInvalid, p.parseError("expected timestamp or # symbol", t2)
}
return EntrySeries, nil
default:
err = fmt.Errorf("%q %q is not a valid start token", t, string(p.l.cur()))
err = p.parseError("expected a valid start token", t)
}
return EntryInvalid, err
}
func (p *OpenMetricsParser) parseComment() error {
// Validate the name of the metric. It must have _total or _bucket as
// suffix for exemplars to be supported.
if err := p.validateNameForExemplar(p.series[:p.offsets[0]-p.start]); err != nil {
return err
}
var err error
// Parse the labels.
p.eOffsets, err = p.parseLVals(p.eOffsets)
@ -395,19 +387,19 @@ func (p *OpenMetricsParser) parseComment() error {
var ts float64
// A float is enough to hold what we need for millisecond resolution.
if ts, err = parseFloat(yoloString(p.l.buf()[1:])); err != nil {
return err
return fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
}
if math.IsNaN(ts) || math.IsInf(ts, 0) {
return errors.New("invalid exemplar timestamp")
return fmt.Errorf("invalid exemplar timestamp %f", ts)
}
p.exemplarTs = int64(ts * 1000)
switch t3 := p.nextToken(); t3 {
case tLinebreak:
default:
return parseError("expected next entry after exemplar timestamp", t3)
return p.parseError("expected next entry after exemplar timestamp", t3)
}
default:
return parseError("expected timestamp or comment", t2)
return p.parseError("expected timestamp or comment", t2)
}
return nil
}
@ -421,21 +413,21 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
return offsets, nil
case tComma:
if first {
return nil, parseError("expected label name or left brace", t)
return nil, p.parseError("expected label name or left brace", t)
}
t = p.nextToken()
if t != tLName {
return nil, parseError("expected label name", t)
return nil, p.parseError("expected label name", t)
}
case tLName:
if !first {
return nil, parseError("expected comma", t)
return nil, p.parseError("expected comma", t)
}
default:
if first {
return nil, parseError("expected label name or left brace", t)
return nil, p.parseError("expected label name or left brace", t)
}
return nil, parseError("expected comma or left brace", t)
return nil, p.parseError("expected comma or left brace", t)
}
first = false
@ -444,13 +436,13 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
offsets = append(offsets, p.l.start, p.l.i)
if t := p.nextToken(); t != tEqual {
return nil, parseError("expected equal", t)
return nil, p.parseError("expected equal", t)
}
if t := p.nextToken(); t != tLValue {
return nil, parseError("expected label value", t)
return nil, p.parseError("expected label value", t)
}
if !utf8.Valid(p.l.buf()) {
return nil, errors.New("invalid UTF-8 label value")
return nil, fmt.Errorf("invalid UTF-8 label value: %q", p.l.buf())
}
// The openMetricsLexer ensures the value string is quoted. Strip first
@ -461,11 +453,11 @@ func (p *OpenMetricsParser) parseLVals(offsets []int) ([]int, error) {
func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error) {
if t != tValue {
return 0, parseError(fmt.Sprintf("expected value after %v", after), t)
return 0, p.parseError(fmt.Sprintf("expected value after %v", after), t)
}
val, err := parseFloat(yoloString(p.l.buf()[1:]))
if err != nil {
return 0, err
return 0, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
}
// Ensure canonical NaN value.
if math.IsNaN(p.exemplarVal) {
@ -473,12 +465,3 @@ func (p *OpenMetricsParser) getFloatValue(t token, after string) (float64, error
}
return val, nil
}
func (p *OpenMetricsParser) validateNameForExemplar(name []byte) error {
for _, suffix := range allowedSuffixes {
if bytes.HasSuffix(name, suffix) {
return nil
}
}
return fmt.Errorf("metric name %v does not support exemplars", string(name))
}

View file

@ -45,9 +45,14 @@ hh_bucket{le="+Inf"} 1
# TYPE gh gaugehistogram
gh_bucket{le="+Inf"} 1
# TYPE hhh histogram
hhh_bucket{le="+Inf"} 1 # {aa="bb"} 4
hhh_bucket{le="+Inf"} 1 # {id="histogram-bucket-test"} 4
hhh_count 1 # {id="histogram-count-test"} 4
# TYPE ggh gaugehistogram
ggh_bucket{le="+Inf"} 1 # {cc="dd",xx="yy"} 4 123.123
ggh_bucket{le="+Inf"} 1 # {id="gaugehistogram-bucket-test",xx="yy"} 4 123.123
ggh_count 1 # {id="gaugehistogram-count-test",xx="yy"} 4 123.123
# TYPE smr_seconds summary
smr_seconds_count 2.0 # {id="summary-count-test"} 1 123.321
smr_seconds_sum 42.0 # {id="summary-sum-test"} 1 123.321
# TYPE ii info
ii{foo="bar"} 1
# TYPE ss stateset
@ -59,7 +64,7 @@ _metric_starting_with_underscore 1
testmetric{_label_starting_with_underscore="foo"} 1
testmetric{label="\"bar\""} 1
# TYPE foo counter
foo_total 17.0 1520879607.789 # {xx="yy"} 5`
foo_total 17.0 1520879607.789 # {id="counter-test"} 5`
input += "\n# HELP metric foo\x00bar"
input += "\nnull_byte_metric{a=\"abc\x00\"} 1"
@ -152,7 +157,12 @@ foo_total 17.0 1520879607.789 # {xx="yy"} 5`
m: `hhh_bucket{le="+Inf"}`,
v: 1,
lset: labels.FromStrings("__name__", "hhh_bucket", "le", "+Inf"),
e: &exemplar.Exemplar{Labels: labels.FromStrings("aa", "bb"), Value: 4},
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "histogram-bucket-test"), Value: 4},
}, {
m: `hhh_count`,
v: 1,
lset: labels.FromStrings("__name__", "hhh_count"),
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "histogram-count-test"), Value: 4},
}, {
m: "ggh",
typ: MetricTypeGaugeHistogram,
@ -160,7 +170,25 @@ foo_total 17.0 1520879607.789 # {xx="yy"} 5`
m: `ggh_bucket{le="+Inf"}`,
v: 1,
lset: labels.FromStrings("__name__", "ggh_bucket", "le", "+Inf"),
e: &exemplar.Exemplar{Labels: labels.FromStrings("cc", "dd", "xx", "yy"), Value: 4, HasTs: true, Ts: 123123},
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "gaugehistogram-bucket-test", "xx", "yy"), Value: 4, HasTs: true, Ts: 123123},
}, {
m: `ggh_count`,
v: 1,
lset: labels.FromStrings("__name__", "ggh_count"),
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "gaugehistogram-count-test", "xx", "yy"), Value: 4, HasTs: true, Ts: 123123},
}, {
m: "smr_seconds",
typ: MetricTypeSummary,
}, {
m: `smr_seconds_count`,
v: 2,
lset: labels.FromStrings("__name__", "smr_seconds_count"),
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "summary-count-test"), Value: 1, HasTs: true, Ts: 123321},
}, {
m: `smr_seconds_sum`,
v: 42,
lset: labels.FromStrings("__name__", "smr_seconds_sum"),
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "summary-sum-test"), Value: 1, HasTs: true, Ts: 123321},
}, {
m: "ii",
typ: MetricTypeInfo,
@ -206,7 +234,7 @@ foo_total 17.0 1520879607.789 # {xx="yy"} 5`
v: 17,
lset: labels.FromStrings("__name__", "foo_total"),
t: int64p(1520879607789),
e: &exemplar.Exemplar{Labels: labels.FromStrings("xx", "yy"), Value: 5},
e: &exemplar.Exemplar{Labels: labels.FromStrings("id", "counter-test"), Value: 5},
}, {
m: "metric",
help: "foo\x00bar",
@ -293,11 +321,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "\n",
err: "\"INVALID\" \"\\n\" is not a valid start token",
err: "expected a valid start token, got \"\\n\" (\"INVALID\") while parsing: \"\\n\"",
},
{
input: "metric",
err: "expected value after metric, got \"EOF\"",
err: "expected value after metric, got \"metric\" (\"EOF\") while parsing: \"metric\"",
},
{
input: "metric 1",
@ -313,19 +341,19 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "a\n#EOF\n",
err: "expected value after metric, got \"INVALID\"",
err: "expected value after metric, got \"\\n\" (\"INVALID\") while parsing: \"a\\n\"",
},
{
input: "\n\n#EOF\n",
err: "\"INVALID\" \"\\n\" is not a valid start token",
err: "expected a valid start token, got \"\\n\" (\"INVALID\") while parsing: \"\\n\"",
},
{
input: " a 1\n#EOF\n",
err: "\"INVALID\" \" \" is not a valid start token",
err: "expected a valid start token, got \" \" (\"INVALID\") while parsing: \" \"",
},
{
input: "9\n#EOF\n",
err: "\"INVALID\" \"9\" is not a valid start token",
err: "expected a valid start token, got \"9\" (\"INVALID\") while parsing: \"9\"",
},
{
input: "# TYPE u untyped\n#EOF\n",
@ -337,11 +365,11 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "# TYPE c counter\n#EOF\n",
err: "\"INVALID\" \" \" is not a valid start token",
err: "expected a valid start token, got \"# \" (\"INVALID\") while parsing: \"# \"",
},
{
input: "# TYPE \n#EOF\n",
err: "expected metric name after TYPE, got \"INVALID\"",
err: "expected metric name after TYPE, got \"\\n\" (\"INVALID\") while parsing: \"# TYPE \\n\"",
},
{
input: "# TYPE m\n#EOF\n",
@ -349,19 +377,19 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "# UNIT metric suffix\n#EOF\n",
err: "unit not a suffix of metric \"metric\"",
err: "unit \"suffix\" not a suffix of metric \"metric\"",
},
{
input: "# UNIT metricsuffix suffix\n#EOF\n",
err: "unit not a suffix of metric \"metricsuffix\"",
err: "unit \"suffix\" not a suffix of metric \"metricsuffix\"",
},
{
input: "# UNIT m suffix\n#EOF\n",
err: "unit not a suffix of metric \"m\"",
err: "unit \"suffix\" not a suffix of metric \"m\"",
},
{
input: "# UNIT \n#EOF\n",
err: "expected metric name after UNIT, got \"INVALID\"",
err: "expected metric name after UNIT, got \"\\n\" (\"INVALID\") while parsing: \"# UNIT \\n\"",
},
{
input: "# UNIT m\n#EOF\n",
@ -369,7 +397,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "# HELP \n#EOF\n",
err: "expected metric name after HELP, got \"INVALID\"",
err: "expected metric name after HELP, got \"\\n\" (\"INVALID\") while parsing: \"# HELP \\n\"",
},
{
input: "# HELP m\n#EOF\n",
@ -377,27 +405,27 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "a\t1\n#EOF\n",
err: "expected value after metric, got \"INVALID\"",
err: "expected value after metric, got \"\\t\" (\"INVALID\") while parsing: \"a\\t\"",
},
{
input: "a 1\t2\n#EOF\n",
err: "strconv.ParseFloat: parsing \"1\\t2\": invalid syntax",
err: "strconv.ParseFloat: parsing \"1\\t2\": invalid syntax while parsing: \"a 1\\t2\"",
},
{
input: "a 1 2 \n#EOF\n",
err: "expected next entry after timestamp, got \"INVALID\"",
err: "expected next entry after timestamp, got \" \\n\" (\"INVALID\") while parsing: \"a 1 2 \\n\"",
},
{
input: "a 1 2 #\n#EOF\n",
err: "expected next entry after timestamp, got \"TIMESTAMP\"",
err: "expected next entry after timestamp, got \" #\\n\" (\"TIMESTAMP\") while parsing: \"a 1 2 #\\n\"",
},
{
input: "a 1 1z\n#EOF\n",
err: "strconv.ParseFloat: parsing \"1z\": invalid syntax",
err: "strconv.ParseFloat: parsing \"1z\": invalid syntax while parsing: \"a 1 1z\"",
},
{
input: " # EOF\n",
err: "\"INVALID\" \" \" is not a valid start token",
err: "expected a valid start token, got \" \" (\"INVALID\") while parsing: \" \"",
},
{
input: "# EOF\na 1",
@ -413,7 +441,7 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "#\tTYPE c counter\n",
err: "\"INVALID\" \"\\t\" is not a valid start token",
err: "expected a valid start token, got \"#\\t\" (\"INVALID\") while parsing: \"#\\t\"",
},
{
input: "# TYPE c counter\n",
@ -421,135 +449,131 @@ func TestOpenMetricsParseErrors(t *testing.T) {
},
{
input: "a 1 1 1\n# EOF\n",
err: "expected next entry after timestamp, got \"TIMESTAMP\"",
err: "expected next entry after timestamp, got \" 1\\n\" (\"TIMESTAMP\") while parsing: \"a 1 1 1\\n\"",
},
{
input: "a{b='c'} 1\n# EOF\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"'\" (\"INVALID\") while parsing: \"a{b='\"",
},
{
input: "a{b=\"c\",} 1\n# EOF\n",
err: "expected label name, got \"BCLOSE\"",
err: "expected label name, got \"} \" (\"BCLOSE\") while parsing: \"a{b=\\\"c\\\",} \"",
},
{
input: "a{,b=\"c\"} 1\n# EOF\n",
err: "expected label name or left brace, got \"COMMA\"",
err: "expected label name or left brace, got \",b\" (\"COMMA\") while parsing: \"a{,b\"",
},
{
input: "a{b=\"c\"d=\"e\"} 1\n# EOF\n",
err: "expected comma, got \"LNAME\"",
err: "expected comma, got \"d=\" (\"LNAME\") while parsing: \"a{b=\\\"c\\\"d=\"",
},
{
input: "a{b=\"c\",,d=\"e\"} 1\n# EOF\n",
err: "expected label name, got \"COMMA\"",
err: "expected label name, got \",d\" (\"COMMA\") while parsing: \"a{b=\\\"c\\\",,d\"",
},
{
input: "a{b=\n# EOF\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\n\" (\"INVALID\") while parsing: \"a{b=\\n\"",
},
{
input: "a{\xff=\"foo\"} 1\n# EOF\n",
err: "expected label name or left brace, got \"INVALID\"",
err: "expected label name or left brace, got \"\\xff\" (\"INVALID\") while parsing: \"a{\\xff\"",
},
{
input: "a{b=\"\xff\"} 1\n# EOF\n",
err: "invalid UTF-8 label value",
err: "invalid UTF-8 label value: \"\\\"\\xff\\\"\"",
},
{
input: "a true\n",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"a true\"",
},
{
input: "something_weird{problem=\"\n# EOF\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\\"\\n\" (\"INVALID\") while parsing: \"something_weird{problem=\\\"\\n\"",
},
{
input: "empty_label_name{=\"\"} 0\n# EOF\n",
err: "expected label name or left brace, got \"EQUAL\"",
err: "expected label name or left brace, got \"=\\\"\" (\"EQUAL\") while parsing: \"empty_label_name{=\\\"\"",
},
{
input: "foo 1_2\n\n# EOF\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 1_2\"",
},
{
input: "foo 0x1p-3\n\n# EOF\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 0x1p-3\"",
},
{
input: "foo 0x1P-3\n\n# EOF\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 0x1P-3\"",
},
{
input: "foo 0 1_2\n\n# EOF\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 0 1_2\"",
},
{
input: "custom_metric_total 1 # {aa=bb}\n# EOF\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"b\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=b\"",
},
{
input: "custom_metric_total 1 # {aa=\"bb\"}\n# EOF\n",
err: "expected value after exemplar labels, got \"INVALID\"",
err: "expected value after exemplar labels, got \"\\n\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"}\\n\"",
},
{
input: `custom_metric_total 1 # {aa="bb"}`,
err: "expected value after exemplar labels, got \"EOF\"",
},
{
input: `custom_metric 1 # {aa="bb"}`,
err: "metric name custom_metric does not support exemplars",
err: "expected value after exemplar labels, got \"}\" (\"EOF\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"}\"",
},
{
input: `custom_metric_total 1 # {aa="bb",,cc="dd"} 1`,
err: "expected label name, got \"COMMA\"",
err: "expected label name, got \",c\" (\"COMMA\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\",,c\"",
},
{
input: `custom_metric_total 1 # {aa="bb"} 1_2`,
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"} 1_2\"",
},
{
input: `custom_metric_total 1 # {aa="bb"} 0x1p-3`,
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"} 0x1p-3\"",
},
{
input: `custom_metric_total 1 # {aa="bb"} true`,
err: "strconv.ParseFloat: parsing \"true\": invalid syntax",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\"} true\"",
},
{
input: `custom_metric_total 1 # {aa="bb",cc=}`,
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"}\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=\\\"bb\\\",cc=}\"",
},
{
input: `custom_metric_total 1 # {aa=\"\xff\"} 9.0`,
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\\\\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {aa=\\\\\"",
},
{
input: `{b="c",} 1`,
err: `"INVALID" "{" is not a valid start token`,
err: "expected a valid start token, got \"{\" (\"INVALID\") while parsing: \"{\"",
},
{
input: `a 1 NaN`,
err: `invalid timestamp`,
err: `invalid timestamp NaN`,
},
{
input: `a 1 -Inf`,
err: `invalid timestamp`,
err: `invalid timestamp -Inf`,
},
{
input: `a 1 Inf`,
err: `invalid timestamp`,
err: `invalid timestamp +Inf`,
},
{
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 NaN",
err: `invalid exemplar timestamp`,
err: `invalid exemplar timestamp NaN`,
},
{
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 -Inf",
err: `invalid exemplar timestamp`,
err: `invalid exemplar timestamp -Inf`,
},
{
input: "# TYPE hhh histogram\nhhh_bucket{le=\"+Inf\"} 1 # {aa=\"bb\"} 4 Inf",
err: `invalid exemplar timestamp`,
err: `invalid exemplar timestamp +Inf`,
},
}
@ -586,35 +610,35 @@ func TestOMNullByteHandling(t *testing.T) {
},
{
input: "a{b=\x00\"ssss\"} 1\n# EOF\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\x00\" (\"INVALID\") while parsing: \"a{b=\\x00\"",
},
{
input: "a{b=\"\x00",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\\"\\x00\" (\"INVALID\") while parsing: \"a{b=\\\"\\x00\"",
},
{
input: "a{b\x00=\"hiih\"} 1",
err: "expected equal, got \"INVALID\"",
err: "expected equal, got \"\\x00\" (\"INVALID\") while parsing: \"a{b\\x00\"",
},
{
input: "a\x00{b=\"ddd\"} 1",
err: "expected value after metric, got \"INVALID\"",
err: "expected value after metric, got \"\\x00\" (\"INVALID\") while parsing: \"a\\x00\"",
},
{
input: "#",
err: "\"INVALID\" \" \" is not a valid start token",
err: "expected a valid start token, got \"#\" (\"INVALID\") while parsing: \"#\"",
},
{
input: "# H",
err: "\"INVALID\" \" \" is not a valid start token",
err: "expected a valid start token, got \"# H\" (\"INVALID\") while parsing: \"# H\"",
},
{
input: "custom_metric_total 1 # {b=\x00\"ssss\"} 1\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\x00\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {b=\\x00\"",
},
{
input: "custom_metric_total 1 # {b=\"\x00ss\"} 1\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\\"\\x00\" (\"INVALID\") while parsing: \"custom_metric_total 1 # {b=\\\"\\x00\"",
},
}

View file

@ -254,8 +254,12 @@ func (p *PromParser) nextToken() token {
}
}
func parseError(exp string, got token) error {
return fmt.Errorf("%s, got %q", exp, got)
func (p *PromParser) parseError(exp string, got token) error {
e := p.l.i + 1
if len(p.l.b) < e {
e = len(p.l.b)
}
return fmt.Errorf("%s, got %q (%q) while parsing: %q", exp, p.l.b[p.l.start:e], got, p.l.b[p.start:e])
}
// Next advances the parser to the next sample. It returns false if no
@ -278,7 +282,7 @@ func (p *PromParser) Next() (Entry, error) {
case tMName:
p.offsets = append(p.offsets, p.l.start, p.l.i)
default:
return EntryInvalid, parseError("expected metric name after "+t.String(), t2)
return EntryInvalid, p.parseError("expected metric name after "+t.String(), t2)
}
switch t2 := p.nextToken(); t2 {
case tText:
@ -308,11 +312,11 @@ func (p *PromParser) Next() (Entry, error) {
}
case tHelp:
if !utf8.Valid(p.text) {
return EntryInvalid, fmt.Errorf("help text is not a valid utf8 string")
return EntryInvalid, fmt.Errorf("help text %q is not a valid utf8 string", p.text)
}
}
if t := p.nextToken(); t != tLinebreak {
return EntryInvalid, parseError("linebreak expected after metadata", t)
return EntryInvalid, p.parseError("linebreak expected after metadata", t)
}
switch t {
case tHelp:
@ -323,7 +327,7 @@ func (p *PromParser) Next() (Entry, error) {
case tComment:
p.text = p.l.buf()
if t := p.nextToken(); t != tLinebreak {
return EntryInvalid, parseError("linebreak expected after comment", t)
return EntryInvalid, p.parseError("linebreak expected after comment", t)
}
return EntryComment, nil
@ -340,10 +344,10 @@ func (p *PromParser) Next() (Entry, error) {
t2 = p.nextToken()
}
if t2 != tValue {
return EntryInvalid, parseError("expected value after metric", t2)
return EntryInvalid, p.parseError("expected value after metric", t2)
}
if p.val, err = parseFloat(yoloString(p.l.buf())); err != nil {
return EntryInvalid, err
return EntryInvalid, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
}
// Ensure canonical NaN value.
if math.IsNaN(p.val) {
@ -356,18 +360,18 @@ func (p *PromParser) Next() (Entry, error) {
case tTimestamp:
p.hasTS = true
if p.ts, err = strconv.ParseInt(yoloString(p.l.buf()), 10, 64); err != nil {
return EntryInvalid, err
return EntryInvalid, fmt.Errorf("%v while parsing: %q", err, p.l.b[p.start:p.l.i])
}
if t2 := p.nextToken(); t2 != tLinebreak {
return EntryInvalid, parseError("expected next entry after timestamp", t2)
return EntryInvalid, p.parseError("expected next entry after timestamp", t2)
}
default:
return EntryInvalid, parseError("expected timestamp or new record", t)
return EntryInvalid, p.parseError("expected timestamp or new record", t)
}
return EntrySeries, nil
default:
err = fmt.Errorf("%q is not a valid start token", t)
err = p.parseError("expected a valid start token", t)
}
return EntryInvalid, err
}
@ -380,18 +384,18 @@ func (p *PromParser) parseLVals() error {
return nil
case tLName:
default:
return parseError("expected label name", t)
return p.parseError("expected label name", t)
}
p.offsets = append(p.offsets, p.l.start, p.l.i)
if t := p.nextToken(); t != tEqual {
return parseError("expected equal", t)
return p.parseError("expected equal", t)
}
if t := p.nextToken(); t != tLValue {
return parseError("expected label value", t)
return p.parseError("expected label value", t)
}
if !utf8.Valid(p.l.buf()) {
return fmt.Errorf("invalid UTF-8 label value")
return fmt.Errorf("invalid UTF-8 label value: %q", p.l.buf())
}
// The promlexer ensures the value string is quoted. Strip first

View file

@ -219,63 +219,63 @@ func TestPromParseErrors(t *testing.T) {
}{
{
input: "a",
err: "expected value after metric, got \"INVALID\"",
err: "expected value after metric, got \"\\n\" (\"INVALID\") while parsing: \"a\\n\"",
},
{
input: "a{b='c'} 1\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"'\" (\"INVALID\") while parsing: \"a{b='\"",
},
{
input: "a{b=\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\n\" (\"INVALID\") while parsing: \"a{b=\\n\"",
},
{
input: "a{\xff=\"foo\"} 1\n",
err: "expected label name, got \"INVALID\"",
err: "expected label name, got \"\\xff\" (\"INVALID\") while parsing: \"a{\\xff\"",
},
{
input: "a{b=\"\xff\"} 1\n",
err: "invalid UTF-8 label value",
err: "invalid UTF-8 label value: \"\\\"\\xff\\\"\"",
},
{
input: "a true\n",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax",
err: "strconv.ParseFloat: parsing \"true\": invalid syntax while parsing: \"a true\"",
},
{
input: "something_weird{problem=\"",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\\"\\n\" (\"INVALID\") while parsing: \"something_weird{problem=\\\"\\n\"",
},
{
input: "empty_label_name{=\"\"} 0",
err: "expected label name, got \"EQUAL\"",
err: "expected label name, got \"=\\\"\" (\"EQUAL\") while parsing: \"empty_label_name{=\\\"\"",
},
{
input: "foo 1_2\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 1_2\"",
},
{
input: "foo 0x1p-3\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 0x1p-3\"",
},
{
input: "foo 0x1P-3\n",
err: "unsupported character in float",
err: "unsupported character in float while parsing: \"foo 0x1P-3\"",
},
{
input: "foo 0 1_2\n",
err: "expected next entry after timestamp, got \"INVALID\"",
err: "expected next entry after timestamp, got \"_\" (\"INVALID\") while parsing: \"foo 0 1_\"",
},
{
input: `{a="ok"} 1`,
err: `"INVALID" is not a valid start token`,
err: "expected a valid start token, got \"{\" (\"INVALID\") while parsing: \"{\"",
},
{
input: "# TYPE #\n#EOF\n",
err: "expected metric name after TYPE, got \"INVALID\"",
err: "expected metric name after TYPE, got \"#\" (\"INVALID\") while parsing: \"# TYPE #\"",
},
{
input: "# HELP #\n#EOF\n",
err: "expected metric name after HELP, got \"INVALID\"",
err: "expected metric name after HELP, got \"#\" (\"INVALID\") while parsing: \"# HELP #\"",
},
}
@ -313,23 +313,23 @@ func TestPromNullByteHandling(t *testing.T) {
},
{
input: "a{b=\x00\"ssss\"} 1\n",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\x00\" (\"INVALID\") while parsing: \"a{b=\\x00\"",
},
{
input: "a{b=\"\x00",
err: "expected label value, got \"INVALID\"",
err: "expected label value, got \"\\\"\\x00\\n\" (\"INVALID\") while parsing: \"a{b=\\\"\\x00\\n\"",
},
{
input: "a{b\x00=\"hiih\"} 1",
err: "expected equal, got \"INVALID\"",
err: "expected equal, got \"\\x00\" (\"INVALID\") while parsing: \"a{b\\x00\"",
},
{
input: "a\x00{b=\"ddd\"} 1",
err: "expected value after metric, got \"INVALID\"",
err: "expected value after metric, got \"\\x00\" (\"INVALID\") while parsing: \"a\\x00\"",
},
{
input: "a 0 1\x00",
err: "expected next entry after timestamp, got \"INVALID\"",
err: "expected next entry after timestamp, got \"\\x00\" (\"INVALID\") while parsing: \"a 0 1\\x00\"",
},
}

View file

@ -328,7 +328,7 @@ func newScrapePool(cfg *config.ScrapeConfig, app storage.Appendable, jitterSeed
options.PassMetadataInContext,
)
}
targetScrapePoolTargetLimit.WithLabelValues(sp.config.JobName).Set(float64(sp.config.TargetLimit))
return sp, nil
}

View file

@ -75,12 +75,17 @@ function bumpVersion() {
if [[ "${version}" == v* ]]; then
version="${version:1}"
fi
# increase the version on all packages
npm version "${version}" --workspaces
# upgrade the @prometheus-io/* dependencies on all packages
for workspace in ${workspaces}; do
sed -E -i "s|(\"@prometheus-io/.+\": )\".+\"|\1\"\^${version}\"|" "${workspace}"/package.json
# sed -i syntax is different on mac and linux
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -E -i "" "s|(\"@prometheus-io/.+\": )\".+\"|\1\"${version}\"|" "${workspace}"/package.json
else
sed -E -i "s|(\"@prometheus-io/.+\": )\".+\"|\1\"${version}\"|" "${workspace}"/package.json
fi
done
# increase the version on all packages
npm version "${version}" --workspaces
}
if [[ "$1" == "--copy" ]]; then

View file

@ -894,6 +894,10 @@ func (t *QueueManager) releaseLabels(ls labels.Labels) {
// processExternalLabels merges externalLabels into ls. If ls contains
// a label in externalLabels, the value in ls wins.
func processExternalLabels(ls labels.Labels, externalLabels []labels.Label) labels.Labels {
if len(externalLabels) == 0 {
return ls
}
b := labels.NewScratchBuilder(ls.Len() + len(externalLabels))
j := 0
ls.Range(func(l labels.Label) {

View file

@ -131,6 +131,10 @@ type Options struct {
// WALCompression will turn on Snappy compression for records on the WAL.
WALCompression bool
// Maximum number of CPUs that can simultaneously processes WAL replay.
// If it is <=0, then GOMAXPROCS is used.
WALReplayConcurrency int
// StripeSize is the size in entries of the series hash map. Reducing the size will save memory but impact performance.
StripeSize int
@ -813,6 +817,9 @@ func open(dir string, l log.Logger, r prometheus.Registerer, opts *Options, rngs
headOpts.PostingsForMatchersCacheTTL = opts.HeadPostingsForMatchersCacheTTL
headOpts.PostingsForMatchersCacheSize = opts.HeadPostingsForMatchersCacheSize
headOpts.PostingsForMatchersCacheForce = opts.HeadPostingsForMatchersCacheForce
if opts.WALReplayConcurrency > 0 {
headOpts.WALReplayConcurrency = opts.WALReplayConcurrency
}
if opts.IsolationDisabled {
// We only override this flag if isolation is disabled at DB level. We use the default otherwise.
headOpts.IsolationDisabled = opts.IsolationDisabled

View file

@ -18,6 +18,7 @@ import (
"io"
"math"
"path/filepath"
"runtime"
"sync"
"time"
@ -58,6 +59,8 @@ var (
// defaultIsolationDisabled is true if isolation is disabled by default.
defaultIsolationDisabled = false
defaultWALReplayConcurrency = runtime.GOMAXPROCS(0)
)
// chunkDiskMapper is a temporary interface while we transition from
@ -175,6 +178,11 @@ type HeadOptions struct {
PostingsForMatchersCacheTTL time.Duration
PostingsForMatchersCacheSize int
PostingsForMatchersCacheForce bool
// Maximum number of CPUs that can simultaneously processes WAL replay.
// The default value is GOMAXPROCS.
// If it is set to a negative value or zero, the default value is used.
WALReplayConcurrency int
}
const (
@ -196,6 +204,7 @@ func DefaultHeadOptions() *HeadOptions {
PostingsForMatchersCacheTTL: defaultPostingsForMatchersCacheTTL,
PostingsForMatchersCacheSize: defaultPostingsForMatchersCacheSize,
PostingsForMatchersCacheForce: false,
WALReplayConcurrency: defaultWALReplayConcurrency,
}
ho.OutOfOrderCapMax.Store(DefaultOutOfOrderCapMax)
return ho
@ -273,6 +282,10 @@ func NewHead(r prometheus.Registerer, l log.Logger, wal, wbl *wlog.WL, opts *Hea
opts.ChunkPool = chunkenc.NewPool()
}
if opts.WALReplayConcurrency <= 0 {
opts.WALReplayConcurrency = defaultWALReplayConcurrency
}
h.chunkDiskMapper, err = chunks.NewChunkDiskMapper(
r,
mmappedChunksDir(opts.ChunkDirRoot),
@ -529,6 +542,17 @@ func newHeadMetrics(h *Head, r prometheus.Registerer) *headMetrics {
}, func() float64 {
return float64(h.iso.lastAppendID())
}),
prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "prometheus_tsdb_head_chunks_storage_size_bytes",
Help: "Size of the chunks_head directory.",
}, func() float64 {
val, err := h.chunkDiskMapper.Size()
if err != nil {
level.Error(h.logger).Log("msg", "Failed to calculate size of \"chunks_head\" dir",
"err", err.Error())
}
return float64(val)
}),
)
}
return m
@ -595,20 +619,47 @@ func (h *Head) Init(minValidTime int64) error {
if h.opts.EnableMemorySnapshotOnShutdown {
level.Info(h.logger).Log("msg", "Chunk snapshot is enabled, replaying from the snapshot")
var err error
snapIdx, snapOffset, refSeries, err = h.loadChunkSnapshot()
if err != nil {
snapIdx, snapOffset = -1, 0
refSeries = make(map[chunks.HeadSeriesRef]*memSeries)
// If there are any WAL files, there should be at least one WAL file with an index that is current or newer
// than the snapshot index. If the WAL index is behind the snapshot index somehow, the snapshot is assumed
// to be outdated.
loadSnapshot := true
if h.wal != nil {
_, endAt, err := wlog.Segments(h.wal.Dir())
if err != nil {
return errors.Wrap(err, "finding WAL segments")
}
h.metrics.snapshotReplayErrorTotal.Inc()
level.Error(h.logger).Log("msg", "Failed to load chunk snapshot", "err", err)
// We clear the partially loaded data to replay fresh from the WAL.
if err := h.resetInMemoryState(); err != nil {
return err
_, idx, _, err := LastChunkSnapshot(h.opts.ChunkDirRoot)
if err != nil && err != record.ErrNotFound {
level.Error(h.logger).Log("msg", "Could not find last snapshot", "err", err)
}
if err == nil && endAt < idx {
loadSnapshot = false
level.Warn(h.logger).Log("msg", "Last WAL file is behind snapshot, removing snapshots")
if err := DeleteChunkSnapshots(h.opts.ChunkDirRoot, math.MaxInt, math.MaxInt); err != nil {
level.Error(h.logger).Log("msg", "Error while deleting snapshot directories", "err", err)
}
}
}
if loadSnapshot {
var err error
snapIdx, snapOffset, refSeries, err = h.loadChunkSnapshot()
if err == nil {
level.Info(h.logger).Log("msg", "Chunk snapshot loading time", "duration", time.Since(start).String())
}
if err != nil {
snapIdx, snapOffset = -1, 0
refSeries = make(map[chunks.HeadSeriesRef]*memSeries)
h.metrics.snapshotReplayErrorTotal.Inc()
level.Error(h.logger).Log("msg", "Failed to load chunk snapshot", "err", err)
// We clear the partially loaded data to replay fresh from the WAL.
if err := h.resetInMemoryState(); err != nil {
return err
}
}
}
level.Info(h.logger).Log("msg", "Chunk snapshot loading time", "duration", time.Since(start).String())
}
mmapChunkReplayStart := time.Now()

View file

@ -4856,3 +4856,58 @@ func TestGaugeFloatHistogramWALAndChunkHeader(t *testing.T) {
checkHeaders()
}
func TestSnapshotAheadOfWALError(t *testing.T) {
head, _ := newTestHead(t, 120*4, false, false)
head.opts.EnableMemorySnapshotOnShutdown = true
// Add a sample to fill WAL.
app := head.Appender(context.Background())
_, err := app.Append(0, labels.FromStrings("foo", "bar"), 10, 10)
require.NoError(t, err)
require.NoError(t, app.Commit())
// Increment snapshot index to create sufficiently large difference.
for i := 0; i < 2; i++ {
_, err = head.wal.NextSegment()
require.NoError(t, err)
}
require.NoError(t, head.Close()) // This will create a snapshot.
_, idx, _, err := LastChunkSnapshot(head.opts.ChunkDirRoot)
require.NoError(t, err)
require.Equal(t, 2, idx)
// Restart the WAL while keeping the old snapshot. The new head is created manually in this case in order
// to keep using the same snapshot directory instead of a random one.
require.NoError(t, os.RemoveAll(head.wal.Dir()))
head.opts.EnableMemorySnapshotOnShutdown = false
w, _ := wlog.NewSize(nil, nil, head.wal.Dir(), 32768, false)
head, err = NewHead(nil, nil, w, nil, head.opts, nil)
require.NoError(t, err)
// Add a sample to fill WAL.
app = head.Appender(context.Background())
_, err = app.Append(0, labels.FromStrings("foo", "bar"), 10, 10)
require.NoError(t, err)
require.NoError(t, app.Commit())
lastSegment, _, _ := w.LastSegmentAndOffset()
require.Equal(t, 0, lastSegment)
require.NoError(t, head.Close())
// New WAL is saved, but old snapshot still exists.
_, idx, _, err = LastChunkSnapshot(head.opts.ChunkDirRoot)
require.NoError(t, err)
require.Equal(t, 2, idx)
// Create new Head which should detect the incorrect index and delete the snapshot.
head.opts.EnableMemorySnapshotOnShutdown = true
w, _ = wlog.NewSize(nil, nil, head.wal.Dir(), 32768, false)
head, err = NewHead(nil, nil, w, nil, head.opts, nil)
require.NoError(t, err)
require.NoError(t, head.Init(math.MinInt64))
// Verify that snapshot directory does not exist anymore.
_, _, _, err = LastChunkSnapshot(head.opts.ChunkDirRoot)
require.Equal(t, record.ErrNotFound, err)
require.NoError(t, head.Close())
}

View file

@ -18,7 +18,6 @@ import (
"math"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
@ -65,13 +64,13 @@ func (h *Head) loadWAL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
// Start workers that each process samples for a partition of the series ID space.
var (
wg sync.WaitGroup
n = runtime.GOMAXPROCS(0)
processors = make([]walSubsetProcessor, n)
concurrency = h.opts.WALReplayConcurrency
processors = make([]walSubsetProcessor, concurrency)
exemplarsInput chan record.RefExemplar
dec record.Decoder
shards = make([][]record.RefSample, n)
histogramShards = make([][]histogramRecord, n)
shards = make([][]record.RefSample, concurrency)
histogramShards = make([][]histogramRecord, concurrency)
decoded = make(chan interface{}, 10)
decodeErr, seriesCreationErr error
@ -116,7 +115,7 @@ func (h *Head) loadWAL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
// For CorruptionErr ensure to terminate all workers before exiting.
_, ok := err.(*wlog.CorruptionErr)
if ok || seriesCreationErr != nil {
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
processors[i].closeAndDrain()
}
close(exemplarsInput)
@ -124,8 +123,8 @@ func (h *Head) loadWAL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
}
}()
wg.Add(n)
for i := 0; i < n; i++ {
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
processors[i].setup()
go func(wp *walSubsetProcessor) {
@ -276,7 +275,7 @@ Outer:
multiRef[walSeries.Ref] = mSeries.ref
}
idx := uint64(mSeries.ref) % uint64(n)
idx := uint64(mSeries.ref) % uint64(concurrency)
processors[idx].input <- walSubsetProcessorInputItem{walSeriesRef: walSeries.Ref, existingSeries: mSeries}
}
//nolint:staticcheck // Ignore SA6002 relax staticcheck verification.
@ -293,7 +292,7 @@ Outer:
if len(samples) < m {
m = len(samples)
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
if shards[i] == nil {
shards[i] = processors[i].reuseBuf()
}
@ -305,10 +304,10 @@ Outer:
if r, ok := multiRef[sam.Ref]; ok {
sam.Ref = r
}
mod := uint64(sam.Ref) % uint64(n)
mod := uint64(sam.Ref) % uint64(concurrency)
shards[mod] = append(shards[mod], sam)
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
if len(shards[i]) > 0 {
processors[i].input <- walSubsetProcessorInputItem{samples: shards[i]}
shards[i] = nil
@ -351,7 +350,7 @@ Outer:
if len(samples) < m {
m = len(samples)
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
if histogramShards[i] == nil {
histogramShards[i] = processors[i].reuseHistogramBuf()
}
@ -363,10 +362,10 @@ Outer:
if r, ok := multiRef[sam.Ref]; ok {
sam.Ref = r
}
mod := uint64(sam.Ref) % uint64(n)
mod := uint64(sam.Ref) % uint64(concurrency)
histogramShards[mod] = append(histogramShards[mod], histogramRecord{ref: sam.Ref, t: sam.T, h: sam.H})
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
if len(histogramShards[i]) > 0 {
processors[i].input <- walSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
histogramShards[i] = nil
@ -388,7 +387,7 @@ Outer:
if len(samples) < m {
m = len(samples)
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
if histogramShards[i] == nil {
histogramShards[i] = processors[i].reuseHistogramBuf()
}
@ -400,10 +399,10 @@ Outer:
if r, ok := multiRef[sam.Ref]; ok {
sam.Ref = r
}
mod := uint64(sam.Ref) % uint64(n)
mod := uint64(sam.Ref) % uint64(concurrency)
histogramShards[mod] = append(histogramShards[mod], histogramRecord{ref: sam.Ref, t: sam.T, fh: sam.FH})
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
if len(histogramShards[i]) > 0 {
processors[i].input <- walSubsetProcessorInputItem{histogramSamples: histogramShards[i]}
histogramShards[i] = nil
@ -444,7 +443,7 @@ Outer:
}
// Signal termination to each worker and wait for it to close its output channel.
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
processors[i].closeAndDrain()
}
close(exemplarsInput)
@ -687,12 +686,12 @@ func (h *Head) loadWBL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
lastSeq, lastOff := lastMmapRef.Unpack()
// Start workers that each process samples for a partition of the series ID space.
var (
wg sync.WaitGroup
n = runtime.GOMAXPROCS(0)
processors = make([]wblSubsetProcessor, n)
wg sync.WaitGroup
concurrency = h.opts.WALReplayConcurrency
processors = make([]wblSubsetProcessor, concurrency)
dec record.Decoder
shards = make([][]record.RefSample, n)
shards = make([][]record.RefSample, concurrency)
decodedCh = make(chan interface{}, 10)
decodeErr error
@ -714,15 +713,15 @@ func (h *Head) loadWBL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
_, ok := err.(*wlog.CorruptionErr)
if ok {
err = &errLoadWbl{err: err}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
processors[i].closeAndDrain()
}
wg.Wait()
}
}()
wg.Add(n)
for i := 0; i < n; i++ {
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
processors[i].setup()
go func(wp *wblSubsetProcessor) {
@ -781,17 +780,17 @@ func (h *Head) loadWBL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
if len(samples) < m {
m = len(samples)
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
shards[i] = processors[i].reuseBuf()
}
for _, sam := range samples[:m] {
if r, ok := multiRef[sam.Ref]; ok {
sam.Ref = r
}
mod := uint64(sam.Ref) % uint64(n)
mod := uint64(sam.Ref) % uint64(concurrency)
shards[mod] = append(shards[mod], sam)
}
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
processors[i].input <- shards[i]
}
samples = samples[m:]
@ -818,7 +817,7 @@ func (h *Head) loadWBL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
mmapMarkerUnknownRefs.Inc()
continue
}
idx := uint64(ms.ref) % uint64(n)
idx := uint64(ms.ref) % uint64(concurrency)
// It is possible that some old sample is being processed in processWALSamples that
// could cause race below. So we wait for the goroutine to empty input the buffer and finish
// processing all old samples after emptying the buffer.
@ -847,7 +846,7 @@ func (h *Head) loadWBL(r *wlog.Reader, multiRef map[chunks.HeadSeriesRef]chunks.
}
// Signal termination to each worker and wait for it to close its output channel.
for i := 0; i < n; i++ {
for i := 0; i < concurrency; i++ {
processors[i].closeAndDrain()
}
wg.Wait()
@ -1383,18 +1382,18 @@ func (h *Head) loadChunkSnapshot() (int, int, map[chunks.HeadSeriesRef]*memSerie
var (
numSeries = 0
unknownRefs = int64(0)
n = runtime.GOMAXPROCS(0)
concurrency = h.opts.WALReplayConcurrency
wg sync.WaitGroup
recordChan = make(chan chunkSnapshotRecord, 5*n)
shardedRefSeries = make([]map[chunks.HeadSeriesRef]*memSeries, n)
errChan = make(chan error, n)
recordChan = make(chan chunkSnapshotRecord, 5*concurrency)
shardedRefSeries = make([]map[chunks.HeadSeriesRef]*memSeries, concurrency)
errChan = make(chan error, concurrency)
refSeries map[chunks.HeadSeriesRef]*memSeries
exemplarBuf []record.RefExemplar
dec record.Decoder
)
wg.Add(n)
for i := 0; i < n; i++ {
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func(idx int, rc <-chan chunkSnapshotRecord) {
defer wg.Done()
defer func() {

View file

@ -1,58 +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 test
import "testing"
func BenchmarkMapConversion(b *testing.B) {
type key string
type val string
m := map[key]val{
"job": "node",
"instance": "123.123.1.211:9090",
"path": "/api/v1/namespaces/<namespace>/deployments/<name>",
"method": "GET",
"namespace": "system",
"status": "500",
}
var sm map[string]string
for i := 0; i < b.N; i++ {
sm = make(map[string]string, len(m))
for k, v := range m {
sm[string(k)] = string(v)
}
}
}
func BenchmarkListIter(b *testing.B) {
var list []uint32
for i := 0; i < 1e4; i++ {
list = append(list, uint32(i))
}
b.ResetTimer()
total := uint32(0)
for j := 0; j < b.N; j++ {
sum := uint32(0)
for _, k := range list {
sum += k
}
total += sum
}
}

View file

@ -1,123 +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 test
import (
"crypto/rand"
"fmt"
"hash/crc32"
"testing"
"github.com/cespare/xxhash/v2"
sip13 "github.com/dgryski/go-sip13"
)
type pair struct {
name, value string
}
var testInput = []pair{
{"job", "node"},
{"instance", "123.123.1.211:9090"},
{"path", "/api/v1/namespaces/<namespace>/deployments/<name>"},
{"method", "GET"},
{"namespace", "system"},
{"status", "500"},
}
func BenchmarkHash(b *testing.B) {
input := []byte{}
for _, v := range testInput {
input = append(input, v.name...)
input = append(input, '\xff')
input = append(input, v.value...)
input = append(input, '\xff')
}
var total uint64
var k0 uint64 = 0x0706050403020100
var k1 uint64 = 0x0f0e0d0c0b0a0908
for name, f := range map[string]func(b []byte) uint64{
"xxhash": xxhash.Sum64,
"fnv64": fnv64a,
"sip13": func(b []byte) uint64 { return sip13.Sum64(k0, k1, b) },
} {
b.Run(name, func(b *testing.B) {
b.SetBytes(int64(len(input)))
total = 0
for i := 0; i < b.N; i++ {
total += f(input)
}
})
}
}
// hashAdd adds a string to a fnv64a hash value, returning the updated hash.
func fnv64a(b []byte) uint64 {
const (
offset64 = 14695981039346656037
prime64 = 1099511628211
)
h := uint64(offset64)
for x := range b {
h ^= uint64(x)
h *= prime64
}
return h
}
func BenchmarkCRC32_diff(b *testing.B) {
data := [][]byte{}
for i := 0; i < 1000; i++ {
b := make([]byte, 512)
rand.Read(b)
data = append(data, b)
}
ctab := crc32.MakeTable(crc32.Castagnoli)
total := uint32(0)
b.Run("direct", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
total += crc32.Checksum(data[i%1000], ctab)
}
})
b.Run("hash-reuse", func(b *testing.B) {
b.ReportAllocs()
h := crc32.New(ctab)
for i := 0; i < b.N; i++ {
h.Reset()
h.Write(data[i%1000])
total += h.Sum32()
}
})
b.Run("hash-new", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
h := crc32.New(ctab)
h.Write(data[i%1000])
total += h.Sum32()
}
})
fmt.Println(total)
}

View file

@ -1,214 +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 test
import (
"bytes"
"crypto/rand"
"testing"
"github.com/prometheus/prometheus/model/labels"
)
func BenchmarkMapClone(b *testing.B) {
m := map[string]string{
"job": "node",
"instance": "123.123.1.211:9090",
"path": "/api/v1/namespaces/<namespace>/deployments/<name>",
"method": "GET",
"namespace": "system",
"status": "500",
"prometheus": "prometheus-core-1",
"datacenter": "eu-west-1",
"pod_name": "abcdef-99999-defee",
}
for i := 0; i < b.N; i++ {
res := make(map[string]string, len(m))
for k, v := range m {
res[k] = v
}
m = res
}
}
func BenchmarkLabelsClone(b *testing.B) {
m := map[string]string{
"job": "node",
"instance": "123.123.1.211:9090",
"path": "/api/v1/namespaces/<namespace>/deployments/<name>",
"method": "GET",
"namespace": "system",
"status": "500",
"prometheus": "prometheus-core-1",
"datacenter": "eu-west-1",
"pod_name": "abcdef-99999-defee",
}
l := labels.FromMap(m)
for i := 0; i < b.N; i++ {
l = l.Copy()
}
}
func BenchmarkLabelMapAccess(b *testing.B) {
m := map[string]string{
"job": "node",
"instance": "123.123.1.211:9090",
"path": "/api/v1/namespaces/<namespace>/deployments/<name>",
"method": "GET",
"namespace": "system",
"status": "500",
"prometheus": "prometheus-core-1",
"datacenter": "eu-west-1",
"pod_name": "abcdef-99999-defee",
}
var v string
for k := range m {
b.Run(k, func(b *testing.B) {
for i := 0; i < b.N; i++ {
v = m[k]
}
})
}
_ = v
}
func BenchmarkLabelSetAccess(b *testing.B) {
m := map[string]string{
"job": "node",
"instance": "123.123.1.211:9090",
"path": "/api/v1/namespaces/<namespace>/deployments/<name>",
"method": "GET",
"namespace": "system",
"status": "500",
"prometheus": "prometheus-core-1",
"datacenter": "eu-west-1",
"pod_name": "abcdef-99999-defee",
}
ls := labels.FromMap(m)
var v string
ls.Range(func(l labels.Label) {
b.Run(l.Name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
v = ls.Get(l.Name)
}
})
})
_ = v
}
func BenchmarkStringBytesEquals(b *testing.B) {
randBytes := func(n int) ([]byte, []byte) {
buf1 := make([]byte, n)
if _, err := rand.Read(buf1); err != nil {
b.Fatal(err)
}
buf2 := make([]byte, n)
copy(buf1, buf2)
return buf1, buf2
}
cases := []struct {
name string
f func() ([]byte, []byte)
}{
{
name: "equal",
f: func() ([]byte, []byte) {
return randBytes(60)
},
},
{
name: "1-flip-end",
f: func() ([]byte, []byte) {
b1, b2 := randBytes(60)
b2[59] ^= b2[59]
return b1, b2
},
},
{
name: "1-flip-middle",
f: func() ([]byte, []byte) {
b1, b2 := randBytes(60)
b2[29] ^= b2[29]
return b1, b2
},
},
{
name: "1-flip-start",
f: func() ([]byte, []byte) {
b1, b2 := randBytes(60)
b2[0] ^= b2[0]
return b1, b2
},
},
{
name: "different-length",
f: func() ([]byte, []byte) {
b1, b2 := randBytes(60)
return b1, b2[:59]
},
},
}
for _, c := range cases {
b.Run(c.name+"-strings", func(b *testing.B) {
ab, bb := c.f()
as, bs := string(ab), string(bb)
b.SetBytes(int64(len(as)))
var r bool
for i := 0; i < b.N; i++ {
r = as == bs
}
_ = r
})
b.Run(c.name+"-bytes", func(b *testing.B) {
ab, bb := c.f()
b.SetBytes(int64(len(ab)))
var r bool
for i := 0; i < b.N; i++ {
r = bytes.Equal(ab, bb)
}
_ = r
})
b.Run(c.name+"-bytes-length-check", func(b *testing.B) {
ab, bb := c.f()
b.SetBytes(int64(len(ab)))
var r bool
for i := 0; i < b.N; i++ {
if len(ab) != len(bb) {
continue
}
r = bytes.Equal(ab, bb)
}
_ = r
})
}
}

View file

@ -199,9 +199,10 @@ type wlMetrics struct {
truncateTotal prometheus.Counter
currentSegment prometheus.Gauge
writesFailed prometheus.Counter
walFileSize prometheus.GaugeFunc
}
func newWLMetrics(r prometheus.Registerer) *wlMetrics {
func newWLMetrics(w *WL, r prometheus.Registerer) *wlMetrics {
m := &wlMetrics{}
m.fsyncDuration = prometheus.NewSummary(prometheus.SummaryOpts{
@ -233,6 +234,17 @@ func newWLMetrics(r prometheus.Registerer) *wlMetrics {
Name: "writes_failed_total",
Help: "Total number of write log writes that failed.",
})
m.walFileSize = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Name: "storage_size_bytes",
Help: "Size of the write log directory.",
}, func() float64 {
val, err := w.Size()
if err != nil {
level.Error(w.logger).Log("msg", "Failed to calculate size of \"wal\" dir",
"err", err.Error())
}
return float64(val)
})
if r != nil {
r.MustRegister(
@ -243,6 +255,7 @@ func newWLMetrics(r prometheus.Registerer) *wlMetrics {
m.truncateTotal,
m.currentSegment,
m.writesFailed,
m.walFileSize,
)
}
@ -279,7 +292,7 @@ func NewSize(logger log.Logger, reg prometheus.Registerer, dir string, segmentSi
if filepath.Base(dir) == WblDirName {
prefix = "prometheus_tsdb_out_of_order_wbl_"
}
w.metrics = newWLMetrics(prometheus.WrapRegistererWithPrefix(prefix, reg))
w.metrics = newWLMetrics(w, prometheus.WrapRegistererWithPrefix(prefix, reg))
_, last, err := Segments(w.Dir())
if err != nil {

View file

@ -6,6 +6,7 @@ import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-d
import { PathPrefixContext } from './contexts/PathPrefixContext';
import { ThemeContext, themeName, themeSetting } from './contexts/ThemeContext';
import { ReadyContext } from './contexts/ReadyContext';
import { AnimateLogoContext } from './contexts/AnimateLogoContext';
import { useLocalStorage } from './hooks/useLocalStorage';
import useMedia from './hooks/useMedia';
import {
@ -60,6 +61,7 @@ const App: FC<AppProps> = ({ consolesLink, agentMode, ready }) => {
const [userTheme, setUserTheme] = useLocalStorage<themeSetting>(themeLocalStorageKey, 'auto');
const browserHasThemes = useMedia('(prefers-color-scheme)');
const browserWantsDarkTheme = useMedia('(prefers-color-scheme: dark)');
const [animateLogo, setAnimateLogo] = useLocalStorage<boolean>('animateLogo', false);
let theme: themeName;
if (userTheme !== 'auto') {
@ -76,46 +78,48 @@ const App: FC<AppProps> = ({ consolesLink, agentMode, ready }) => {
<PathPrefixContext.Provider value={basePath}>
<ReadyContext.Provider value={ready}>
<Router basename={basePath}>
<Navigation consolesLink={consolesLink} agentMode={agentMode} />
<Container fluid style={{ paddingTop: 70 }}>
<Switch>
<Redirect exact from="/" to={agentMode ? '/agent' : '/graph'} />
{/*
<AnimateLogoContext.Provider value={animateLogo}>
<Navigation consolesLink={consolesLink} agentMode={agentMode} animateLogo={animateLogo} />
<Container fluid style={{ paddingTop: 70 }}>
<Switch>
<Redirect exact from="/" to={agentMode ? '/agent' : '/graph'} />
{/*
NOTE: Any route added here needs to also be added to the list of
React-handled router paths ("reactRouterPaths") in /web/web.go.
*/}
<Route path="/agent">
<AgentPage />
</Route>
<Route path="/graph">
<PanelListPage />
</Route>
<Route path="/alerts">
<AlertsPage />
</Route>
<Route path="/config">
<ConfigPage />
</Route>
<Route path="/flags">
<FlagsPage />
</Route>
<Route path="/rules">
<RulesPage />
</Route>
<Route path="/service-discovery">
<ServiceDiscoveryPage />
</Route>
<Route path="/status">
<StatusPage agentMode={agentMode} />
</Route>
<Route path="/tsdb-status">
<TSDBStatusPage />
</Route>
<Route path="/targets">
<TargetsPage />
</Route>
</Switch>
</Container>
<Route path="/agent">
<AgentPage />
</Route>
<Route path="/graph">
<PanelListPage />
</Route>
<Route path="/alerts">
<AlertsPage />
</Route>
<Route path="/config">
<ConfigPage />
</Route>
<Route path="/flags">
<FlagsPage />
</Route>
<Route path="/rules">
<RulesPage />
</Route>
<Route path="/service-discovery">
<ServiceDiscoveryPage />
</Route>
<Route path="/status">
<StatusPage agentMode={agentMode} setAnimateLogo={setAnimateLogo} />
</Route>
<Route path="/tsdb-status">
<TSDBStatusPage />
</Route>
<Route path="/targets">
<TargetsPage />
</Route>
</Switch>
</Container>
</AnimateLogoContext.Provider>
</Router>
</ReadyContext.Provider>
</PathPrefixContext.Provider>

View file

@ -13,21 +13,22 @@ import {
DropdownItem,
} from 'reactstrap';
import { ThemeToggle } from './Theme';
import logo from './images/prometheus_logo_grey.svg';
import { ReactComponent as PromLogo } from './images/prometheus_logo_grey.svg';
interface NavbarProps {
consolesLink: string | null;
agentMode: boolean;
animateLogo?: boolean | false;
}
const Navigation: FC<NavbarProps> = ({ consolesLink, agentMode }) => {
const Navigation: FC<NavbarProps> = ({ consolesLink, agentMode, animateLogo }) => {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return (
<Navbar className="mb-3" dark color="dark" expand="md" fixed="top">
<NavbarToggler onClick={toggle} className="mr-2" />
<Link className="pt-0 navbar-brand" to={agentMode ? '/agent' : '/graph'}>
<img src={logo} className="d-inline-block align-top" alt="Prometheus logo" title="Prometheus" />
<PromLogo className={`d-inline-block align-top${animateLogo ? ' animate' : ''}`} title="Prometheus" />
Prometheus{agentMode && ' Agent'}
</Link>
<Collapse isOpen={isOpen} navbar style={{ justifyContent: 'space-between' }}>

View file

@ -0,0 +1,3 @@
import { createContext } from 'react';
export const AnimateLogoContext = createContext<boolean>(false);

View file

@ -1,4 +1,4 @@
import React, { Fragment, FC } from 'react';
import React, { Fragment, FC, useState, useEffect } from 'react';
import { Table } from 'reactstrap';
import { withStatusIndicator } from '../../components/withStatusIndicator';
import { useFetch } from '../../hooks/useFetch';
@ -97,7 +97,46 @@ const StatusResult: FC<{ fetchPath: string; title: string }> = ({ fetchPath, tit
);
};
const Status: FC<{ agentMode: boolean }> = ({ agentMode }) => {
interface StatusProps {
agentMode?: boolean | false;
setAnimateLogo?: (animateLogo: boolean) => void;
}
const Status: FC<StatusProps> = ({ agentMode, setAnimateLogo }) => {
/* _
* /' \
* | |
* \__/ */
const [inputText, setInputText] = useState('');
useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
const keyPressed = event.key.toUpperCase();
setInputText((prevInputText) => {
const newInputText = prevInputText.slice(-3) + String.fromCharCode(((keyPressed.charCodeAt(0) - 64) % 26) + 65);
return newInputText;
});
};
document.addEventListener('keypress', handleKeyPress);
return () => {
document.removeEventListener('keypress', handleKeyPress);
};
}, []);
useEffect(() => {
if (setAnimateLogo && inputText != '') {
setAnimateLogo(inputText.toUpperCase() === 'QSPN');
}
}, [inputText]);
/* _
* /' \
* | |
* \__/ */
const pathPrefix = usePathPrefix();
const path = `${pathPrefix}/${API_PATH}`;

View file

@ -104,9 +104,14 @@ button.execute-btn {
margin-bottom: 20px;
}
.navbar-brand img {
.navbar-brand svg {
padding-right: 1rem;
height: 1.9rem;
width: 2.9rem;
}
.navbar-brand svg.animate path {
animation: flamecolor 4s ease-in 1 forwards,flame 1s ease-in infinite;
}
.navbar-brand {
@ -357,3 +362,16 @@ input[type='checkbox']:checked + label {
display: block;
font-family: monospace;
}
@keyframes flamecolor {
100% {
fill: #e95224;
}
}
@keyframes flame {
0% { d: path("M 56.667,0.667 C 25.372,0.667 0,26.036 0,57.332 c 0,31.295 25.372,56.666 56.667,56.666 31.295,0 56.666,-25.371 56.666,-56.666 0,-31.296 -25.372,-56.665 -56.666,-56.665 z m 0,106.055 c -8.904,0 -16.123,-5.948 -16.123,-13.283 H 72.79 c 0,7.334 -7.219,13.283 -16.123,13.283 z M 83.297,89.04 H 30.034 V 79.382 H 83.298 V 89.04 Z M 83.106,74.411 H 30.186 C 30.01,74.208 29.83,74.008 29.66,73.802 24.208,67.182 22.924,63.726 21.677,60.204 c -0.021,-0.116 6.611,1.355 11.314,2.413 0,0 2.42,0.56 5.958,1.205 -3.397,-3.982 -5.414,-9.044 -5.414,-14.218 0,-11.359 8.712,-21.285 5.569,-29.308 3.059,0.249 6.331,6.456 6.552,16.161 3.252,-4.494 4.613,-12.701 4.613,-17.733 0,-5.21 3.433,-11.262 6.867,-11.469 -3.061,5.045 0.793,9.37 4.219,20.099 1.285,4.03 1.121,10.812 2.113,15.113 C 63.797,33.534 65.333,20.5 71,16 c -2.5,5.667 0.37,12.758 2.333,16.167 3.167,5.5 5.087,9.667 5.087,17.548 0,5.284 -1.951,10.259 -5.242,14.148 3.742,-0.702 6.326,-1.335 6.326,-1.335 l 12.152,-2.371 c 10e-4,-10e-4 -1.765,7.261 -8.55,14.254 z");
}
50% { d: path("M 56.667,0.667 C 25.372,0.667 0,26.036 0,57.332 c 0,31.295 25.372,56.666 56.667,56.666 31.295,0 56.666,-25.371 56.666,-56.666 0,-31.296 -25.372,-56.665 -56.666,-56.665 z m 0,106.055 c -8.904,0 -16.123,-5.948 -16.123,-13.283 H 72.79 c 0,7.334 -7.219,13.283 -16.123,13.283 z M 83.297,89.04 H 30.034 V 79.382 H 83.298 V 89.04 Z M 83.106,74.411 H 30.186 C 30.01,74.208 29.83,74.008 29.66,73.802 24.208,67.182 22.924,63.726 21.677,60.204 c -0.021,-0.116 6.611,1.355 11.314,2.413 0,0 2.42,0.56 5.958,1.205 -3.397,-3.982 -5.414,-9.044 -5.414,-14.218 0,-11.359 1.640181,-23.047128 7.294982,-29.291475 C 39.391377,29.509803 45.435,26.752 45.656,36.457 c 3.252,-4.494 7.100362,-8.366957 7.100362,-13.398957 0,-5.21 0.137393,-8.650513 -3.479689,-15.0672265 7.834063,1.6180944 8.448052,4.2381285 11.874052,14.9671285 1.285,4.03 1.325275,15.208055 2.317275,19.509055 0.329,-8.933 6.441001,-14.01461 5.163951,-21.391003 5.755224,5.771457 4.934508,10.495521 7.126537,14.288218 3.167,5.5 2.382625,7.496239 2.382625,15.377239 0,5.284 -1.672113,9.232546 -4.963113,13.121546 3.742,-0.702 6.326,-1.335 6.326,-1.335 l 12.152,-2.371 c 10e-4,-10e-4 -1.765,7.261 -8.55,14.254 z"); }
100% {
d: path("M 56.667,0.667 C 25.372,0.667 0,26.036 0,57.332 c 0,31.295 25.372,56.666 56.667,56.666 31.295,0 56.666,-25.371 56.666,-56.666 0,-31.296 -25.372,-56.665 -56.666,-56.665 z m 0,106.055 c -8.904,0 -16.123,-5.948 -16.123,-13.283 H 72.79 c 0,7.334 -7.219,13.283 -16.123,13.283 z M 83.297,89.04 H 30.034 V 79.382 H 83.298 V 89.04 Z M 83.106,74.411 H 30.186 C 30.01,74.208 29.83,74.008 29.66,73.802 24.208,67.182 22.924,63.726 21.677,60.204 c -0.021,-0.116 6.611,1.355 11.314,2.413 0,0 2.42,0.56 5.958,1.205 -3.397,-3.982 -5.414,-9.044 -5.414,-14.218 0,-11.359 8.712,-21.285 5.569,-29.308 3.059,0.249 6.331,6.456 6.552,16.161 3.252,-4.494 4.613,-12.701 4.613,-17.733 0,-5.21 3.433,-11.262 6.867,-11.469 -3.061,5.045 0.793,9.37 4.219,20.099 1.285,4.03 1.121,10.812 2.113,15.113 C 63.797,33.534 65.333,20.5 71,16 c -2.5,5.667 0.37,12.758 2.333,16.167 3.167,5.5 5.087,9.667 5.087,17.548 0,5.284 -1.951,10.259 -5.242,14.148 3.742,-0.702 6.326,-1.335 6.326,-1.335 l 12.152,-2.371 c 10e-4,-10e-4 -1.765,7.261 -8.55,14.254 z"); }
}