Add metrics module

This commit is contained in:
Daniel 2021-01-07 12:35:21 +01:00
parent fa18d1ef4c
commit 813a5f0f0d
18 changed files with 921 additions and 3 deletions

14
go.mod
View file

@ -5,6 +5,7 @@ go 1.15
require (
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/VictoriaMetrics/metrics v1.12.3
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6
github.com/armon/go-radix v1.0.0
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833
@ -13,7 +14,6 @@ require (
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/gofrs/uuid v3.3.0+incompatible
github.com/golang/protobuf v1.4.2 // indirect
github.com/google/renameio v0.1.0
github.com/gorilla/mux v1.7.4
github.com/gorilla/websocket v1.4.2
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -29,8 +29,16 @@ require (
github.com/tidwall/gjson v1.6.0
github.com/tidwall/sjson v1.1.1
go.etcd.io/bbolt v1.3.4
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20200523222454-059865788121
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 // indirect
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
)
require (
// The follow-up commit removes Windows support.
// TOOD: Check how we want to handle this in the future, possibly ingest
// needed functionality into here.
github.com/google/renameio v0.1.1-0.20200217212219-353f81969824
)

58
go.sum
View file

@ -1,17 +1,24 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/metrics v1.12.3 h1:Fe6JHC6MSEKa+BtLhPN8WIvS+HKPzMc2evEpNeCGy7I=
github.com/VictoriaMetrics/metrics v1.12.3/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpYk2gxGJnDjsYuboNTcRmbtGKGs=
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
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=
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833 h1:yCfXxYaelOyqnia8F/Yng47qhmfC9nKTRIbYRrRueq4=
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -27,19 +34,24 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.1 h1:w9pSFNSdq/JPM1N12Fz/F/bzo993Is1W+Q7HjPzi7yg=
github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU=
github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@ -53,6 +65,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -61,6 +74,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/renameio v0.1.1-0.20200217212219-353f81969824 h1:9q700G0beHecUuiZOuKgNqNsGQixTeDLnzVZ5nsW3lc=
github.com/google/renameio v0.1.1-0.20200217212219-353f81969824/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
@ -73,6 +89,7 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
@ -114,8 +131,11 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0=
github.com/seehuhn/fortuna v1.0.1/go.mod h1:LX8ubejCnUoT/hX+1aKUtbKls2H6DRkqzkc7TdR3iis=
github.com/seehuhn/sha256d v1.0.0 h1:TXTsAuEWr02QjRm153Fnvvb6fXXDo7Bmy1FizxarGYw=
github.com/seehuhn/sha256d v1.0.0/go.mod h1:PEuxg9faClSveVuFXacQmi+NtDI/PX8bpKjtNzf2+s4=
github.com/shirou/gopsutil v2.20.4+incompatible h1:cMT4rxS55zx9NVUnCkrmXCsEB/RNfG9SwHY9evtX8Ng=
github.com/shirou/gopsutil v2.20.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@ -125,6 +145,7 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -140,17 +161,27 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tevino/abool v1.0.0 h1:5hlcsW0yartQp609pbLLrE/s3ZNm2k/F7YSGuqJxpbM=
github.com/tevino/abool v1.0.0/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U=
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.1.2 h1:vOk5VrGjMBIoPR5k6wA8vBaC8toeJ8XO0yfRjFEc1h8=
github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@ -158,19 +189,31 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -179,17 +222,31 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY=
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
@ -199,6 +256,7 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

138
metrics/api.go Normal file
View file

@ -0,0 +1,138 @@
package metrics
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
"github.com/safing/portbase/api"
"github.com/safing/portbase/config"
"github.com/safing/portbase/log"
)
func registerAPI() error {
api.RegisterHandler("/metrics", &metricsAPI{})
return api.RegisterEndpoint(api.Endpoint{
Path: "metrics/list",
Read: api.PermitAnyone,
MimeType: api.MimeTypeJSON,
DataFunc: func(*api.Request) ([]byte, error) {
registryLock.RLock()
defer registryLock.RUnlock()
return json.Marshal(registry)
},
Name: "Export Registered Metrics",
Description: "List all registered metrics with their metadata.",
})
}
type metricsAPI struct{}
func (m *metricsAPI) ReadPermission(*http.Request) api.Permission { return api.Dynamic }
func (m *metricsAPI) WritePermission(*http.Request) api.Permission { return api.NotSupported }
func (m *metricsAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Get API Request for permission and query.
ar := api.GetAPIRequest(r)
if ar == nil {
http.Error(w, "Missing API Request.", http.StatusInternalServerError)
return
}
// Get expertise level from query.
expertiseLevel := config.ExpertiseLevelDeveloper
switch ar.Request.URL.Query().Get("level") {
case config.ExpertiseLevelNameUser:
expertiseLevel = config.ExpertiseLevelUser
case config.ExpertiseLevelNameExpert:
expertiseLevel = config.ExpertiseLevelExpert
case config.ExpertiseLevelNameDeveloper:
expertiseLevel = config.ExpertiseLevelDeveloper
}
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
w.WriteHeader(http.StatusOK)
WriteMetrics(w, ar.AuthToken.Read, expertiseLevel)
}
// WriteMetrics writes all metrics that match the given permission and
// expertiseLevel to the given writer.
func WriteMetrics(w io.Writer, permission api.Permission, expertiseLevel config.ExpertiseLevel) {
registryLock.RLock()
defer registryLock.RUnlock()
// Check if metric ID is already registered.
for _, metric := range registry {
if permission >= metric.Opts().Permission &&
expertiseLevel >= metric.Opts().ExpertiseLevel {
metric.WritePrometheus(w)
}
}
}
func writeMetricsTo(ctx context.Context, url string) error {
// First, collect metrics into buffer.
buf := &bytes.Buffer{}
WriteMetrics(buf, api.PermitSelf, config.ExpertiseLevelDeveloper)
// Check if there is something to send.
if buf.Len() == 0 {
log.Debugf("metrics: not pushing metrics, nothing to send")
return nil
}
// Create request
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, buf)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Send.
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Check return status.
switch resp.StatusCode {
case http.StatusOK,
http.StatusAccepted,
http.StatusNoContent:
return nil
default:
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf(
"got %s while writing metrics to %s: %s",
resp.Status,
url,
body,
)
}
}
func metricsWriter(ctx context.Context) error {
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
err := writeMetricsTo(ctx, pushURL)
if err != nil {
return err
}
}
}
}

152
metrics/metric.go Normal file
View file

@ -0,0 +1,152 @@
package metrics
import (
"fmt"
"io"
"regexp"
"sort"
"strings"
"github.com/safing/portbase/api"
"github.com/safing/portbase/config"
vm "github.com/VictoriaMetrics/metrics"
)
// PrometheusFormatRequirement is required format defined by prometheus for
// metric and label names.
const PrometheusFormatRequirement = "[a-zA-Z_][a-zA-Z0-9_]*"
var prometheusFormat = regexp.MustCompile(PrometheusFormatRequirement)
// Metric represents one or more metrics.
type Metric interface {
ID() string
LabeledID() string
Opts() *Options
WritePrometheus(w io.Writer)
}
type metricBase struct {
Identifier string
Labels map[string]string
LabeledIdentifier string
Options *Options
set *vm.Set
}
// Options can be used to set advanced metric settings.
type Options struct {
// Name defines an optional human readable name for the metric.
Name string
// AlertLimit defines an upper limit that triggers an alert.
AlertLimit float64
// AlertTimeframe defines an optional timeframe in seconds for which the
// AlertLimit should be interpreted in.
AlertTimeframe float64
// Permission defines the permission that is required to read the metric.
Permission api.Permission
// ExpertiseLevel defines the expertise level that the metric is meant for.
ExpertiseLevel config.ExpertiseLevel
// Persist enabled persisting the metric on shutdown and loading the previous
// value at start. This is only supported for counters.
Persist bool
}
func newMetricBase(id string, labels map[string]string, opts Options) (*metricBase, error) {
// Check formats.
if !prometheusFormat.MatchString(id) {
return nil, fmt.Errorf("metric name %q must match %s", id, PrometheusFormatRequirement)
}
for labelName := range labels {
if !prometheusFormat.MatchString(labelName) {
return nil, fmt.Errorf("metric label name %q must match %s", labelName, PrometheusFormatRequirement)
}
}
// Check permission.
if opts.Permission < api.PermitAnyone {
// Default to PermitUser.
opts.Permission = api.PermitUser
}
// Create metric base.
base := &metricBase{
Identifier: id,
Labels: labels,
Options: &opts,
set: vm.NewSet(),
}
base.LabeledIdentifier = base.buildLabeledID()
return base, nil
}
// ID returns the given ID of the metric.
func (m *metricBase) ID() string {
return m.Identifier
}
// LabeledID returns the Prometheus-compatible labeled ID of the metric.
func (m *metricBase) LabeledID() string {
return m.LabeledIdentifier
}
// Opts returns the metric options. They may not be modified.
func (m *metricBase) Opts() *Options {
return m.Options
}
// WritePrometheus writes the metric in the prometheus format to the given writer.
func (m *metricBase) WritePrometheus(w io.Writer) {
m.set.WritePrometheus(w)
}
func (m *metricBase) buildLabeledID() string {
// Because we use the namespace and the global flags here, we need to flag
// them as immutable.
registryLock.Lock()
defer registryLock.Unlock()
firstMetricRegistered = true
// Build ID from Identifier.
metricID := strings.TrimSpace(strings.ReplaceAll(m.Identifier, "/", "_"))
// Add namespace to ID.
if metricNamespace != "" {
metricID = metricNamespace + "_" + metricID
}
// Return now if no labels are defined.
if len(globalLabels) == 0 && len(m.Labels) == 0 {
return metricID
}
// Add global labels to the custom ones.
// This overrides conflicts.
for labelName, labelValue := range globalLabels {
m.Labels[labelName] = labelValue
}
// Render labels into a slice and sort them in order to make the labeled ID
// reproducible.
labels := make([]string, 0, len(m.Labels))
for labelName, labelValue := range m.Labels {
labels = append(labels, fmt.Sprintf("%s=%q", labelName, labelValue))
}
sort.Strings(labels)
// Return fully labaled ID.
return fmt.Sprintf("%s{%s}", metricID, strings.Join(labels, ","))
}
// Split metrics into sets, according to the API Auth Levels, which will also correspond to the UI Mode levels. SPN // nodes will also allow public access to metrics with the permission "PermitAnyone".
// Save "life-long" metrics on shutdown and load them at start.
// Generate the correct metric name and labels.
// Expose metrics via http, but also via the runtime DB in order to push metrics to the UI.
// The UI will have to parse the prometheus metrics format and will not be able to immediately present historical data, // but data will have to be built.
// Provide the option to push metrics to a prometheus push gateway, this is especially helpful when gathering data from // loads of SPN nodes.

44
metrics/metric_counter.go Normal file
View file

@ -0,0 +1,44 @@
package metrics
import (
vm "github.com/VictoriaMetrics/metrics"
)
// Counter is a counter metric.
type Counter struct {
*metricBase
*vm.Counter
}
// NewCounter registers a new counter metric.
func NewCounter(id string, labels map[string]string, opts *Options) (*Counter, error) {
// Ensure that there are options.
if opts == nil {
opts = &Options{}
}
// Make base.
base, err := newMetricBase(id, labels, *opts)
if err != nil {
return nil, err
}
// Create metric struct.
m := &Counter{
metricBase: base,
}
// Create metric in set
m.Counter = m.set.NewCounter(m.LabeledID())
// Register metric.
err = register(m)
if err != nil {
return nil, err
}
// Load state.
m.loadState()
return m, nil
}

41
metrics/metric_gauge.go Normal file
View file

@ -0,0 +1,41 @@
package metrics
import (
vm "github.com/VictoriaMetrics/metrics"
)
// Gauge is a gauge metric.
type Gauge struct {
*metricBase
*vm.Gauge
}
// NewGauge registers a new gauge metric.
func NewGauge(id string, labels map[string]string, fn func() float64, opts *Options) (*Gauge, error) {
// Ensure that there are options.
if opts == nil {
opts = &Options{}
}
// Make base.
base, err := newMetricBase(id, labels, *opts)
if err != nil {
return nil, err
}
// Create metric struct.
m := &Gauge{
metricBase: base,
}
// Create metric in set
m.Gauge = m.set.NewGauge(m.LabeledID(), fn)
// Register metric.
err = register(m)
if err != nil {
return nil, err
}
return m, nil
}

View file

@ -0,0 +1,41 @@
package metrics
import (
vm "github.com/VictoriaMetrics/metrics"
)
// Histogram is a histogram metric.
type Histogram struct {
*metricBase
*vm.Histogram
}
// NewHistogram registers a new histogram metric.
func NewHistogram(id string, labels map[string]string, opts *Options) (*Histogram, error) {
// Ensure that there are options.
if opts == nil {
opts = &Options{}
}
// Make base.
base, err := newMetricBase(id, labels, *opts)
if err != nil {
return nil, err
}
// Create metric struct.
m := &Histogram{
metricBase: base,
}
// Create metric in set
m.Histogram = m.set.NewHistogram(m.LabeledID())
// Register metric.
err = register(m)
if err != nil {
return nil, err
}
return m, nil
}

40
metrics/metric_info.go Normal file
View file

@ -0,0 +1,40 @@
package metrics
import (
"runtime"
"strings"
"github.com/safing/portbase/info"
)
func registerInfoMetric() error {
meta := info.GetInfo()
_, err := NewGauge(
"info",
map[string]string{
"version": checkUnknown(meta.Version),
"commit": checkUnknown(meta.Commit),
"build_options": checkUnknown(meta.BuildOptions),
"build_user": checkUnknown(meta.BuildUser),
"build_host": checkUnknown(meta.BuildHost),
"build_date": checkUnknown(meta.BuildDate),
"build_source": checkUnknown(meta.BuildSource),
"go_os": runtime.GOOS,
"go_arch": runtime.GOARCH,
"go_version": runtime.Version(),
"go_compiler": runtime.Compiler,
},
func() float64 {
return 1
},
nil,
)
return err
}
func checkUnknown(s string) string {
if strings.Contains(s, "unknown") {
return "unknown"
}
return s
}

45
metrics/metric_runtime.go Normal file
View file

@ -0,0 +1,45 @@
package metrics
import (
"io"
vm "github.com/VictoriaMetrics/metrics"
"github.com/safing/portbase/api"
"github.com/safing/portbase/config"
)
func init() {
registry = append(registry, &runtimeMetrics{})
}
var runtimeOpts = &Options{
Name: "Golang Runtime",
Permission: api.PermitAdmin,
ExpertiseLevel: config.ExpertiseLevelDeveloper,
}
type runtimeMetrics struct{}
func (r *runtimeMetrics) ID() string {
return "_runtime"
}
func (r *runtimeMetrics) LabeledID() string {
return "_runtime"
}
func (r *runtimeMetrics) Opts() *Options {
return runtimeOpts
}
func (r *runtimeMetrics) Permission() api.Permission {
return runtimeOpts.Permission
}
func (r *runtimeMetrics) ExpertiseLevel() config.ExpertiseLevel {
return runtimeOpts.ExpertiseLevel
}
func (r *runtimeMetrics) WritePrometheus(w io.Writer) {
vm.WriteProcessMetrics(w)
}

131
metrics/module.go Normal file
View file

@ -0,0 +1,131 @@
package metrics
import (
"errors"
"flag"
"fmt"
"sort"
"sync"
"github.com/safing/portbase/modules"
)
var (
module *modules.Module
registry []Metric
registryLock sync.RWMutex
firstMetricRegistered bool
metricNamespace string
globalLabels = make(map[string]string)
pushURL string
// ErrAlreadyStarted is returned when an operation is only valid before the
// first metric is registered, and is called after.
ErrAlreadyStarted = errors.New("can only be changed before first metric is registered")
// ErrAlreadyRegistered is returned when a metric with the same ID is
// registered again.
ErrAlreadyRegistered = errors.New("metric already registered")
// ErrAlreadySet is returned when a value is already set and cannot be changed.
ErrAlreadySet = errors.New("already set")
)
func init() {
flag.StringVar(&pushURL, "push-metrics", "", "URL to push prometheus metrics to")
module = modules.Register("metrics", prep, start, stop, "database", "api")
}
func prep() error {
return registerInfoMetric()
}
func start() error {
if err := registerAPI(); err != nil {
return err
}
if pushURL != "" {
module.StartServiceWorker("metric pusher", 0, metricsWriter)
}
return nil
}
func stop() error {
storePersistentMetrics()
return nil
}
func register(m Metric) error {
registryLock.Lock()
defer registryLock.Unlock()
// Check if metric ID is already registered.
for _, registeredMetric := range registry {
if m.LabeledID() == registeredMetric.LabeledID() {
return ErrAlreadyRegistered
}
}
// Add new metric to registry and sort it.
registry = append(registry, m)
sort.Sort(metricRegistry(registry))
// Set flag that first metric is now registered.
firstMetricRegistered = true
return nil
}
// SetNamespace sets the namespace for all metrics. It is prefixed to all
// metric IDs.
// It must be set before any metric is registered.
// Does not affect golang runtime metrics.
func SetNamespace(namespace string) error {
// Lock registry and check if a first metric is already registered.
registryLock.Lock()
defer registryLock.Unlock()
if firstMetricRegistered {
return ErrAlreadyStarted
}
// Check if the namespace is already set.
if metricNamespace != "" {
return ErrAlreadySet
}
metricNamespace = namespace
return nil
}
// AddGlobalLabel adds a global label to all metrics.
// Global labels must be added before any metric is registered.
// Does not affect golang runtime metrics.
func AddGlobalLabel(name, value string) error {
// Lock registry and check if a first metric is already registered.
registryLock.Lock()
defer registryLock.Unlock()
if firstMetricRegistered {
return ErrAlreadyStarted
}
// Check format.
if !prometheusFormat.MatchString(name) {
return fmt.Errorf("metric label name %q must match %s", name, PrometheusFormatRequirement)
}
globalLabels[name] = value
return nil
}
type metricRegistry []Metric
func (r metricRegistry) Len() int { return len(r) }
func (r metricRegistry) Less(i, j int) bool { return r[i].LabeledID() < r[j].LabeledID() }
func (r metricRegistry) Swap(i, j int) { r[i], r[j] = r[j], r[i] }

146
metrics/persistence.go Normal file
View file

@ -0,0 +1,146 @@
package metrics
import (
"errors"
"fmt"
"sync"
"time"
"github.com/safing/portbase/database"
"github.com/safing/portbase/database/record"
"github.com/safing/portbase/log"
"github.com/tevino/abool"
)
var (
storage *metricsStorage
storageLoaded = abool.New()
storageKey string
db = database.NewInterface(&database.Options{
Local: true,
Internal: true,
})
)
type metricsStorage struct {
sync.Mutex
record.Base
Start time.Time
Counters map[string]uint64
}
// EnableMetricPersistence enables metric persistence for metrics that opted
// for it. They given key is the database key where the metric data will be
// persisted.
// This call also directly loads the stored data from the database.
// The returned error is only about loading the metrics, not about enabling
// persistence.
// May only be called once.
func EnableMetricPersistence(key string) error {
// Check if already loaded.
if storageLoaded.IsSet() {
return nil
}
// Set storage key.
storageKey = key
// Load metrics from storage.
var err error
storage, err = getMetricsStorage(key)
switch {
case err == nil:
// Continue.
case errors.Is(err, database.ErrNotFound):
return nil
default:
return err
}
storageLoaded.Set()
// Load saved state for all counter metrics.
registryLock.RLock()
defer registryLock.RUnlock()
for _, m := range registry {
counter, ok := m.(*Counter)
if ok {
counter.loadState()
}
}
return nil
}
func (c *Counter) loadState() {
// Check if we can and should load the state.
if !storageLoaded.IsSet() || !c.Opts().Persist {
return
}
c.Set(storage.Counters[c.LabeledID()])
}
func storePersistentMetrics() {
// Check if persistence is enabled.
if storageKey == "" {
return
}
// Create new storage.
newStorage := &metricsStorage{
Start: time.Now(),
Counters: make(map[string]uint64),
}
newStorage.SetKey(storageKey)
// Copy values from previous version.
if storageLoaded.IsSet() {
newStorage.Start = storage.Start
}
registryLock.RLock()
defer registryLock.RUnlock()
// Export all counter metrics.
for _, m := range registry {
if m.Opts().Persist {
counter, ok := m.(*Counter)
if ok {
newStorage.Counters[m.LabeledID()] = counter.Get()
}
}
}
// Save to database.
err := db.Put(newStorage)
if err != nil {
log.Warningf("metrics: failed to save metrics storage to db: %s", err)
}
}
func getMetricsStorage(key string) (*metricsStorage, error) {
r, err := db.Get(key)
if err != nil {
return nil, err
}
// unwrap
if r.IsWrapped() {
// only allocate a new struct, if we need it
new := &metricsStorage{}
err = record.Unwrap(r, new)
if err != nil {
return nil, err
}
return new, nil
}
// or adjust type
new, ok := r.(*metricsStorage)
if !ok {
return nil, fmt.Errorf("record not of type *metricsStorage, but %T", r)
}
return new, nil
}

1
metrics/test/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
data

4
metrics/test/README.md Normal file
View file

@ -0,0 +1,4 @@
# Testing metrics
You can spin up a test setup for pushing and viewing metrics with `docker-compose up`.
Then use the flag `--push-metrics http://127.0.0.1:8428/api/v1/import/prometheus` to push metrics.

View file

@ -0,0 +1,36 @@
version: '3.8'
networks:
pm-metrics-test-net:
services:
victoriametrics:
container_name: pm-metrics-test-victoriametrics
image: victoriametrics/victoria-metrics
command:
- '--storageDataPath=/storage'
ports:
- 8428:8428
volumes:
- ./data/victoriametrics:/storage
networks:
- pm-metrics-test-net
restart: always
grafana:
container_name: pm-metrics-test-grafana
image: grafana/grafana
command:
- '--config=/etc/grafana/provisioning/config.ini'
depends_on:
- "victoriametrics"
ports:
- 3000:3000
volumes:
- ./data/grafana:/var/lib/grafana
- ./grafana:/etc/grafana/provisioning
- ./dashboards:/dashboards
networks:
- pm-metrics-test-net
restart: always

View file

@ -0,0 +1,10 @@
[auth]
disable_login_form = true
disable_signout_menu = true
[auth.basic]
enabled = false
[auth.anonymous]
enabled = true
org_role = Admin

View file

@ -0,0 +1,11 @@
apiVersion: 1
providers:
- name: 'Portmaster'
folder: 'Portmaster'
disableDeletion: true
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /dashboards
foldersFromFilesStructure: true

View file

@ -0,0 +1,8 @@
apiVersion: 1
datasources:
- name: VictoriaMetrics
type: prometheus
access: proxy
url: http://pm-metrics-test-victoriametrics:8428
isDefault: true

View file

@ -5,6 +5,10 @@ import (
"io"
"os"
// Version is fixed to commit 353f8196982447d8b12c64f69530e657331e3dbc.
// The follow-up commit removes Windows support.
// TOOD: Check how we want to handle this in the future, possibly ingest
// needed functionality into here.
"github.com/google/renameio"
)