first commit
This commit is contained in:
commit
8fd487b935
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
cache.json
|
13
.golangci.yml
Normal file
13
.golangci.yml
Normal file
@ -0,0 +1,13 @@
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- depguard
|
||||
- exhaustivestruct
|
||||
- exhaustruct
|
||||
- gomnd
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
- varnamelen
|
||||
run:
|
||||
skip-dirs:
|
||||
- httpslog/internal/mutil/*
|
50
Earthfile
Normal file
50
Earthfile
Normal file
@ -0,0 +1,50 @@
|
||||
VERSION 0.7
|
||||
ARG --global GO_VERSION=1.22.0
|
||||
ARG --global GOLANGCILINT_VERSION=v1.54.2
|
||||
ARG --global GORELEASER_VERSION=v1.24.0
|
||||
FROM golang:$GO_VERSION-alpine3.18
|
||||
WORKDIR /glucose_exporter
|
||||
|
||||
SAVE_CODE:
|
||||
COMMAND
|
||||
SAVE ARTIFACT go.mod AS LOCAL go.mod
|
||||
SAVE ARTIFACT go.sum AS LOCAL go.sum
|
||||
SAVE ARTIFACT api AS LOCAL api
|
||||
SAVE ARTIFACT httpslog AS LOCAL httpslog
|
||||
SAVE ARTIFACT internal AS LOCAL internal
|
||||
|
||||
code:
|
||||
COPY go.mod go.sum ./
|
||||
COPY --dir api ./
|
||||
COPY --dir httpslog ./
|
||||
COPY --dir internal ./
|
||||
COPY --dir vendor ./
|
||||
SAVE ARTIFACT . code
|
||||
|
||||
ci:
|
||||
BUILD +test
|
||||
BUILD +lint
|
||||
|
||||
test:
|
||||
COPY +code/code ./
|
||||
RUN go test -v ./...
|
||||
|
||||
tidy:
|
||||
RUN apk add --no-cache git
|
||||
COPY +code/code ./
|
||||
RUN go mod tidy
|
||||
RUN go mod vendor
|
||||
SAVE ARTIFACT go.mod AS LOCAL go.mod
|
||||
SAVE ARTIFACT go.sum AS LOCAL go.sum
|
||||
SAVE ARTIFACT vendor AS LOCAL vendor
|
||||
|
||||
deps:
|
||||
FROM +base
|
||||
RUN (cd /tmp; go install github.com/golangci/golangci-lint/cmd/golangci-lint@$GOLANGCILINT_VERSION)
|
||||
RUN (cd /tmp; go install github.com/goreleaser/goreleaser@$GORELEASER_VERSION)
|
||||
|
||||
lint:
|
||||
FROM +deps
|
||||
COPY +code/code ./
|
||||
COPY .golangci.yml .golangci.yml
|
||||
RUN golangci-lint run
|
143
api/api.go
Normal file
143
api/api.go
Normal file
@ -0,0 +1,143 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"go.xsfx.dev/glucose_exporter/internal/epoch"
|
||||
)
|
||||
|
||||
const BaseURL = "https://api.libreview.io/llu"
|
||||
|
||||
// client is our default HTTP client to use.
|
||||
//
|
||||
//nolint:gochecknoglobals
|
||||
var client = http.Client{
|
||||
Timeout: 2 * time.Second,
|
||||
}
|
||||
|
||||
func request(
|
||||
ctx context.Context,
|
||||
method string,
|
||||
url string,
|
||||
token string,
|
||||
data []byte,
|
||||
) (*http.Response, error) {
|
||||
req, err := http.NewRequest(method, url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return &http.Response{}, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("cache-control", "no-cache")
|
||||
req.Header.Add("connection", "Keep-Alive")
|
||||
req.Header.Add("content-type", "application/json")
|
||||
req.Header.Add("product", "llu.android")
|
||||
req.Header.Add("version", "4.7")
|
||||
|
||||
if token != "" {
|
||||
req.Header.Add("authorization", "Bearer "+token)
|
||||
}
|
||||
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
return &http.Response{}, fmt.Errorf("doing the request: %w", err)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type Ticket struct {
|
||||
Token string `json:"token"`
|
||||
Expires epoch.Epoch `json:"expires"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
Data struct {
|
||||
AuthTicket Ticket `json:"authTicket"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func Login(ctx context.Context, baseURL, username, password string) (LoginResponse, error) {
|
||||
data := struct {
|
||||
Email string `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}{username, password}
|
||||
|
||||
d, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return LoginResponse{}, fmt.Errorf("marshalling data: %w", err)
|
||||
}
|
||||
|
||||
url, err := url.JoinPath(baseURL, "/auth/login")
|
||||
if err != nil {
|
||||
return LoginResponse{}, fmt.Errorf("joining url: %w", err)
|
||||
}
|
||||
|
||||
resp, err := request(ctx, http.MethodPost, url, "", d)
|
||||
if err != nil {
|
||||
return LoginResponse{}, fmt.Errorf("doing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return LoginResponse{}, fmt.Errorf("reading body: %w", err)
|
||||
}
|
||||
|
||||
var authTicket LoginResponse
|
||||
if err := json.Unmarshal(body, &authTicket); err != nil {
|
||||
return LoginResponse{}, fmt.Errorf("unmarshal login response: %w", err)
|
||||
}
|
||||
|
||||
return authTicket, nil
|
||||
}
|
||||
|
||||
type ConnectionsResponse struct {
|
||||
Data []struct {
|
||||
PatientID string `json:"patientID"`
|
||||
|
||||
Sensor struct {
|
||||
DeviceID string `json:"deviceId"`
|
||||
Serialnumber string `json:"sn"`
|
||||
} `json:"sensor"`
|
||||
|
||||
GlucoseMeasurement struct {
|
||||
ValueInMgPerDl int `json:"ValueInMgPerDl"`
|
||||
TrendArrow int `json:"TrendArrow"`
|
||||
} `json:"glucoseMeasurement"`
|
||||
} `json:"data"`
|
||||
|
||||
Ticket Ticket `json:"ticket"`
|
||||
}
|
||||
|
||||
func Connections(ctx context.Context, baseURL, token string) (ConnectionsResponse, error) {
|
||||
url, err := url.JoinPath(baseURL, "/connections")
|
||||
if err != nil {
|
||||
return ConnectionsResponse{}, fmt.Errorf("joining url: %w", err)
|
||||
}
|
||||
|
||||
resp, err := request(ctx, http.MethodGet, url, token, []byte{})
|
||||
if err != nil {
|
||||
return ConnectionsResponse{}, fmt.Errorf("doing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ConnectionsResponse{}, fmt.Errorf("reading body: %w", err)
|
||||
}
|
||||
|
||||
var connResp ConnectionsResponse
|
||||
|
||||
if err := json.Unmarshal(body, &connResp); err != nil {
|
||||
return ConnectionsResponse{}, fmt.Errorf("unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return connResp, nil
|
||||
}
|
17
go.mod
Normal file
17
go.mod
Normal file
@ -0,0 +1,17 @@
|
||||
module go.xsfx.dev/glucose_exporter
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0
|
||||
github.com/VictoriaMetrics/metrics v1.33.0
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/lmittmann/tint v1.0.4
|
||||
github.com/matryer/is v1.4.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/valyala/fastrand v1.1.0 // indirect
|
||||
github.com/valyala/histogram v1.2.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
)
|
19
go.sum
Normal file
19
go.sum
Normal file
@ -0,0 +1,19 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/VictoriaMetrics/metrics v1.33.0 h1:EnkDEaGiL2u95t+W76GfecC/LMYpy+tFrexYzBWQIAc=
|
||||
github.com/VictoriaMetrics/metrics v1.33.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
|
||||
github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA=
|
||||
github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18=
|
||||
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
|
||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
|
||||
github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ=
|
||||
github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
59
httpslog/httpslog.go
Normal file
59
httpslog/httpslog.go
Normal file
@ -0,0 +1,59 @@
|
||||
package httpslog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VictoriaMetrics/metrics"
|
||||
"go.xsfx.dev/glucose_exporter/httpslog/internal/mutil"
|
||||
)
|
||||
|
||||
func Handler() func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
lw := mutil.WrapWriter(w)
|
||||
defer func() {
|
||||
// Logging.
|
||||
slog.Info(
|
||||
"handled request",
|
||||
"status",
|
||||
lw.Status(),
|
||||
"bytes",
|
||||
lw.BytesWritten(),
|
||||
"duration",
|
||||
time.Since(start),
|
||||
"method",
|
||||
r.Method,
|
||||
"url",
|
||||
r.URL,
|
||||
"user-agent",
|
||||
r.UserAgent(),
|
||||
"proto",
|
||||
r.Proto,
|
||||
)
|
||||
|
||||
// Metrics.
|
||||
labels := fmt.Sprintf(
|
||||
`method="%s",url="%s",status="%d"`,
|
||||
r.Method,
|
||||
r.URL,
|
||||
lw.Status(),
|
||||
)
|
||||
|
||||
metrics.GetOrCreateCounter(fmt.Sprintf(`requests_total{%s}`, labels)).Inc()
|
||||
|
||||
metrics.GetOrCreateSummary(
|
||||
fmt.Sprintf(`requests_duration_seconds{%s}`, labels),
|
||||
).UpdateDuration(start)
|
||||
|
||||
metrics.GetOrCreateHistogram(
|
||||
fmt.Sprintf("response_size{%s}", labels),
|
||||
).Update(float64(lw.BytesWritten()))
|
||||
}()
|
||||
next.ServeHTTP(lw, r)
|
||||
})
|
||||
}
|
||||
}
|
20
httpslog/internal/mutil/LICENSE
Normal file
20
httpslog/internal/mutil/LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2014, 2015, 2016 Carl Jackson (carl@avtok.com)
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
6
httpslog/internal/mutil/mutil.go
Normal file
6
httpslog/internal/mutil/mutil.go
Normal file
@ -0,0 +1,6 @@
|
||||
// Package mutil contains various functions that are helpful when writing http
|
||||
// middleware.
|
||||
//
|
||||
// It has been vendored from Goji v1.0, with the exception of the code for Go 1.8:
|
||||
// https://github.com/zenazn/goji/
|
||||
package mutil
|
154
httpslog/internal/mutil/writer_proxy.go
Normal file
154
httpslog/internal/mutil/writer_proxy.go
Normal file
@ -0,0 +1,154 @@
|
||||
package mutil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// WriterProxy is a proxy around an http.ResponseWriter that allows you to hook
|
||||
// into various parts of the response process.
|
||||
type WriterProxy interface {
|
||||
http.ResponseWriter
|
||||
// Status returns the HTTP status of the request, or 0 if one has not
|
||||
// yet been sent.
|
||||
Status() int
|
||||
// BytesWritten returns the total number of bytes sent to the client.
|
||||
BytesWritten() int
|
||||
// Tee causes the response body to be written to the given io.Writer in
|
||||
// addition to proxying the writes through. Only one io.Writer can be
|
||||
// tee'd to at once: setting a second one will overwrite the first.
|
||||
// Writes will be sent to the proxy before being written to this
|
||||
// io.Writer. It is illegal for the tee'd writer to be modified
|
||||
// concurrently with writes.
|
||||
Tee(io.Writer)
|
||||
// Unwrap returns the original proxied target.
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// WrapWriter wraps an http.ResponseWriter, returning a proxy that allows you to
|
||||
// hook into various parts of the response process.
|
||||
func WrapWriter(w http.ResponseWriter) WriterProxy {
|
||||
_, cn := w.(http.CloseNotifier)
|
||||
_, fl := w.(http.Flusher)
|
||||
_, hj := w.(http.Hijacker)
|
||||
_, rf := w.(io.ReaderFrom)
|
||||
|
||||
bw := basicWriter{ResponseWriter: w}
|
||||
if cn && fl && hj && rf {
|
||||
return &fancyWriter{bw}
|
||||
}
|
||||
if fl {
|
||||
return &flushWriter{bw}
|
||||
}
|
||||
return &bw
|
||||
}
|
||||
|
||||
// basicWriter wraps a http.ResponseWriter that implements the minimal
|
||||
// http.ResponseWriter interface.
|
||||
type basicWriter struct {
|
||||
http.ResponseWriter
|
||||
wroteHeader bool
|
||||
code int
|
||||
bytes int
|
||||
tee io.Writer
|
||||
}
|
||||
|
||||
func (b *basicWriter) WriteHeader(code int) {
|
||||
if !b.wroteHeader {
|
||||
b.code = code
|
||||
b.wroteHeader = true
|
||||
b.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *basicWriter) Write(buf []byte) (int, error) {
|
||||
b.WriteHeader(http.StatusOK)
|
||||
n, err := b.ResponseWriter.Write(buf)
|
||||
if b.tee != nil {
|
||||
_, err2 := b.tee.Write(buf[:n])
|
||||
// Prefer errors generated by the proxied writer.
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
}
|
||||
b.bytes += n
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (b *basicWriter) maybeWriteHeader() {
|
||||
if !b.wroteHeader {
|
||||
b.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *basicWriter) Status() int {
|
||||
return b.code
|
||||
}
|
||||
|
||||
func (b *basicWriter) BytesWritten() int {
|
||||
return b.bytes
|
||||
}
|
||||
|
||||
func (b *basicWriter) Tee(w io.Writer) {
|
||||
b.tee = w
|
||||
}
|
||||
|
||||
func (b *basicWriter) Unwrap() http.ResponseWriter {
|
||||
return b.ResponseWriter
|
||||
}
|
||||
|
||||
// fancyWriter is a writer that additionally satisfies http.CloseNotifier,
|
||||
// http.Flusher, http.Hijacker, and io.ReaderFrom. It exists for the common case
|
||||
// of wrapping the http.ResponseWriter that package http gives you, in order to
|
||||
// make the proxied object support the full method set of the proxied object.
|
||||
type fancyWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *fancyWriter) CloseNotify() <-chan bool {
|
||||
cn := f.basicWriter.ResponseWriter.(http.CloseNotifier)
|
||||
return cn.CloseNotify()
|
||||
}
|
||||
|
||||
func (f *fancyWriter) Flush() {
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
func (f *fancyWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hj := f.basicWriter.ResponseWriter.(http.Hijacker)
|
||||
return hj.Hijack()
|
||||
}
|
||||
|
||||
func (f *fancyWriter) ReadFrom(r io.Reader) (int64, error) {
|
||||
if f.basicWriter.tee != nil {
|
||||
n, err := io.Copy(&f.basicWriter, r)
|
||||
f.bytes += int(n)
|
||||
return n, err
|
||||
}
|
||||
rf := f.basicWriter.ResponseWriter.(io.ReaderFrom)
|
||||
f.basicWriter.maybeWriteHeader()
|
||||
|
||||
n, err := rf.ReadFrom(r)
|
||||
f.bytes += int(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
type flushWriter struct {
|
||||
basicWriter
|
||||
}
|
||||
|
||||
func (f *flushWriter) Flush() {
|
||||
fl := f.basicWriter.ResponseWriter.(http.Flusher)
|
||||
fl.Flush()
|
||||
}
|
||||
|
||||
var (
|
||||
_ http.CloseNotifier = &fancyWriter{}
|
||||
_ http.Flusher = &fancyWriter{}
|
||||
_ http.Hijacker = &fancyWriter{}
|
||||
_ io.ReaderFrom = &fancyWriter{}
|
||||
_ http.Flusher = &flushWriter{}
|
||||
)
|
66
internal/cache/cache.go
vendored
Normal file
66
internal/cache/cache.go
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"go.xsfx.dev/glucose_exporter/internal/config"
|
||||
"go.xsfx.dev/glucose_exporter/internal/epoch"
|
||||
)
|
||||
|
||||
const cacheFile = "cache.json"
|
||||
|
||||
type Cache struct {
|
||||
JWT string `json:"jwt,omitempty"`
|
||||
Expires epoch.Epoch `json:"expires,omitempty"`
|
||||
BaseURL string `json:"base_url,omitempty"`
|
||||
}
|
||||
|
||||
func FullPath() string {
|
||||
return path.Join(config.Cfg.CacheDir, cacheFile)
|
||||
}
|
||||
|
||||
func Load() (Cache, error) {
|
||||
var c Cache
|
||||
|
||||
slog.Debug("reading cache", "file", FullPath())
|
||||
|
||||
b, err := os.ReadFile(FullPath())
|
||||
if err != nil {
|
||||
return Cache{}, fmt.Errorf("reading cache file: %w", err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &c); err != nil {
|
||||
return Cache{}, fmt.Errorf("unmarshal cache: %w", err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func Save(c Cache) error {
|
||||
slog.Debug("writing cache", "file", FullPath())
|
||||
|
||||
s, err := Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading cache: %w", err)
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&s, c, mergo.WithOverride); err != nil {
|
||||
return fmt.Errorf("merging cache: %w", err)
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling cache: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(FullPath(), b, 0o600); err != nil {
|
||||
return fmt.Errorf("writing cache file: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
104
internal/cache/cache_test.go
vendored
Normal file
104
internal/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
package cache_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"go.xsfx.dev/glucose_exporter/internal/cache"
|
||||
"go.xsfx.dev/glucose_exporter/internal/config"
|
||||
"go.xsfx.dev/glucose_exporter/internal/epoch"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
is := is.New(t)
|
||||
|
||||
d := t.TempDir()
|
||||
|
||||
config.Cfg.CacheDir = d
|
||||
|
||||
t.Cleanup(func() { config.Cfg.CacheDir = "" })
|
||||
|
||||
err := os.WriteFile(
|
||||
path.Join(d, "cache.json"),
|
||||
[]byte(`
|
||||
{
|
||||
"jwt": "f00b4r",
|
||||
"expires": 1726302083
|
||||
}
|
||||
`),
|
||||
0o600,
|
||||
)
|
||||
is.NoErr(err)
|
||||
|
||||
c, err := cache.Load()
|
||||
is.NoErr(err)
|
||||
|
||||
is.Equal(c.JWT, "f00b4r")
|
||||
is.True(time.Time(c.Expires).Equal(time.Date(2024, 9, 14, 8, 21, 23, 0, time.UTC)))
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
prefill []byte
|
||||
input cache.Cache
|
||||
expected cache.Cache
|
||||
}{
|
||||
{
|
||||
name: "empty init",
|
||||
prefill: []byte(`{}`),
|
||||
input: cache.Cache{JWT: "f00b4r"},
|
||||
expected: cache.Cache{JWT: "f00b4r"},
|
||||
},
|
||||
{
|
||||
name: "adding jwt",
|
||||
prefill: []byte(`{"jwt":"b4rf00"}`),
|
||||
input: cache.Cache{
|
||||
Expires: epoch.Epoch(time.Date(2024, 9, 14, 8, 21, 23, 0, time.UTC)),
|
||||
},
|
||||
expected: cache.Cache{
|
||||
JWT: "b4rf00",
|
||||
Expires: epoch.Epoch(time.Date(2024, 9, 14, 8, 21, 23, 0, time.UTC)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "changing jwt",
|
||||
prefill: []byte(`{"jwt":"b4rf00"}`),
|
||||
input: cache.Cache{
|
||||
JWT: "f00b4r",
|
||||
},
|
||||
expected: cache.Cache{
|
||||
JWT: "f00b4r",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
for _, tt := range tables {
|
||||
tt := tt
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := t.TempDir()
|
||||
|
||||
config.Cfg.CacheDir = d
|
||||
|
||||
t.Cleanup(func() { config.Cfg.CacheDir = "" })
|
||||
|
||||
err := os.WriteFile(path.Join(d, "cache.json"), tt.prefill, 0o600)
|
||||
is.NoErr(err)
|
||||
|
||||
err = cache.Save(tt.input)
|
||||
is.NoErr(err)
|
||||
|
||||
l, err := cache.Load()
|
||||
is.NoErr(err)
|
||||
|
||||
is.Equal(l.JWT, tt.expected.JWT)
|
||||
is.True(time.Time(l.Expires).Equal(time.Time(tt.expected.Expires)))
|
||||
})
|
||||
}
|
||||
}
|
11
internal/config/config.go
Normal file
11
internal/config/config.go
Normal file
@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
var Cfg Config //nolint:gochecknoglobals
|
||||
|
||||
type Config struct {
|
||||
Email string `env:"EMAIL,required"`
|
||||
Password string `env:"PASSWORD,expand"`
|
||||
PasswordFile string `env:"PASSWORD_FILE,expand"`
|
||||
CacheDir string `env:"CACHE_DIR,expand" envDefault:"/var/cache/glucose_exporter"`
|
||||
Debug bool `env:"DEBUG"`
|
||||
}
|
26
internal/epoch/epoch.go
Normal file
26
internal/epoch/epoch.go
Normal file
@ -0,0 +1,26 @@
|
||||
package epoch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Epoch time.Time
|
||||
|
||||
func (e Epoch) MarshalJSON() ([]byte, error) {
|
||||
return []byte(strconv.FormatInt(time.Time(e).Unix(), 10)), nil
|
||||
}
|
||||
|
||||
func (e *Epoch) UnmarshalJSON(b []byte) error {
|
||||
q, err := strconv.ParseInt(string(b), 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse int: %w", err)
|
||||
}
|
||||
|
||||
*(*time.Time)(e) = time.Unix(q, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e Epoch) String() string { return time.Time(e).String() }
|
76
internal/epoch/epoch_test.go
Normal file
76
internal/epoch/epoch_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
package epoch_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/matryer/is"
|
||||
"go.xsfx.dev/glucose_exporter/internal/epoch"
|
||||
)
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
input epoch.Epoch
|
||||
expected []byte
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "00",
|
||||
input: epoch.Epoch(time.Date(2024, 9, 14, 8, 21, 23, 0, time.UTC)),
|
||||
expected: []byte("1726302083"),
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
name: "01",
|
||||
input: epoch.Epoch(time.Time{}),
|
||||
expected: []byte("-62135596800"),
|
||||
err: nil,
|
||||
},
|
||||
}
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
for _, table := range tables {
|
||||
tt := table
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, err := tt.input.MarshalJSON()
|
||||
if tt.err == nil {
|
||||
is.Equal(b, tt.expected)
|
||||
} else {
|
||||
is.True(errors.Is(err, tt.err))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
tables := []struct {
|
||||
name string
|
||||
input []byte
|
||||
expected epoch.Epoch
|
||||
}{
|
||||
{
|
||||
name: "00",
|
||||
input: []byte("1726302083"),
|
||||
expected: epoch.Epoch(time.Date(2024, 9, 14, 8, 21, 23, 0, time.UTC)),
|
||||
},
|
||||
}
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
for _, table := range tables {
|
||||
tt := table
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
e := epoch.Epoch{}
|
||||
|
||||
err := e.UnmarshalJSON(tt.input)
|
||||
is.NoErr(err)
|
||||
|
||||
is.Equal(time.Time(e).UTC(), time.Time(tt.expected).UTC())
|
||||
})
|
||||
}
|
||||
}
|
103
internal/metrics/metrics.go
Normal file
103
internal/metrics/metrics.go
Normal file
@ -0,0 +1,103 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
vm "github.com/VictoriaMetrics/metrics"
|
||||
"go.xsfx.dev/glucose_exporter/api"
|
||||
"go.xsfx.dev/glucose_exporter/internal/cache"
|
||||
"go.xsfx.dev/glucose_exporter/internal/config"
|
||||
)
|
||||
|
||||
var ErrOddDataLength = errors.New("odd data length")
|
||||
|
||||
func Handler(w http.ResponseWriter, r *http.Request) {
|
||||
if err := glucose(r.Context(), w); err != nil {
|
||||
slog.Error("getting glucose", "err", err)
|
||||
vm.GetOrCreateCounter("errors_total").Inc()
|
||||
}
|
||||
|
||||
vm.WritePrometheus(w, false)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func glucose(ctx context.Context, w io.Writer) error {
|
||||
c, err := cache.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading cache: %w", err)
|
||||
}
|
||||
|
||||
// No token exists or token is expired
|
||||
if c.JWT == "" || time.Now().After(time.Time(c.Expires)) {
|
||||
slog.Debug("needs a fresh token")
|
||||
|
||||
token, err := api.Login(
|
||||
ctx,
|
||||
api.BaseURL,
|
||||
config.Cfg.Email,
|
||||
config.Cfg.Password,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("login: %w", err)
|
||||
}
|
||||
|
||||
slog.Debug("got token", "token", token)
|
||||
|
||||
if err := cache.Save(
|
||||
cache.Cache{JWT: token.Data.AuthTicket.Token, Expires: token.Data.AuthTicket.Expires},
|
||||
); err != nil {
|
||||
return fmt.Errorf("saving cache: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Loading cache again with the new token inside.
|
||||
c, err = cache.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading cache: %w", err)
|
||||
}
|
||||
|
||||
// Getting connections, which includes the glucose data.
|
||||
resp, err := api.Connections(ctx, api.BaseURL, c.JWT)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connections: %w", err)
|
||||
}
|
||||
|
||||
// Storing new token to cache.
|
||||
if err := cache.Save(cache.Cache{
|
||||
JWT: resp.Ticket.Token,
|
||||
Expires: resp.Ticket.Expires,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("saving cache: %w", err)
|
||||
}
|
||||
|
||||
if len(resp.Data) != 1 {
|
||||
return ErrOddDataLength
|
||||
}
|
||||
|
||||
labels := fmt.Sprintf(
|
||||
`patient_id="%s",sensor_serialnumber="%s"`,
|
||||
resp.Data[0].PatientID,
|
||||
resp.Data[0].Sensor.Serialnumber,
|
||||
)
|
||||
|
||||
// Writing metrics.
|
||||
vm.WriteGaugeUint64(
|
||||
w,
|
||||
fmt.Sprintf("value_in_mg_per_dl{%s}", labels),
|
||||
uint64(resp.Data[0].GlucoseMeasurement.ValueInMgPerDl),
|
||||
)
|
||||
|
||||
vm.WriteGaugeUint64(
|
||||
w,
|
||||
fmt.Sprintf("trend_arrow{%s}", labels),
|
||||
uint64(resp.Data[0].GlucoseMeasurement.TrendArrow),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
65
main.go
Normal file
65
main.go
Normal file
@ -0,0 +1,65 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
"github.com/lmittmann/tint"
|
||||
|
||||
"go.xsfx.dev/glucose_exporter/httpslog"
|
||||
"go.xsfx.dev/glucose_exporter/internal/cache"
|
||||
"go.xsfx.dev/glucose_exporter/internal/config"
|
||||
"go.xsfx.dev/glucose_exporter/internal/metrics"
|
||||
)
|
||||
|
||||
const addr = ":2112"
|
||||
|
||||
func main() {
|
||||
logOpts := &tint.Options{Level: slog.LevelInfo, TimeFormat: time.Kitchen}
|
||||
initLogging(logOpts)
|
||||
|
||||
if err := env.Parse(&config.Cfg); err != nil {
|
||||
slog.Error("parsing env config", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if config.Cfg.Debug {
|
||||
initLogging(&tint.Options{Level: slog.LevelDebug, TimeFormat: time.Kitchen})
|
||||
}
|
||||
|
||||
cacheFileLogger := slog.With("file", cache.FullPath())
|
||||
|
||||
// Check if cache files needs to be created.
|
||||
_, err := os.Stat(cache.FullPath())
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
if err := os.WriteFile(cache.FullPath(), []byte("{}"), 0o600); err != nil {
|
||||
cacheFileLogger.Error("init cache file", "err", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
cacheFileLogger.Debug("init cache file")
|
||||
}
|
||||
} else {
|
||||
cacheFileLogger.Error("getting cache file stat", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/metrics", metrics.Handler)
|
||||
|
||||
slog.Info("listening", "addr", addr)
|
||||
|
||||
if err := http.ListenAndServe(addr, httpslog.Handler()(mux)); err != nil {
|
||||
slog.Error("listen and serve", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initLogging(opts *tint.Options) {
|
||||
slog.SetDefault(slog.New(tint.NewHandler(os.Stderr, opts)))
|
||||
}
|
12
vendor/dario.cat/mergo/.deepsource.toml
vendored
Normal file
12
vendor/dario.cat/mergo/.deepsource.toml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
version = 1
|
||||
|
||||
test_patterns = [
|
||||
"*_test.go"
|
||||
]
|
||||
|
||||
[[analyzers]]
|
||||
name = "go"
|
||||
enabled = true
|
||||
|
||||
[analyzers.meta]
|
||||
import_path = "dario.cat/mergo"
|
33
vendor/dario.cat/mergo/.gitignore
vendored
Normal file
33
vendor/dario.cat/mergo/.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
#### joe made this: http://goel.io/joe
|
||||
|
||||
#### go ####
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
#### vim ####
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
12
vendor/dario.cat/mergo/.travis.yml
vendored
Normal file
12
vendor/dario.cat/mergo/.travis.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
language: go
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
install:
|
||||
- go get -t
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/mattn/goveralls
|
||||
script:
|
||||
- go test -race -v ./...
|
||||
after_script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN
|
46
vendor/dario.cat/mergo/CODE_OF_CONDUCT.md
vendored
Normal file
46
vendor/dario.cat/mergo/CODE_OF_CONDUCT.md
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at i@dario.im. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
112
vendor/dario.cat/mergo/CONTRIBUTING.md
vendored
Normal file
112
vendor/dario.cat/mergo/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
<!-- omit in toc -->
|
||||
# Contributing to mergo
|
||||
|
||||
First off, thanks for taking the time to contribute! ❤️
|
||||
|
||||
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
|
||||
|
||||
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
||||
> - Star the project
|
||||
> - Tweet about it
|
||||
> - Refer this project in your project's readme
|
||||
> - Mention the project at local meetups and tell your friends/colleagues
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Table of Contents
|
||||
|
||||
- [Code of Conduct](#code-of-conduct)
|
||||
- [I Have a Question](#i-have-a-question)
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and everyone participating in it is governed by the
|
||||
[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
||||
to <>.
|
||||
|
||||
|
||||
## I Have a Question
|
||||
|
||||
> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo).
|
||||
|
||||
Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
||||
|
||||
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
||||
|
||||
- Open an [Issue](https://github.com/imdario/mergo/issues/new).
|
||||
- Provide as much context as you can about what you're running into.
|
||||
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
|
||||
|
||||
We will then take care of the issue as soon as possible.
|
||||
|
||||
## I Want To Contribute
|
||||
|
||||
> ### Legal Notice <!-- omit in toc -->
|
||||
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting a Bug Report
|
||||
|
||||
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
||||
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug).
|
||||
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Possibly your input and the output
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Bug Report?
|
||||
|
||||
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
|
||||
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
|
||||
|
||||
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
||||
|
||||
- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
||||
- Explain the behavior you would expect and the actual behavior.
|
||||
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
||||
- Provide the information you collected in the previous section.
|
||||
|
||||
Once it's filed:
|
||||
|
||||
- The project team will label the issue accordingly.
|
||||
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
||||
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### Before Submitting an Enhancement
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
<!-- omit in toc -->
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues).
|
||||
|
||||
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
||||
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
||||
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
||||
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
|
||||
- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||
|
||||
<!-- omit in toc -->
|
||||
## Attribution
|
||||
This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!
|
28
vendor/dario.cat/mergo/LICENSE
vendored
Normal file
28
vendor/dario.cat/mergo/LICENSE
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
Copyright (c) 2013 Dario Castañé. All rights reserved.
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
248
vendor/dario.cat/mergo/README.md
vendored
Normal file
248
vendor/dario.cat/mergo/README.md
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
# Mergo
|
||||
|
||||
[![GitHub release][5]][6]
|
||||
[![GoCard][7]][8]
|
||||
[![Test status][1]][2]
|
||||
[![OpenSSF Scorecard][21]][22]
|
||||
[![OpenSSF Best Practices][19]][20]
|
||||
[![Coverage status][9]][10]
|
||||
[![Sourcegraph][11]][12]
|
||||
[![FOSSA status][13]][14]
|
||||
|
||||
[![GoDoc][3]][4]
|
||||
[![Become my sponsor][15]][16]
|
||||
[![Tidelift][17]][18]
|
||||
|
||||
[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master
|
||||
[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml
|
||||
[3]: https://godoc.org/github.com/imdario/mergo?status.svg
|
||||
[4]: https://godoc.org/github.com/imdario/mergo
|
||||
[5]: https://img.shields.io/github/release/imdario/mergo.svg
|
||||
[6]: https://github.com/imdario/mergo/releases
|
||||
[7]: https://goreportcard.com/badge/imdario/mergo
|
||||
[8]: https://goreportcard.com/report/github.com/imdario/mergo
|
||||
[9]: https://coveralls.io/repos/github/imdario/mergo/badge.svg?branch=master
|
||||
[10]: https://coveralls.io/github/imdario/mergo?branch=master
|
||||
[11]: https://sourcegraph.com/github.com/imdario/mergo/-/badge.svg
|
||||
[12]: https://sourcegraph.com/github.com/imdario/mergo?badge
|
||||
[13]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=shield
|
||||
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
|
||||
[15]: https://img.shields.io/github/sponsors/imdario
|
||||
[16]: https://github.com/sponsors/imdario
|
||||
[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo
|
||||
[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo
|
||||
[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge
|
||||
[20]: https://bestpractices.coreinfrastructure.org/projects/7177
|
||||
[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge
|
||||
[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo
|
||||
|
||||
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||
|
||||
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||
|
||||
Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the Province of Ancona in the Italian region of Marche.
|
||||
|
||||
## Status
|
||||
|
||||
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
|
||||
|
||||
### Important notes
|
||||
|
||||
#### 1.0.0
|
||||
|
||||
In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`.
|
||||
|
||||
#### 0.3.9
|
||||
|
||||
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules.
|
||||
|
||||
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
|
||||
|
||||
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||
|
||||
### Donations
|
||||
|
||||
If Mergo is useful to you, consider buying me a coffee, a beer, or making a monthly donation to allow me to keep building great free software. :heart_eyes:
|
||||
|
||||
<a href='https://ko-fi.com/B0B58839' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://az743702.vo.msecnd.net/cdn/kofi1.png?v=0' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
<a href="https://liberapay.com/dario/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a>
|
||||
<a href='https://github.com/sponsors/imdario' target='_blank'><img alt="Become my sponsor" src="https://img.shields.io/github/sponsors/imdario?style=for-the-badge" /></a>
|
||||
|
||||
### Mergo in the wild
|
||||
|
||||
- [moby/moby](https://github.com/moby/moby)
|
||||
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
|
||||
- [vmware/dispatch](https://github.com/vmware/dispatch)
|
||||
- [Shopify/themekit](https://github.com/Shopify/themekit)
|
||||
- [imdario/zas](https://github.com/imdario/zas)
|
||||
- [matcornic/hermes](https://github.com/matcornic/hermes)
|
||||
- [OpenBazaar/openbazaar-go](https://github.com/OpenBazaar/openbazaar-go)
|
||||
- [kataras/iris](https://github.com/kataras/iris)
|
||||
- [michaelsauter/crane](https://github.com/michaelsauter/crane)
|
||||
- [go-task/task](https://github.com/go-task/task)
|
||||
- [sensu/uchiwa](https://github.com/sensu/uchiwa)
|
||||
- [ory/hydra](https://github.com/ory/hydra)
|
||||
- [sisatech/vcli](https://github.com/sisatech/vcli)
|
||||
- [dairycart/dairycart](https://github.com/dairycart/dairycart)
|
||||
- [projectcalico/felix](https://github.com/projectcalico/felix)
|
||||
- [resin-os/balena](https://github.com/resin-os/balena)
|
||||
- [go-kivik/kivik](https://github.com/go-kivik/kivik)
|
||||
- [Telefonica/govice](https://github.com/Telefonica/govice)
|
||||
- [supergiant/supergiant](supergiant/supergiant)
|
||||
- [SergeyTsalkov/brooce](https://github.com/SergeyTsalkov/brooce)
|
||||
- [soniah/dnsmadeeasy](https://github.com/soniah/dnsmadeeasy)
|
||||
- [ohsu-comp-bio/funnel](https://github.com/ohsu-comp-bio/funnel)
|
||||
- [EagerIO/Stout](https://github.com/EagerIO/Stout)
|
||||
- [lynndylanhurley/defsynth-api](https://github.com/lynndylanhurley/defsynth-api)
|
||||
- [russross/canvasassignments](https://github.com/russross/canvasassignments)
|
||||
- [rdegges/cryptly-api](https://github.com/rdegges/cryptly-api)
|
||||
- [casualjim/exeggutor](https://github.com/casualjim/exeggutor)
|
||||
- [divshot/gitling](https://github.com/divshot/gitling)
|
||||
- [RWJMurphy/gorl](https://github.com/RWJMurphy/gorl)
|
||||
- [andrerocker/deploy42](https://github.com/andrerocker/deploy42)
|
||||
- [elwinar/rambler](https://github.com/elwinar/rambler)
|
||||
- [tmaiaroto/gopartman](https://github.com/tmaiaroto/gopartman)
|
||||
- [jfbus/impressionist](https://github.com/jfbus/impressionist)
|
||||
- [Jmeyering/zealot](https://github.com/Jmeyering/zealot)
|
||||
- [godep-migrator/rigger-host](https://github.com/godep-migrator/rigger-host)
|
||||
- [Dronevery/MultiwaySwitch-Go](https://github.com/Dronevery/MultiwaySwitch-Go)
|
||||
- [thoas/picfit](https://github.com/thoas/picfit)
|
||||
- [mantasmatelis/whooplist-server](https://github.com/mantasmatelis/whooplist-server)
|
||||
- [jnuthong/item_search](https://github.com/jnuthong/item_search)
|
||||
- [bukalapak/snowboard](https://github.com/bukalapak/snowboard)
|
||||
- [containerssh/containerssh](https://github.com/containerssh/containerssh)
|
||||
- [goreleaser/goreleaser](https://github.com/goreleaser/goreleaser)
|
||||
- [tjpnz/structbot](https://github.com/tjpnz/structbot)
|
||||
|
||||
## Install
|
||||
|
||||
go get dario.cat/mergo
|
||||
|
||||
// use in your .go code
|
||||
import (
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
## Usage
|
||||
|
||||
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as [they are zero values](https://golang.org/ref/spec#The_zero_value) too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||
|
||||
```go
|
||||
if err := mergo.Merge(&dst, src); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Also, you can merge overwriting values using the transformer `WithOverride`.
|
||||
|
||||
```go
|
||||
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Additionally, you can map a `map[string]interface{}` to a struct (and otherwise, from struct to map), following the same restrictions as in `Merge()`. Keys are capitalized to find each corresponding exported field.
|
||||
|
||||
```go
|
||||
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as `map[string]interface{}`. They will be just assigned as values.
|
||||
|
||||
Here is a nice example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
type Foo struct {
|
||||
A string
|
||||
B int64
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Foo{
|
||||
A: "one",
|
||||
B: 2,
|
||||
}
|
||||
dest := Foo{
|
||||
A: "two",
|
||||
}
|
||||
mergo.Merge(&dest, src)
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// {two 2}
|
||||
}
|
||||
```
|
||||
|
||||
Note: if test are failing due missing package, please execute:
|
||||
|
||||
go get gopkg.in/yaml.v3
|
||||
|
||||
### Transformers
|
||||
|
||||
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, `time.Time` is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero `time.Time`?
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"dario.cat/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timeTransformer struct {
|
||||
}
|
||||
|
||||
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||
if typ == reflect.TypeOf(time.Time{}) {
|
||||
return func(dst, src reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
isZero := dst.MethodByName("IsZero")
|
||||
result := isZero.Call([]reflect.Value{})
|
||||
if result[0].Bool() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
Time time.Time
|
||||
// ...
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Snapshot{time.Now()}
|
||||
dest := Snapshot{}
|
||||
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||
}
|
||||
```
|
||||
|
||||
## Contact me
|
||||
|
||||
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): [@im_dario](https://twitter.com/im_dario)
|
||||
|
||||
## About
|
||||
|
||||
Written by [Dario Castañé](http://dario.im).
|
||||
|
||||
## License
|
||||
|
||||
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
|
||||
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)
|
14
vendor/dario.cat/mergo/SECURITY.md
vendored
Normal file
14
vendor/dario.cat/mergo/SECURITY.md
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.3.x | :white_check_mark: |
|
||||
| < 0.3 | :x: |
|
||||
|
||||
## Security contact information
|
||||
|
||||
To report a security vulnerability, please use the
|
||||
[Tidelift security contact](https://tidelift.com/security).
|
||||
Tidelift will coordinate the fix and disclosure.
|
148
vendor/dario.cat/mergo/doc.go
vendored
Normal file
148
vendor/dario.cat/mergo/doc.go
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
|
||||
|
||||
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
|
||||
|
||||
# Status
|
||||
|
||||
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
|
||||
|
||||
# Important notes
|
||||
|
||||
1.0.0
|
||||
|
||||
In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`.
|
||||
|
||||
0.3.9
|
||||
|
||||
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
|
||||
|
||||
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
|
||||
|
||||
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
|
||||
|
||||
# Install
|
||||
|
||||
Do your usual installation procedure:
|
||||
|
||||
go get dario.cat/mergo
|
||||
|
||||
// use in your .go code
|
||||
import (
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
# Usage
|
||||
|
||||
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
|
||||
|
||||
if err := mergo.Merge(&dst, src); err != nil {
|
||||
// ...
|
||||
}
|
||||
|
||||
Also, you can merge overwriting values using the transformer WithOverride.
|
||||
|
||||
if err := mergo.Merge(&dst, src, mergo.WithOverride); err != nil {
|
||||
// ...
|
||||
}
|
||||
|
||||
Additionally, you can map a map[string]interface{} to a struct (and otherwise, from struct to map), following the same restrictions as in Merge(). Keys are capitalized to find each corresponding exported field.
|
||||
|
||||
if err := mergo.Map(&dst, srcMap); err != nil {
|
||||
// ...
|
||||
}
|
||||
|
||||
Warning: if you map a struct to map, it won't do it recursively. Don't expect Mergo to map struct members of your struct as map[string]interface{}. They will be just assigned as values.
|
||||
|
||||
Here is a nice example:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
type Foo struct {
|
||||
A string
|
||||
B int64
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Foo{
|
||||
A: "one",
|
||||
B: 2,
|
||||
}
|
||||
dest := Foo{
|
||||
A: "two",
|
||||
}
|
||||
mergo.Merge(&dest, src)
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// {two 2}
|
||||
}
|
||||
|
||||
# Transformers
|
||||
|
||||
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"dario.cat/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timeTransformer struct {
|
||||
}
|
||||
|
||||
func (t timeTransformer) Transformer(typ reflect.Type) func(dst, src reflect.Value) error {
|
||||
if typ == reflect.TypeOf(time.Time{}) {
|
||||
return func(dst, src reflect.Value) error {
|
||||
if dst.CanSet() {
|
||||
isZero := dst.MethodByName("IsZero")
|
||||
result := isZero.Call([]reflect.Value{})
|
||||
if result[0].Bool() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
Time time.Time
|
||||
// ...
|
||||
}
|
||||
|
||||
func main() {
|
||||
src := Snapshot{time.Now()}
|
||||
dest := Snapshot{}
|
||||
mergo.Merge(&dest, src, mergo.WithTransformers(timeTransformer{}))
|
||||
fmt.Println(dest)
|
||||
// Will print
|
||||
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||
}
|
||||
|
||||
# Contact me
|
||||
|
||||
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
|
||||
|
||||
# About
|
||||
|
||||
Written by Dario Castañé: https://da.rio.hn
|
||||
|
||||
# License
|
||||
|
||||
BSD 3-Clause license, as Go language.
|
||||
*/
|
||||
package mergo
|
178
vendor/dario.cat/mergo/map.go
vendored
Normal file
178
vendor/dario.cat/mergo/map.go
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright 2014 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func changeInitialCase(s string, mapper func(rune) rune) string {
|
||||
if s == "" {
|
||||
return s
|
||||
}
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
return string(mapper(r)) + s[n:]
|
||||
}
|
||||
|
||||
func isExported(field reflect.StructField) bool {
|
||||
r, _ := utf8.DecodeRuneInString(field.Name)
|
||||
return r >= 'A' && r <= 'Z'
|
||||
}
|
||||
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||
overwrite := config.Overwrite
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{typ, seen, addr}
|
||||
}
|
||||
zeroValue := reflect.Value{}
|
||||
switch dst.Kind() {
|
||||
case reflect.Map:
|
||||
dstMap := dst.Interface().(map[string]interface{})
|
||||
for i, n := 0, src.NumField(); i < n; i++ {
|
||||
srcType := src.Type()
|
||||
field := srcType.Field(i)
|
||||
if !isExported(field) {
|
||||
continue
|
||||
}
|
||||
fieldName := field.Name
|
||||
fieldName = changeInitialCase(fieldName, unicode.ToLower)
|
||||
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) {
|
||||
dstMap[fieldName] = src.Field(i).Interface()
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if dst.IsNil() {
|
||||
v := reflect.New(dst.Type().Elem())
|
||||
dst.Set(v)
|
||||
}
|
||||
dst = dst.Elem()
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
srcMap := src.Interface().(map[string]interface{})
|
||||
for key := range srcMap {
|
||||
config.overwriteWithEmptyValue = true
|
||||
srcValue := srcMap[key]
|
||||
fieldName := changeInitialCase(key, unicode.ToUpper)
|
||||
dstElement := dst.FieldByName(fieldName)
|
||||
if dstElement == zeroValue {
|
||||
// We discard it because the field doesn't exist.
|
||||
continue
|
||||
}
|
||||
srcElement := reflect.ValueOf(srcValue)
|
||||
dstKind := dstElement.Kind()
|
||||
srcKind := srcElement.Kind()
|
||||
if srcKind == reflect.Ptr && dstKind != reflect.Ptr {
|
||||
srcElement = srcElement.Elem()
|
||||
srcKind = reflect.TypeOf(srcElement.Interface()).Kind()
|
||||
} else if dstKind == reflect.Ptr {
|
||||
// Can this work? I guess it can't.
|
||||
if srcKind != reflect.Ptr && srcElement.CanAddr() {
|
||||
srcPtr := srcElement.Addr()
|
||||
srcElement = reflect.ValueOf(srcPtr)
|
||||
srcKind = reflect.Ptr
|
||||
}
|
||||
}
|
||||
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
if srcKind == dstKind {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if dstKind == reflect.Interface && dstElement.Kind() == reflect.Interface {
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else if srcKind == reflect.Map {
|
||||
if err = deepMap(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("type mismatch on %s field: found %v, expected %v", fieldName, srcKind, dstKind)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Map sets fields' values in dst from src.
|
||||
// src can be a map with string keys or a struct. dst must be the opposite:
|
||||
// if src is a map, dst must be a valid pointer to struct. If src is a struct,
|
||||
// dst must be map[string]interface{}.
|
||||
// It won't merge unexported (private) fields and will do recursively
|
||||
// any exported field.
|
||||
// If dst is a map, keys will be src fields' names in lower camel case.
|
||||
// Missing key in src that doesn't match a field in dst will be skipped. This
|
||||
// doesn't apply if dst is a map.
|
||||
// This is separated method from Merge because it is cleaner and it keeps sane
|
||||
// semantics: merging equal types, mapping different (restricted) types.
|
||||
func Map(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return _map(dst, src, opts...)
|
||||
}
|
||||
|
||||
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
|
||||
// non-empty src attribute values.
|
||||
// Deprecated: Use Map(…) with WithOverride
|
||||
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return _map(dst, src, append(opts, WithOverride)...)
|
||||
}
|
||||
|
||||
func _map(dst, src interface{}, opts ...func(*Config)) error {
|
||||
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||
return ErrNonPointerArgument
|
||||
}
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
config := &Config{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
// To be friction-less, we redirect equal-type arguments
|
||||
// to deepMerge. Only because arguments can be anything.
|
||||
if vSrc.Kind() == vDst.Kind() {
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
switch vSrc.Kind() {
|
||||
case reflect.Struct:
|
||||
if vDst.Kind() != reflect.Map {
|
||||
return ErrExpectedMapAsDestination
|
||||
}
|
||||
case reflect.Map:
|
||||
if vDst.Kind() != reflect.Struct {
|
||||
return ErrExpectedStructAsDestination
|
||||
}
|
||||
default:
|
||||
return ErrNotSupported
|
||||
}
|
||||
return deepMap(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
409
vendor/dario.cat/mergo/merge.go
vendored
Normal file
409
vendor/dario.cat/mergo/merge.go
vendored
Normal file
@ -0,0 +1,409 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func hasMergeableFields(dst reflect.Value) (exported bool) {
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
field := dst.Type().Field(i)
|
||||
if field.Anonymous && dst.Field(i).Kind() == reflect.Struct {
|
||||
exported = exported || hasMergeableFields(dst.Field(i))
|
||||
} else if isExportedComponent(&field) {
|
||||
exported = exported || len(field.PkgPath) == 0
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isExportedComponent(field *reflect.StructField) bool {
|
||||
pkgPath := field.PkgPath
|
||||
if len(pkgPath) > 0 {
|
||||
return false
|
||||
}
|
||||
c := field.Name[0]
|
||||
if 'a' <= c && c <= 'z' || c == '_' {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Transformers Transformers
|
||||
Overwrite bool
|
||||
ShouldNotDereference bool
|
||||
AppendSlice bool
|
||||
TypeCheck bool
|
||||
overwriteWithEmptyValue bool
|
||||
overwriteSliceWithEmptyValue bool
|
||||
sliceDeepCopy bool
|
||||
debug bool
|
||||
}
|
||||
|
||||
type Transformers interface {
|
||||
Transformer(reflect.Type) func(dst, src reflect.Value) error
|
||||
}
|
||||
|
||||
// Traverses recursively both values, assigning src's fields values to dst.
|
||||
// The map argument tracks comparisons that have already been seen, which allows
|
||||
// short circuiting on recursive types.
|
||||
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
|
||||
overwrite := config.Overwrite
|
||||
typeCheck := config.TypeCheck
|
||||
overwriteWithEmptySrc := config.overwriteWithEmptyValue
|
||||
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
|
||||
sliceDeepCopy := config.sliceDeepCopy
|
||||
|
||||
if !src.IsValid() {
|
||||
return
|
||||
}
|
||||
if dst.CanAddr() {
|
||||
addr := dst.UnsafeAddr()
|
||||
h := 17 * addr
|
||||
seen := visited[h]
|
||||
typ := dst.Type()
|
||||
for p := seen; p != nil; p = p.next {
|
||||
if p.ptr == addr && p.typ == typ {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{typ, seen, addr}
|
||||
}
|
||||
|
||||
if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() {
|
||||
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
|
||||
err = fn(dst, src)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch dst.Kind() {
|
||||
case reflect.Struct:
|
||||
if hasMergeableFields(dst) {
|
||||
for i, n := 0, dst.NumField(); i < n; i++ {
|
||||
if err = deepMerge(dst.Field(i), src.Field(i), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
if dst.IsNil() && !src.IsNil() {
|
||||
if dst.CanSet() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
} else {
|
||||
dst = src
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if src.Kind() != reflect.Map {
|
||||
if overwrite && dst.CanSet() {
|
||||
dst.Set(src)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range src.MapKeys() {
|
||||
srcElement := src.MapIndex(key)
|
||||
if !srcElement.IsValid() {
|
||||
continue
|
||||
}
|
||||
dstElement := dst.MapIndex(key)
|
||||
switch srcElement.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
|
||||
if srcElement.IsNil() {
|
||||
if overwrite {
|
||||
dst.SetMapIndex(key, srcElement)
|
||||
}
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
if !srcElement.CanInterface() {
|
||||
continue
|
||||
}
|
||||
switch reflect.TypeOf(srcElement.Interface()).Kind() {
|
||||
case reflect.Struct:
|
||||
fallthrough
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Map:
|
||||
srcMapElm := srcElement
|
||||
dstMapElm := dstElement
|
||||
if srcMapElm.CanInterface() {
|
||||
srcMapElm = reflect.ValueOf(srcMapElm.Interface())
|
||||
if dstMapElm.IsValid() {
|
||||
dstMapElm = reflect.ValueOf(dstMapElm.Interface())
|
||||
}
|
||||
}
|
||||
if err = deepMerge(dstMapElm, srcMapElm, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
case reflect.Slice:
|
||||
srcSlice := reflect.ValueOf(srcElement.Interface())
|
||||
|
||||
var dstSlice reflect.Value
|
||||
if !dstElement.IsValid() || dstElement.IsNil() {
|
||||
dstSlice = reflect.MakeSlice(srcSlice.Type(), 0, srcSlice.Len())
|
||||
} else {
|
||||
dstSlice = reflect.ValueOf(dstElement.Interface())
|
||||
}
|
||||
|
||||
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
||||
if typeCheck && srcSlice.Type() != dstSlice.Type() {
|
||||
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||
}
|
||||
dstSlice = srcSlice
|
||||
} else if config.AppendSlice {
|
||||
if srcSlice.Type() != dstSlice.Type() {
|
||||
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
|
||||
}
|
||||
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
|
||||
} else if sliceDeepCopy {
|
||||
i := 0
|
||||
for ; i < srcSlice.Len() && i < dstSlice.Len(); i++ {
|
||||
srcElement := srcSlice.Index(i)
|
||||
dstElement := dstSlice.Index(i)
|
||||
|
||||
if srcElement.CanInterface() {
|
||||
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||
}
|
||||
if dstElement.CanInterface() {
|
||||
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||
}
|
||||
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dst.SetMapIndex(key, dstSlice)
|
||||
}
|
||||
}
|
||||
|
||||
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
|
||||
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
|
||||
continue
|
||||
}
|
||||
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
dst.SetMapIndex(key, srcElement)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that all keys in dst are deleted if they are not in src.
|
||||
if overwriteWithEmptySrc {
|
||||
for _, key := range dst.MapKeys() {
|
||||
srcElement := src.MapIndex(key)
|
||||
if !srcElement.IsValid() {
|
||||
dst.SetMapIndex(key, reflect.Value{})
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
if !dst.CanSet() {
|
||||
break
|
||||
}
|
||||
if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
|
||||
dst.Set(src)
|
||||
} else if config.AppendSlice {
|
||||
if src.Type() != dst.Type() {
|
||||
return fmt.Errorf("cannot append two slice with different type (%s, %s)", src.Type(), dst.Type())
|
||||
}
|
||||
dst.Set(reflect.AppendSlice(dst, src))
|
||||
} else if sliceDeepCopy {
|
||||
for i := 0; i < src.Len() && i < dst.Len(); i++ {
|
||||
srcElement := src.Index(i)
|
||||
dstElement := dst.Index(i)
|
||||
if srcElement.CanInterface() {
|
||||
srcElement = reflect.ValueOf(srcElement.Interface())
|
||||
}
|
||||
if dstElement.CanInterface() {
|
||||
dstElement = reflect.ValueOf(dstElement.Interface())
|
||||
}
|
||||
|
||||
if err = deepMerge(dstElement, srcElement, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Ptr:
|
||||
fallthrough
|
||||
case reflect.Interface:
|
||||
if isReflectNil(src) {
|
||||
if overwriteWithEmptySrc && dst.CanSet() && src.Type().AssignableTo(dst.Type()) {
|
||||
dst.Set(src)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if src.Kind() != reflect.Interface {
|
||||
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else if src.Kind() == reflect.Ptr {
|
||||
if !config.ShouldNotDereference {
|
||||
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
} else if dst.Elem().Type() == src.Type() {
|
||||
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if dst.IsNil() || overwrite {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if dst.Elem().Kind() == src.Elem().Kind() {
|
||||
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
|
||||
if mustSet {
|
||||
if dst.CanSet() {
|
||||
dst.Set(src)
|
||||
} else {
|
||||
dst = src
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Merge will fill any empty for value type attributes on the dst struct using corresponding
|
||||
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
|
||||
// and dst must be a pointer to struct.
|
||||
// It won't merge unexported (private) fields and will do recursively any exported field.
|
||||
func Merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return merge(dst, src, opts...)
|
||||
}
|
||||
|
||||
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
|
||||
// non-empty src attribute values.
|
||||
// Deprecated: use Merge(…) with WithOverride
|
||||
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
|
||||
return merge(dst, src, append(opts, WithOverride)...)
|
||||
}
|
||||
|
||||
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
|
||||
func WithTransformers(transformers Transformers) func(*Config) {
|
||||
return func(config *Config) {
|
||||
config.Transformers = transformers
|
||||
}
|
||||
}
|
||||
|
||||
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
|
||||
func WithOverride(config *Config) {
|
||||
config.Overwrite = true
|
||||
}
|
||||
|
||||
// WithOverwriteWithEmptyValue will make merge override non empty dst attributes with empty src attributes values.
|
||||
func WithOverwriteWithEmptyValue(config *Config) {
|
||||
config.Overwrite = true
|
||||
config.overwriteWithEmptyValue = true
|
||||
}
|
||||
|
||||
// WithOverrideEmptySlice will make merge override empty dst slice with empty src slice.
|
||||
func WithOverrideEmptySlice(config *Config) {
|
||||
config.overwriteSliceWithEmptyValue = true
|
||||
}
|
||||
|
||||
// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty
|
||||
// (i.e. a non-nil pointer is never considered empty).
|
||||
func WithoutDereference(config *Config) {
|
||||
config.ShouldNotDereference = true
|
||||
}
|
||||
|
||||
// WithAppendSlice will make merge append slices instead of overwriting it.
|
||||
func WithAppendSlice(config *Config) {
|
||||
config.AppendSlice = true
|
||||
}
|
||||
|
||||
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
|
||||
func WithTypeCheck(config *Config) {
|
||||
config.TypeCheck = true
|
||||
}
|
||||
|
||||
// WithSliceDeepCopy will merge slice element one by one with Overwrite flag.
|
||||
func WithSliceDeepCopy(config *Config) {
|
||||
config.sliceDeepCopy = true
|
||||
config.Overwrite = true
|
||||
}
|
||||
|
||||
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||
return ErrNonPointerArgument
|
||||
}
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
||||
err error
|
||||
)
|
||||
|
||||
config := &Config{}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
if vDst, vSrc, err = resolveValues(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
if vDst.Type() != vSrc.Type() {
|
||||
return ErrDifferentArgumentsTypes
|
||||
}
|
||||
return deepMerge(vDst, vSrc, make(map[uintptr]*visit), 0, config)
|
||||
}
|
||||
|
||||
// IsReflectNil is the reflect value provided nil
|
||||
func isReflectNil(v reflect.Value) bool {
|
||||
k := v.Kind()
|
||||
switch k {
|
||||
case reflect.Interface, reflect.Slice, reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr:
|
||||
// Both interface and slice are nil if first word is 0.
|
||||
// Both are always bigger than a word; assume flagIndir.
|
||||
return v.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
81
vendor/dario.cat/mergo/mergo.go
vendored
Normal file
81
vendor/dario.cat/mergo/mergo.go
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright 2013 Dario Castañé. All rights reserved.
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Based on src/pkg/reflect/deepequal.go from official
|
||||
// golang's stdlib.
|
||||
|
||||
package mergo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Errors reported by Mergo when it finds invalid arguments.
|
||||
var (
|
||||
ErrNilArguments = errors.New("src and dst must not be nil")
|
||||
ErrDifferentArgumentsTypes = errors.New("src and dst must be of same type")
|
||||
ErrNotSupported = errors.New("only structs, maps, and slices are supported")
|
||||
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
|
||||
ErrNonPointerArgument = errors.New("dst must be a pointer")
|
||||
)
|
||||
|
||||
// During deepMerge, must keep track of checks that are
|
||||
// in progress. The comparison algorithm assumes that all
|
||||
// checks in progress are true when it reencounters them.
|
||||
// Visited are stored in a map indexed by 17 * a1 + a2;
|
||||
type visit struct {
|
||||
typ reflect.Type
|
||||
next *visit
|
||||
ptr uintptr
|
||||
}
|
||||
|
||||
// From src/pkg/encoding/json/encode.go.
|
||||
func isEmptyValue(v reflect.Value, shouldDereference bool) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
if shouldDereference {
|
||||
return isEmptyValue(v.Elem(), shouldDereference)
|
||||
}
|
||||
return false
|
||||
case reflect.Func:
|
||||
return v.IsNil()
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func resolveValues(dst, src interface{}) (vDst, vSrc reflect.Value, err error) {
|
||||
if dst == nil || src == nil {
|
||||
err = ErrNilArguments
|
||||
return
|
||||
}
|
||||
vDst = reflect.ValueOf(dst).Elem()
|
||||
if vDst.Kind() != reflect.Struct && vDst.Kind() != reflect.Map && vDst.Kind() != reflect.Slice {
|
||||
err = ErrNotSupported
|
||||
return
|
||||
}
|
||||
vSrc = reflect.ValueOf(src)
|
||||
// We check if vSrc is a pointer to dereference it.
|
||||
if vSrc.Kind() == reflect.Ptr {
|
||||
vSrc = vSrc.Elem()
|
||||
}
|
||||
return
|
||||
}
|
22
vendor/github.com/VictoriaMetrics/metrics/LICENSE
generated
vendored
Normal file
22
vendor/github.com/VictoriaMetrics/metrics/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 VictoriaMetrics
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
114
vendor/github.com/VictoriaMetrics/metrics/README.md
generated
vendored
Normal file
114
vendor/github.com/VictoriaMetrics/metrics/README.md
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
[![Build Status](https://github.com/VictoriaMetrics/metrics/workflows/main/badge.svg)](https://github.com/VictoriaMetrics/metrics/actions)
|
||||
[![GoDoc](https://godoc.org/github.com/VictoriaMetrics/metrics?status.svg)](http://godoc.org/github.com/VictoriaMetrics/metrics)
|
||||
[![Go Report](https://goreportcard.com/badge/github.com/VictoriaMetrics/metrics)](https://goreportcard.com/report/github.com/VictoriaMetrics/metrics)
|
||||
[![codecov](https://codecov.io/gh/VictoriaMetrics/metrics/branch/master/graph/badge.svg)](https://codecov.io/gh/VictoriaMetrics/metrics)
|
||||
|
||||
|
||||
# metrics - lightweight package for exporting metrics in Prometheus format
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Lightweight. Has minimal number of third-party dependencies and all these deps are small.
|
||||
See [this article](https://medium.com/@valyala/stripping-dependency-bloat-in-victoriametrics-docker-image-983fb5912b0d) for details.
|
||||
* Easy to use. See the [API docs](http://godoc.org/github.com/VictoriaMetrics/metrics).
|
||||
* Fast.
|
||||
* Allows exporting distinct metric sets via distinct endpoints. See [Set](http://godoc.org/github.com/VictoriaMetrics/metrics#Set).
|
||||
* Supports [easy-to-use histograms](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram), which just work without any tuning.
|
||||
Read more about VictoriaMetrics histograms at [this article](https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350).
|
||||
* Can push metrics to VictoriaMetrics or to any other remote storage, which accepts metrics
|
||||
in [Prometheus text exposition format](https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format).
|
||||
See [these docs](http://godoc.org/github.com/VictoriaMetrics/metrics#InitPush).
|
||||
|
||||
|
||||
### Limitations
|
||||
|
||||
* It doesn't implement advanced functionality from [github.com/prometheus/client_golang](https://godoc.org/github.com/prometheus/client_golang).
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
```go
|
||||
import "github.com/VictoriaMetrics/metrics"
|
||||
|
||||
// Register various metrics.
|
||||
// Metric name may contain labels in Prometheus format - see below.
|
||||
var (
|
||||
// Register counter without labels.
|
||||
requestsTotal = metrics.NewCounter("requests_total")
|
||||
|
||||
// Register summary with a single label.
|
||||
requestDuration = metrics.NewSummary(`requests_duration_seconds{path="/foobar/baz"}`)
|
||||
|
||||
// Register gauge with two labels.
|
||||
queueSize = metrics.NewGauge(`queue_size{queue="foobar",topic="baz"}`, func() float64 {
|
||||
return float64(foobarQueue.Len())
|
||||
})
|
||||
|
||||
// Register histogram with a single label.
|
||||
responseSize = metrics.NewHistogram(`response_size{path="/foo/bar"}`)
|
||||
)
|
||||
|
||||
// ...
|
||||
func requestHandler() {
|
||||
// Increment requestTotal counter.
|
||||
requestsTotal.Inc()
|
||||
|
||||
startTime := time.Now()
|
||||
processRequest()
|
||||
// Update requestDuration summary.
|
||||
requestDuration.UpdateDuration(startTime)
|
||||
|
||||
// Update responseSize histogram.
|
||||
responseSize.Update(responseSize)
|
||||
}
|
||||
|
||||
// Expose the registered metrics at `/metrics` path.
|
||||
http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
|
||||
metrics.WritePrometheus(w, true)
|
||||
})
|
||||
|
||||
// ... or push registered metrics every 10 seconds to http://victoria-metrics:8428/api/v1/import/prometheus
|
||||
// with the added `instance="foobar"` label to all the pushed metrics.
|
||||
metrics.InitPush("http://victoria-metrics:8428/api/v1/import/prometheus", 10*time.Second, `instance="foobar"`, true)
|
||||
```
|
||||
|
||||
By default, exposed metrics [do not have](https://github.com/VictoriaMetrics/metrics/issues/48#issuecomment-1620765811)
|
||||
`TYPE` or `HELP` meta information. Call [`ExposeMetadata(true)`](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#ExposeMetadata)
|
||||
in order to generate `TYPE` and `HELP` meta information per each metric.
|
||||
|
||||
See [docs](https://pkg.go.dev/github.com/VictoriaMetrics/metrics) for more info.
|
||||
|
||||
### Users
|
||||
|
||||
* `Metrics` has been extracted from [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) sources.
|
||||
See [this article](https://medium.com/devopslinks/victoriametrics-creating-the-best-remote-storage-for-prometheus-5d92d66787ac)
|
||||
for more info about `VictoriaMetrics`.
|
||||
|
||||
|
||||
### FAQ
|
||||
|
||||
#### Why the `metrics` API isn't compatible with `github.com/prometheus/client_golang`?
|
||||
|
||||
Because the `github.com/prometheus/client_golang` is too complex and is hard to use.
|
||||
|
||||
|
||||
#### Why the `metrics.WritePrometheus` doesn't expose documentation for each metric?
|
||||
|
||||
Because this documentation is ignored by Prometheus. The documentation is for users.
|
||||
Just give [meaningful names to the exported metrics](https://prometheus.io/docs/practices/naming/#metric-names)
|
||||
or add comments in the source code or in other suitable place explaining each metric exposed from your application.
|
||||
|
||||
|
||||
#### How to implement [CounterVec](https://godoc.org/github.com/prometheus/client_golang/prometheus#CounterVec) in `metrics`?
|
||||
|
||||
Just use [GetOrCreateCounter](http://godoc.org/github.com/VictoriaMetrics/metrics#GetOrCreateCounter)
|
||||
instead of `CounterVec.With`. See [this example](https://pkg.go.dev/github.com/VictoriaMetrics/metrics#example-Counter-Vec) for details.
|
||||
|
||||
|
||||
#### Why [Histogram](http://godoc.org/github.com/VictoriaMetrics/metrics#Histogram) buckets contain `vmrange` labels instead of `le` labels like in Prometheus histograms?
|
||||
|
||||
Buckets with `vmrange` labels occupy less disk space compared to Promethes-style buckets with `le` labels,
|
||||
because `vmrange` buckets don't include counters for the previous ranges. [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics) provides `prometheus_buckets`
|
||||
function, which converts `vmrange` buckets to Prometheus-style buckets with `le` labels. This is useful for building heatmaps in Grafana.
|
||||
Additionally, its' `histogram_quantile` function transparently handles histogram buckets with `vmrange` labels.
|
86
vendor/github.com/VictoriaMetrics/metrics/counter.go
generated
vendored
Normal file
86
vendor/github.com/VictoriaMetrics/metrics/counter.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// NewCounter registers and returns new counter with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
func NewCounter(name string) *Counter {
|
||||
return defaultSet.NewCounter(name)
|
||||
}
|
||||
|
||||
// Counter is a counter.
|
||||
//
|
||||
// It may be used as a gauge if Dec and Set are called.
|
||||
type Counter struct {
|
||||
n uint64
|
||||
}
|
||||
|
||||
// Inc increments c.
|
||||
func (c *Counter) Inc() {
|
||||
atomic.AddUint64(&c.n, 1)
|
||||
}
|
||||
|
||||
// Dec decrements c.
|
||||
func (c *Counter) Dec() {
|
||||
atomic.AddUint64(&c.n, ^uint64(0))
|
||||
}
|
||||
|
||||
// Add adds n to c.
|
||||
func (c *Counter) Add(n int) {
|
||||
atomic.AddUint64(&c.n, uint64(n))
|
||||
}
|
||||
|
||||
// AddInt64 adds n to c.
|
||||
func (c *Counter) AddInt64(n int64) {
|
||||
atomic.AddUint64(&c.n, uint64(n))
|
||||
}
|
||||
|
||||
// Get returns the current value for c.
|
||||
func (c *Counter) Get() uint64 {
|
||||
return atomic.LoadUint64(&c.n)
|
||||
}
|
||||
|
||||
// Set sets c value to n.
|
||||
func (c *Counter) Set(n uint64) {
|
||||
atomic.StoreUint64(&c.n, n)
|
||||
}
|
||||
|
||||
// marshalTo marshals c with the given prefix to w.
|
||||
func (c *Counter) marshalTo(prefix string, w io.Writer) {
|
||||
v := c.Get()
|
||||
fmt.Fprintf(w, "%s %d\n", prefix, v)
|
||||
}
|
||||
|
||||
func (c *Counter) metricType() string {
|
||||
return "counter"
|
||||
}
|
||||
|
||||
// GetOrCreateCounter returns registered counter with the given name
|
||||
// or creates new counter if the registry doesn't contain counter with
|
||||
// the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewCounter instead of GetOrCreateCounter.
|
||||
func GetOrCreateCounter(name string) *Counter {
|
||||
return defaultSet.GetOrCreateCounter(name)
|
||||
}
|
86
vendor/github.com/VictoriaMetrics/metrics/floatcounter.go
generated
vendored
Normal file
86
vendor/github.com/VictoriaMetrics/metrics/floatcounter.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewFloatCounter registers and returns new counter of float64 type with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
func NewFloatCounter(name string) *FloatCounter {
|
||||
return defaultSet.NewFloatCounter(name)
|
||||
}
|
||||
|
||||
// FloatCounter is a float64 counter guarded by RWmutex.
|
||||
//
|
||||
// It may be used as a gauge if Add and Sub are called.
|
||||
type FloatCounter struct {
|
||||
mu sync.Mutex
|
||||
n float64
|
||||
}
|
||||
|
||||
// Add adds n to fc.
|
||||
func (fc *FloatCounter) Add(n float64) {
|
||||
fc.mu.Lock()
|
||||
fc.n += n
|
||||
fc.mu.Unlock()
|
||||
}
|
||||
|
||||
// Sub substracts n from fc.
|
||||
func (fc *FloatCounter) Sub(n float64) {
|
||||
fc.mu.Lock()
|
||||
fc.n -= n
|
||||
fc.mu.Unlock()
|
||||
}
|
||||
|
||||
// Get returns the current value for fc.
|
||||
func (fc *FloatCounter) Get() float64 {
|
||||
fc.mu.Lock()
|
||||
n := fc.n
|
||||
fc.mu.Unlock()
|
||||
return n
|
||||
}
|
||||
|
||||
// Set sets fc value to n.
|
||||
func (fc *FloatCounter) Set(n float64) {
|
||||
fc.mu.Lock()
|
||||
fc.n = n
|
||||
fc.mu.Unlock()
|
||||
}
|
||||
|
||||
// marshalTo marshals fc with the given prefix to w.
|
||||
func (fc *FloatCounter) marshalTo(prefix string, w io.Writer) {
|
||||
v := fc.Get()
|
||||
fmt.Fprintf(w, "%s %g\n", prefix, v)
|
||||
}
|
||||
|
||||
func (fc *FloatCounter) metricType() string {
|
||||
return "counter"
|
||||
}
|
||||
|
||||
// GetOrCreateFloatCounter returns registered FloatCounter with the given name
|
||||
// or creates new FloatCounter if the registry doesn't contain FloatCounter with
|
||||
// the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned FloatCounter is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewFloatCounter instead of GetOrCreateFloatCounter.
|
||||
func GetOrCreateFloatCounter(name string) *FloatCounter {
|
||||
return defaultSet.GetOrCreateFloatCounter(name)
|
||||
}
|
122
vendor/github.com/VictoriaMetrics/metrics/gauge.go
generated
vendored
Normal file
122
vendor/github.com/VictoriaMetrics/metrics/gauge.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// NewGauge registers and returns gauge with the given name, which calls f to obtain gauge value.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// f must be safe for concurrent calls.
|
||||
// if f is nil, then it is expected that the gauge value is changed via Set(), Inc(), Dec() and Add() calls.
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
//
|
||||
// See also FloatCounter for working with floating-point values.
|
||||
func NewGauge(name string, f func() float64) *Gauge {
|
||||
return defaultSet.NewGauge(name, f)
|
||||
}
|
||||
|
||||
// Gauge is a float64 gauge.
|
||||
type Gauge struct {
|
||||
// valueBits contains uint64 representation of float64 passed to Gauge.Set.
|
||||
valueBits uint64
|
||||
|
||||
// f is a callback, which is called for returning the gauge value.
|
||||
f func() float64
|
||||
}
|
||||
|
||||
// Get returns the current value for g.
|
||||
func (g *Gauge) Get() float64 {
|
||||
if f := g.f; f != nil {
|
||||
return f()
|
||||
}
|
||||
n := atomic.LoadUint64(&g.valueBits)
|
||||
return math.Float64frombits(n)
|
||||
}
|
||||
|
||||
// Set sets g value to v.
|
||||
//
|
||||
// The g must be created with nil callback in order to be able to call this function.
|
||||
func (g *Gauge) Set(v float64) {
|
||||
if g.f != nil {
|
||||
panic(fmt.Errorf("cannot call Set on gauge created with non-nil callback"))
|
||||
}
|
||||
n := math.Float64bits(v)
|
||||
atomic.StoreUint64(&g.valueBits, n)
|
||||
}
|
||||
|
||||
// Inc increments g by 1.
|
||||
//
|
||||
// The g must be created with nil callback in order to be able to call this function.
|
||||
func (g *Gauge) Inc() {
|
||||
g.Add(1)
|
||||
}
|
||||
|
||||
// Dec decrements g by 1.
|
||||
//
|
||||
// The g must be created with nil callback in order to be able to call this function.
|
||||
func (g *Gauge) Dec() {
|
||||
g.Add(-1)
|
||||
}
|
||||
|
||||
// Add adds fAdd to g. fAdd may be positive and negative.
|
||||
//
|
||||
// The g must be created with nil callback in order to be able to call this function.
|
||||
func (g *Gauge) Add(fAdd float64) {
|
||||
if g.f != nil {
|
||||
panic(fmt.Errorf("cannot call Set on gauge created with non-nil callback"))
|
||||
}
|
||||
for {
|
||||
n := atomic.LoadUint64(&g.valueBits)
|
||||
f := math.Float64frombits(n)
|
||||
fNew := f + fAdd
|
||||
nNew := math.Float64bits(fNew)
|
||||
if atomic.CompareAndSwapUint64(&g.valueBits, n, nNew) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gauge) marshalTo(prefix string, w io.Writer) {
|
||||
v := g.Get()
|
||||
if float64(int64(v)) == v {
|
||||
// Marshal integer values without scientific notation
|
||||
fmt.Fprintf(w, "%s %d\n", prefix, int64(v))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s %g\n", prefix, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gauge) metricType() string {
|
||||
return "gauge"
|
||||
}
|
||||
|
||||
// GetOrCreateGauge returns registered gauge with the given name
|
||||
// or creates new gauge if the registry doesn't contain gauge with
|
||||
// the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewGauge instead of GetOrCreateGauge.
|
||||
//
|
||||
// See also FloatCounter for working with floating-point values.
|
||||
func GetOrCreateGauge(name string, f func() float64) *Gauge {
|
||||
return defaultSet.GetOrCreateGauge(name, f)
|
||||
}
|
184
vendor/github.com/VictoriaMetrics/metrics/go_metrics.go
generated
vendored
Normal file
184
vendor/github.com/VictoriaMetrics/metrics/go_metrics.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"runtime"
|
||||
runtimemetrics "runtime/metrics"
|
||||
"strings"
|
||||
|
||||
"github.com/valyala/histogram"
|
||||
)
|
||||
|
||||
// See https://pkg.go.dev/runtime/metrics#hdr-Supported_metrics
|
||||
var runtimeMetrics = [][2]string{
|
||||
{"/sched/latencies:seconds", "go_sched_latencies_seconds"},
|
||||
{"/sync/mutex/wait/total:seconds", "go_mutex_wait_seconds_total"},
|
||||
{"/cpu/classes/gc/mark/assist:cpu-seconds", "go_gc_mark_assist_cpu_seconds_total"},
|
||||
{"/cpu/classes/gc/total:cpu-seconds", "go_gc_cpu_seconds_total"},
|
||||
{"/gc/pauses:seconds", "go_gc_pauses_seconds"},
|
||||
{"/cpu/classes/scavenge/total:cpu-seconds", "go_scavenge_cpu_seconds_total"},
|
||||
{"/gc/gomemlimit:bytes", "go_memlimit_bytes"},
|
||||
}
|
||||
|
||||
var supportedRuntimeMetrics = initSupportedRuntimeMetrics(runtimeMetrics)
|
||||
|
||||
func initSupportedRuntimeMetrics(rms [][2]string) [][2]string {
|
||||
exposedMetrics := make(map[string]struct{})
|
||||
for _, d := range runtimemetrics.All() {
|
||||
exposedMetrics[d.Name] = struct{}{}
|
||||
}
|
||||
var supportedMetrics [][2]string
|
||||
for _, rm := range rms {
|
||||
metricName := rm[0]
|
||||
if _, ok := exposedMetrics[metricName]; ok {
|
||||
supportedMetrics = append(supportedMetrics, rm)
|
||||
} else {
|
||||
log.Printf("github.com/VictoriaMetrics/metrics: do not expose %s metric, since the corresponding metric %s isn't supported in the current Go runtime", rm[1], metricName)
|
||||
}
|
||||
}
|
||||
return supportedMetrics
|
||||
}
|
||||
|
||||
func writeGoMetrics(w io.Writer) {
|
||||
writeRuntimeMetrics(w)
|
||||
|
||||
var ms runtime.MemStats
|
||||
runtime.ReadMemStats(&ms)
|
||||
WriteGaugeUint64(w, "go_memstats_alloc_bytes", ms.Alloc)
|
||||
WriteCounterUint64(w, "go_memstats_alloc_bytes_total", ms.TotalAlloc)
|
||||
WriteGaugeUint64(w, "go_memstats_buck_hash_sys_bytes", ms.BuckHashSys)
|
||||
WriteCounterUint64(w, "go_memstats_frees_total", ms.Frees)
|
||||
WriteGaugeFloat64(w, "go_memstats_gc_cpu_fraction", ms.GCCPUFraction)
|
||||
WriteGaugeUint64(w, "go_memstats_gc_sys_bytes", ms.GCSys)
|
||||
|
||||
WriteGaugeUint64(w, "go_memstats_heap_alloc_bytes", ms.HeapAlloc)
|
||||
WriteGaugeUint64(w, "go_memstats_heap_idle_bytes", ms.HeapIdle)
|
||||
WriteGaugeUint64(w, "go_memstats_heap_inuse_bytes", ms.HeapInuse)
|
||||
WriteGaugeUint64(w, "go_memstats_heap_objects", ms.HeapObjects)
|
||||
WriteGaugeUint64(w, "go_memstats_heap_released_bytes", ms.HeapReleased)
|
||||
WriteGaugeUint64(w, "go_memstats_heap_sys_bytes", ms.HeapSys)
|
||||
WriteGaugeFloat64(w, "go_memstats_last_gc_time_seconds", float64(ms.LastGC)/1e9)
|
||||
WriteCounterUint64(w, "go_memstats_lookups_total", ms.Lookups)
|
||||
WriteCounterUint64(w, "go_memstats_mallocs_total", ms.Mallocs)
|
||||
WriteGaugeUint64(w, "go_memstats_mcache_inuse_bytes", ms.MCacheInuse)
|
||||
WriteGaugeUint64(w, "go_memstats_mcache_sys_bytes", ms.MCacheSys)
|
||||
WriteGaugeUint64(w, "go_memstats_mspan_inuse_bytes", ms.MSpanInuse)
|
||||
WriteGaugeUint64(w, "go_memstats_mspan_sys_bytes", ms.MSpanSys)
|
||||
WriteGaugeUint64(w, "go_memstats_next_gc_bytes", ms.NextGC)
|
||||
WriteGaugeUint64(w, "go_memstats_other_sys_bytes", ms.OtherSys)
|
||||
WriteGaugeUint64(w, "go_memstats_stack_inuse_bytes", ms.StackInuse)
|
||||
WriteGaugeUint64(w, "go_memstats_stack_sys_bytes", ms.StackSys)
|
||||
WriteGaugeUint64(w, "go_memstats_sys_bytes", ms.Sys)
|
||||
|
||||
WriteCounterUint64(w, "go_cgo_calls_count", uint64(runtime.NumCgoCall()))
|
||||
WriteGaugeUint64(w, "go_cpu_count", uint64(runtime.NumCPU()))
|
||||
|
||||
gcPauses := histogram.NewFast()
|
||||
for _, pauseNs := range ms.PauseNs[:] {
|
||||
gcPauses.Update(float64(pauseNs) / 1e9)
|
||||
}
|
||||
phis := []float64{0, 0.25, 0.5, 0.75, 1}
|
||||
quantiles := make([]float64, 0, len(phis))
|
||||
WriteMetadataIfNeeded(w, "go_gc_duration_seconds", "summary")
|
||||
for i, q := range gcPauses.Quantiles(quantiles[:0], phis) {
|
||||
fmt.Fprintf(w, `go_gc_duration_seconds{quantile="%g"} %g`+"\n", phis[i], q)
|
||||
}
|
||||
fmt.Fprintf(w, "go_gc_duration_seconds_sum %g\n", float64(ms.PauseTotalNs)/1e9)
|
||||
fmt.Fprintf(w, "go_gc_duration_seconds_count %d\n", ms.NumGC)
|
||||
|
||||
WriteCounterUint64(w, "go_gc_forced_count", uint64(ms.NumForcedGC))
|
||||
|
||||
WriteGaugeUint64(w, "go_gomaxprocs", uint64(runtime.GOMAXPROCS(0)))
|
||||
WriteGaugeUint64(w, "go_goroutines", uint64(runtime.NumGoroutine()))
|
||||
numThread, _ := runtime.ThreadCreateProfile(nil)
|
||||
WriteGaugeUint64(w, "go_threads", uint64(numThread))
|
||||
|
||||
// Export build details.
|
||||
WriteMetadataIfNeeded(w, "go_info", "gauge")
|
||||
fmt.Fprintf(w, "go_info{version=%q} 1\n", runtime.Version())
|
||||
|
||||
WriteMetadataIfNeeded(w, "go_info_ext", "gauge")
|
||||
fmt.Fprintf(w, "go_info_ext{compiler=%q, GOARCH=%q, GOOS=%q, GOROOT=%q} 1\n",
|
||||
runtime.Compiler, runtime.GOARCH, runtime.GOOS, runtime.GOROOT())
|
||||
}
|
||||
|
||||
func writeRuntimeMetrics(w io.Writer) {
|
||||
samples := make([]runtimemetrics.Sample, len(supportedRuntimeMetrics))
|
||||
for i, rm := range supportedRuntimeMetrics {
|
||||
samples[i].Name = rm[0]
|
||||
}
|
||||
runtimemetrics.Read(samples)
|
||||
for i, rm := range supportedRuntimeMetrics {
|
||||
writeRuntimeMetric(w, rm[1], &samples[i])
|
||||
}
|
||||
}
|
||||
|
||||
func writeRuntimeMetric(w io.Writer, name string, sample *runtimemetrics.Sample) {
|
||||
kind := sample.Value.Kind()
|
||||
switch kind {
|
||||
case runtimemetrics.KindBad:
|
||||
panic(fmt.Errorf("BUG: unexpected runtimemetrics.KindBad for sample.Name=%q", sample.Name))
|
||||
case runtimemetrics.KindUint64:
|
||||
v := sample.Value.Uint64()
|
||||
if strings.HasSuffix(name, "_total") {
|
||||
WriteCounterUint64(w, name, v)
|
||||
} else {
|
||||
WriteGaugeUint64(w, name, v)
|
||||
}
|
||||
case runtimemetrics.KindFloat64:
|
||||
v := sample.Value.Float64()
|
||||
if isCounterName(name) {
|
||||
WriteCounterFloat64(w, name, v)
|
||||
} else {
|
||||
WriteGaugeFloat64(w, name, v)
|
||||
}
|
||||
case runtimemetrics.KindFloat64Histogram:
|
||||
h := sample.Value.Float64Histogram()
|
||||
writeRuntimeHistogramMetric(w, name, h)
|
||||
default:
|
||||
panic(fmt.Errorf("unexpected metric kind=%d", kind))
|
||||
}
|
||||
}
|
||||
|
||||
func writeRuntimeHistogramMetric(w io.Writer, name string, h *runtimemetrics.Float64Histogram) {
|
||||
buckets := h.Buckets
|
||||
counts := h.Counts
|
||||
if len(buckets) != len(counts)+1 {
|
||||
panic(fmt.Errorf("the number of buckets must be bigger than the number of counts by 1 in histogram %s; got buckets=%d, counts=%d", name, len(buckets), len(counts)))
|
||||
}
|
||||
tailCount := uint64(0)
|
||||
if strings.HasSuffix(name, "_seconds") {
|
||||
// Limit the maximum bucket to 1 second, since Go runtime exposes buckets with 10K seconds,
|
||||
// which have little sense. At the same time such buckets may lead to high cardinality issues
|
||||
// at the scraper side.
|
||||
for len(buckets) > 0 && buckets[len(buckets)-1] > 1 {
|
||||
buckets = buckets[:len(buckets)-1]
|
||||
tailCount += counts[len(counts)-1]
|
||||
counts = counts[:len(counts)-1]
|
||||
}
|
||||
}
|
||||
|
||||
iStep := float64(len(buckets)) / maxRuntimeHistogramBuckets
|
||||
|
||||
totalCount := uint64(0)
|
||||
iNext := 0.0
|
||||
WriteMetadataIfNeeded(w, name, "histogram")
|
||||
for i, count := range counts {
|
||||
totalCount += count
|
||||
if float64(i) >= iNext {
|
||||
iNext += iStep
|
||||
le := buckets[i+1]
|
||||
if !math.IsInf(le, 1) {
|
||||
fmt.Fprintf(w, `%s_bucket{le="%g"} %d`+"\n", name, le, totalCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
totalCount += tailCount
|
||||
fmt.Fprintf(w, `%s_bucket{le="+Inf"} %d`+"\n", name, totalCount)
|
||||
}
|
||||
|
||||
// Limit the number of buckets for Go runtime histograms in order to prevent from high cardinality issues at scraper side.
|
||||
const maxRuntimeHistogramBuckets = 30
|
234
vendor/github.com/VictoriaMetrics/metrics/histogram.go
generated
vendored
Normal file
234
vendor/github.com/VictoriaMetrics/metrics/histogram.go
generated
vendored
Normal file
@ -0,0 +1,234 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
e10Min = -9
|
||||
e10Max = 18
|
||||
bucketsPerDecimal = 18
|
||||
decimalBucketsCount = e10Max - e10Min
|
||||
bucketsCount = decimalBucketsCount * bucketsPerDecimal
|
||||
)
|
||||
|
||||
var bucketMultiplier = math.Pow(10, 1.0/bucketsPerDecimal)
|
||||
|
||||
// Histogram is a histogram for non-negative values with automatically created buckets.
|
||||
//
|
||||
// See https://medium.com/@valyala/improving-histogram-usability-for-prometheus-and-grafana-bc7e5df0e350
|
||||
//
|
||||
// Each bucket contains a counter for values in the given range.
|
||||
// Each non-empty bucket is exposed via the following metric:
|
||||
//
|
||||
// <metric_name>_bucket{<optional_tags>,vmrange="<start>...<end>"} <counter>
|
||||
//
|
||||
// Where:
|
||||
//
|
||||
// - <metric_name> is the metric name passed to NewHistogram
|
||||
// - <optional_tags> is optional tags for the <metric_name>, which are passed to NewHistogram
|
||||
// - <start> and <end> - start and end values for the given bucket
|
||||
// - <counter> - the number of hits to the given bucket during Update* calls
|
||||
//
|
||||
// Histogram buckets can be converted to Prometheus-like buckets with `le` labels
|
||||
// with `prometheus_buckets(<metric_name>_bucket)` function from PromQL extensions in VictoriaMetrics.
|
||||
// (see https://github.com/VictoriaMetrics/VictoriaMetrics/wiki/MetricsQL ):
|
||||
//
|
||||
// prometheus_buckets(request_duration_bucket)
|
||||
//
|
||||
// Time series produced by the Histogram have better compression ratio comparing to
|
||||
// Prometheus histogram buckets with `le` labels, since they don't include counters
|
||||
// for all the previous buckets.
|
||||
//
|
||||
// Zero histogram is usable.
|
||||
type Histogram struct {
|
||||
// Mu gurantees synchronous update for all the counters and sum.
|
||||
mu sync.Mutex
|
||||
|
||||
decimalBuckets [decimalBucketsCount]*[bucketsPerDecimal]uint64
|
||||
|
||||
lower uint64
|
||||
upper uint64
|
||||
|
||||
sum float64
|
||||
}
|
||||
|
||||
// Reset resets the given histogram.
|
||||
func (h *Histogram) Reset() {
|
||||
h.mu.Lock()
|
||||
for _, db := range h.decimalBuckets[:] {
|
||||
if db == nil {
|
||||
continue
|
||||
}
|
||||
for i := range db[:] {
|
||||
db[i] = 0
|
||||
}
|
||||
}
|
||||
h.lower = 0
|
||||
h.upper = 0
|
||||
h.sum = 0
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
// Update updates h with v.
|
||||
//
|
||||
// Negative values and NaNs are ignored.
|
||||
func (h *Histogram) Update(v float64) {
|
||||
if math.IsNaN(v) || v < 0 {
|
||||
// Skip NaNs and negative values.
|
||||
return
|
||||
}
|
||||
bucketIdx := (math.Log10(v) - e10Min) * bucketsPerDecimal
|
||||
h.mu.Lock()
|
||||
h.sum += v
|
||||
if bucketIdx < 0 {
|
||||
h.lower++
|
||||
} else if bucketIdx >= bucketsCount {
|
||||
h.upper++
|
||||
} else {
|
||||
idx := uint(bucketIdx)
|
||||
if bucketIdx == float64(idx) && idx > 0 {
|
||||
// Edge case for 10^n values, which must go to the lower bucket
|
||||
// according to Prometheus logic for `le`-based histograms.
|
||||
idx--
|
||||
}
|
||||
decimalBucketIdx := idx / bucketsPerDecimal
|
||||
offset := idx % bucketsPerDecimal
|
||||
db := h.decimalBuckets[decimalBucketIdx]
|
||||
if db == nil {
|
||||
var b [bucketsPerDecimal]uint64
|
||||
db = &b
|
||||
h.decimalBuckets[decimalBucketIdx] = db
|
||||
}
|
||||
db[offset]++
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
// VisitNonZeroBuckets calls f for all buckets with non-zero counters.
|
||||
//
|
||||
// vmrange contains "<start>...<end>" string with bucket bounds. The lower bound
|
||||
// isn't included in the bucket, while the upper bound is included.
|
||||
// This is required to be compatible with Prometheus-style histogram buckets
|
||||
// with `le` (less or equal) labels.
|
||||
func (h *Histogram) VisitNonZeroBuckets(f func(vmrange string, count uint64)) {
|
||||
h.mu.Lock()
|
||||
if h.lower > 0 {
|
||||
f(lowerBucketRange, h.lower)
|
||||
}
|
||||
for decimalBucketIdx, db := range h.decimalBuckets[:] {
|
||||
if db == nil {
|
||||
continue
|
||||
}
|
||||
for offset, count := range db[:] {
|
||||
if count > 0 {
|
||||
bucketIdx := decimalBucketIdx*bucketsPerDecimal + offset
|
||||
vmrange := getVMRange(bucketIdx)
|
||||
f(vmrange, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
if h.upper > 0 {
|
||||
f(upperBucketRange, h.upper)
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
// NewHistogram creates and returns new histogram with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned histogram is safe to use from concurrent goroutines.
|
||||
func NewHistogram(name string) *Histogram {
|
||||
return defaultSet.NewHistogram(name)
|
||||
}
|
||||
|
||||
// GetOrCreateHistogram returns registered histogram with the given name
|
||||
// or creates new histogram if the registry doesn't contain histogram with
|
||||
// the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned histogram is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewHistogram instead of GetOrCreateHistogram.
|
||||
func GetOrCreateHistogram(name string) *Histogram {
|
||||
return defaultSet.GetOrCreateHistogram(name)
|
||||
}
|
||||
|
||||
// UpdateDuration updates request duration based on the given startTime.
|
||||
func (h *Histogram) UpdateDuration(startTime time.Time) {
|
||||
d := time.Since(startTime).Seconds()
|
||||
h.Update(d)
|
||||
}
|
||||
|
||||
func getVMRange(bucketIdx int) string {
|
||||
bucketRangesOnce.Do(initBucketRanges)
|
||||
return bucketRanges[bucketIdx]
|
||||
}
|
||||
|
||||
func initBucketRanges() {
|
||||
v := math.Pow10(e10Min)
|
||||
start := fmt.Sprintf("%.3e", v)
|
||||
for i := 0; i < bucketsCount; i++ {
|
||||
v *= bucketMultiplier
|
||||
end := fmt.Sprintf("%.3e", v)
|
||||
bucketRanges[i] = start + "..." + end
|
||||
start = end
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
lowerBucketRange = fmt.Sprintf("0...%.3e", math.Pow10(e10Min))
|
||||
upperBucketRange = fmt.Sprintf("%.3e...+Inf", math.Pow10(e10Max))
|
||||
|
||||
bucketRanges [bucketsCount]string
|
||||
bucketRangesOnce sync.Once
|
||||
)
|
||||
|
||||
func (h *Histogram) marshalTo(prefix string, w io.Writer) {
|
||||
countTotal := uint64(0)
|
||||
h.VisitNonZeroBuckets(func(vmrange string, count uint64) {
|
||||
tag := fmt.Sprintf("vmrange=%q", vmrange)
|
||||
metricName := addTag(prefix, tag)
|
||||
name, labels := splitMetricName(metricName)
|
||||
fmt.Fprintf(w, "%s_bucket%s %d\n", name, labels, count)
|
||||
countTotal += count
|
||||
})
|
||||
if countTotal == 0 {
|
||||
return
|
||||
}
|
||||
name, labels := splitMetricName(prefix)
|
||||
sum := h.getSum()
|
||||
if float64(int64(sum)) == sum {
|
||||
fmt.Fprintf(w, "%s_sum%s %d\n", name, labels, int64(sum))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s_sum%s %g\n", name, labels, sum)
|
||||
}
|
||||
fmt.Fprintf(w, "%s_count%s %d\n", name, labels, countTotal)
|
||||
}
|
||||
|
||||
func (h *Histogram) getSum() float64 {
|
||||
h.mu.Lock()
|
||||
sum := h.sum
|
||||
h.mu.Unlock()
|
||||
return sum
|
||||
}
|
||||
|
||||
func (h *Histogram) metricType() string {
|
||||
return "histogram"
|
||||
}
|
334
vendor/github.com/VictoriaMetrics/metrics/metrics.go
generated
vendored
Normal file
334
vendor/github.com/VictoriaMetrics/metrics/metrics.go
generated
vendored
Normal file
@ -0,0 +1,334 @@
|
||||
// Package metrics implements Prometheus-compatible metrics for applications.
|
||||
//
|
||||
// This package is lightweight alternative to https://github.com/prometheus/client_golang
|
||||
// with simpler API and smaller dependencies.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// 1. Register the required metrics via New* functions.
|
||||
// 2. Expose them to `/metrics` page via WritePrometheus.
|
||||
// 3. Update the registered metrics during application lifetime.
|
||||
//
|
||||
// The package has been extracted from https://victoriametrics.com/
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type namedMetric struct {
|
||||
name string
|
||||
metric metric
|
||||
isAux bool
|
||||
}
|
||||
|
||||
type metric interface {
|
||||
marshalTo(prefix string, w io.Writer)
|
||||
metricType() string
|
||||
}
|
||||
|
||||
var defaultSet = NewSet()
|
||||
|
||||
func init() {
|
||||
RegisterSet(defaultSet)
|
||||
}
|
||||
|
||||
var (
|
||||
registeredSets = make(map[*Set]struct{})
|
||||
registeredSetsLock sync.Mutex
|
||||
)
|
||||
|
||||
// RegisterSet registers the given set s for metrics export via global WritePrometheus() call.
|
||||
//
|
||||
// See also UnregisterSet.
|
||||
func RegisterSet(s *Set) {
|
||||
registeredSetsLock.Lock()
|
||||
registeredSets[s] = struct{}{}
|
||||
registeredSetsLock.Unlock()
|
||||
}
|
||||
|
||||
// UnregisterSet stops exporting metrics for the given s via global WritePrometheus() call.
|
||||
//
|
||||
// Call s.UnregisterAllMetrics() after unregistering s if it is no longer used.
|
||||
func UnregisterSet(s *Set) {
|
||||
registeredSetsLock.Lock()
|
||||
delete(registeredSets, s)
|
||||
registeredSetsLock.Unlock()
|
||||
}
|
||||
|
||||
// RegisterMetricsWriter registers writeMetrics callback for including metrics in the output generated by WritePrometheus.
|
||||
//
|
||||
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
|
||||
// The last line generated by writeMetrics must end with \n.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is OK to register multiple writeMetrics callbacks - all of them will be called sequentially for gererating the output at WritePrometheus.
|
||||
func RegisterMetricsWriter(writeMetrics func(w io.Writer)) {
|
||||
defaultSet.RegisterMetricsWriter(writeMetrics)
|
||||
}
|
||||
|
||||
// WritePrometheus writes all the metrics in Prometheus format from the default set, all the added sets and metrics writers to w.
|
||||
//
|
||||
// Additional sets can be registered via RegisterSet() call.
|
||||
// Additional metric writers can be registered via RegisterMetricsWriter() call.
|
||||
//
|
||||
// If exposeProcessMetrics is true, then various `go_*` and `process_*` metrics
|
||||
// are exposed for the current process.
|
||||
//
|
||||
// The WritePrometheus func is usually called inside "/metrics" handler:
|
||||
//
|
||||
// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
|
||||
// metrics.WritePrometheus(w, true)
|
||||
// })
|
||||
func WritePrometheus(w io.Writer, exposeProcessMetrics bool) {
|
||||
registeredSetsLock.Lock()
|
||||
sets := make([]*Set, 0, len(registeredSets))
|
||||
for s := range registeredSets {
|
||||
sets = append(sets, s)
|
||||
}
|
||||
registeredSetsLock.Unlock()
|
||||
|
||||
sort.Slice(sets, func(i, j int) bool {
|
||||
return uintptr(unsafe.Pointer(sets[i])) < uintptr(unsafe.Pointer(sets[j]))
|
||||
})
|
||||
for _, s := range sets {
|
||||
s.WritePrometheus(w)
|
||||
}
|
||||
if exposeProcessMetrics {
|
||||
WriteProcessMetrics(w)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteProcessMetrics writes additional process metrics in Prometheus format to w.
|
||||
//
|
||||
// The following `go_*` and `process_*` metrics are exposed for the currently
|
||||
// running process. Below is a short description for the exposed `process_*` metrics:
|
||||
//
|
||||
// - process_cpu_seconds_system_total - CPU time spent in syscalls
|
||||
//
|
||||
// - process_cpu_seconds_user_total - CPU time spent in userspace
|
||||
//
|
||||
// - process_cpu_seconds_total - CPU time spent by the process
|
||||
//
|
||||
// - process_major_pagefaults_total - page faults resulted in disk IO
|
||||
//
|
||||
// - process_minor_pagefaults_total - page faults resolved without disk IO
|
||||
//
|
||||
// - process_resident_memory_bytes - recently accessed memory (aka RSS or resident memory)
|
||||
//
|
||||
// - process_resident_memory_peak_bytes - the maximum RSS memory usage
|
||||
//
|
||||
// - process_resident_memory_anon_bytes - RSS for memory-mapped files
|
||||
//
|
||||
// - process_resident_memory_file_bytes - RSS for memory allocated by the process
|
||||
//
|
||||
// - process_resident_memory_shared_bytes - RSS for memory shared between multiple processes
|
||||
//
|
||||
// - process_virtual_memory_bytes - virtual memory usage
|
||||
//
|
||||
// - process_virtual_memory_peak_bytes - the maximum virtual memory usage
|
||||
//
|
||||
// - process_num_threads - the number of threads
|
||||
//
|
||||
// - process_start_time_seconds - process start time as unix timestamp
|
||||
//
|
||||
// - process_io_read_bytes_total - the number of bytes read via syscalls
|
||||
//
|
||||
// - process_io_written_bytes_total - the number of bytes written via syscalls
|
||||
//
|
||||
// - process_io_read_syscalls_total - the number of read syscalls
|
||||
//
|
||||
// - process_io_write_syscalls_total - the number of write syscalls
|
||||
//
|
||||
// - process_io_storage_read_bytes_total - the number of bytes actually read from disk
|
||||
//
|
||||
// - process_io_storage_written_bytes_total - the number of bytes actually written to disk
|
||||
//
|
||||
// - go_sched_latencies_seconds - time spent by goroutines in ready state before they start execution
|
||||
//
|
||||
// - go_mutex_wait_seconds_total - summary time spent by all the goroutines while waiting for locked mutex
|
||||
//
|
||||
// - go_gc_mark_assist_cpu_seconds_total - summary CPU time spent by goroutines in GC mark assist state
|
||||
//
|
||||
// - go_gc_cpu_seconds_total - summary time spent in GC
|
||||
//
|
||||
// - go_gc_pauses_seconds - duration of GC pauses
|
||||
//
|
||||
// - go_scavenge_cpu_seconds_total - CPU time spent on returning the memory to OS
|
||||
//
|
||||
// - go_memlimit_bytes - the GOMEMLIMIT env var value
|
||||
//
|
||||
// - go_memstats_alloc_bytes - memory usage for Go objects in the heap
|
||||
//
|
||||
// - go_memstats_alloc_bytes_total - the cumulative counter for total size of allocated Go objects
|
||||
//
|
||||
// - go_memstats_buck_hash_sys_bytes - bytes of memory in profiling bucket hash tables
|
||||
//
|
||||
// - go_memstats_frees_total - the cumulative counter for number of freed Go objects
|
||||
//
|
||||
// - go_memstats_gc_cpu_fraction - the fraction of CPU spent in Go garbage collector
|
||||
//
|
||||
// - go_memstats_gc_sys_bytes - the size of Go garbage collector metadata
|
||||
//
|
||||
// - go_memstats_heap_alloc_bytes - the same as go_memstats_alloc_bytes
|
||||
//
|
||||
// - go_memstats_heap_idle_bytes - idle memory ready for new Go object allocations
|
||||
//
|
||||
// - go_memstats_heap_inuse_bytes - bytes in in-use spans
|
||||
//
|
||||
// - go_memstats_heap_objects - the number of Go objects in the heap
|
||||
//
|
||||
// - go_memstats_heap_released_bytes - bytes of physical memory returned to the OS
|
||||
//
|
||||
// - go_memstats_heap_sys_bytes - memory requested for Go objects from the OS
|
||||
//
|
||||
// - go_memstats_last_gc_time_seconds - unix timestamp the last garbage collection finished
|
||||
//
|
||||
// - go_memstats_lookups_total - the number of pointer lookups performed by the runtime
|
||||
//
|
||||
// - go_memstats_mallocs_total - the number of allocations for Go objects
|
||||
//
|
||||
// - go_memstats_mcache_inuse_bytes - bytes of allocated mcache structures
|
||||
//
|
||||
// - go_memstats_mcache_sys_bytes - bytes of memory obtained from the OS for mcache structures
|
||||
//
|
||||
// - go_memstats_mspan_inuse_bytes - bytes of allocated mspan structures
|
||||
//
|
||||
// - go_memstats_mspan_sys_bytes - bytes of memory obtained from the OS for mspan structures
|
||||
//
|
||||
// - go_memstats_next_gc_bytes - the target heap size when the next garbage collection should start
|
||||
//
|
||||
// - go_memstats_other_sys_bytes - bytes of memory in miscellaneous off-heap runtime allocations
|
||||
//
|
||||
// - go_memstats_stack_inuse_bytes - memory used for goroutine stacks
|
||||
//
|
||||
// - go_memstats_stack_sys_bytes - memory requested fromthe OS for goroutine stacks
|
||||
//
|
||||
// - go_memstats_sys_bytes - memory requested by Go runtime from the OS
|
||||
//
|
||||
// - go_cgo_calls_count - the total number of CGO calls
|
||||
//
|
||||
// - go_cpu_count - the number of CPU cores on the host where the app runs
|
||||
//
|
||||
// The WriteProcessMetrics func is usually called in combination with writing Set metrics
|
||||
// inside "/metrics" handler:
|
||||
//
|
||||
// http.HandleFunc("/metrics", func(w http.ResponseWriter, req *http.Request) {
|
||||
// mySet.WritePrometheus(w)
|
||||
// metrics.WriteProcessMetrics(w)
|
||||
// })
|
||||
//
|
||||
// See also WrteFDMetrics.
|
||||
func WriteProcessMetrics(w io.Writer) {
|
||||
writeGoMetrics(w)
|
||||
writeProcessMetrics(w)
|
||||
writePushMetrics(w)
|
||||
}
|
||||
|
||||
// WriteFDMetrics writes `process_max_fds` and `process_open_fds` metrics to w.
|
||||
func WriteFDMetrics(w io.Writer) {
|
||||
writeFDMetrics(w)
|
||||
}
|
||||
|
||||
// UnregisterMetric removes metric with the given name from default set.
|
||||
//
|
||||
// See also UnregisterAllMetrics.
|
||||
func UnregisterMetric(name string) bool {
|
||||
return defaultSet.UnregisterMetric(name)
|
||||
}
|
||||
|
||||
// UnregisterAllMetrics unregisters all the metrics from default set.
|
||||
//
|
||||
// It also unregisters writeMetrics callbacks passed to RegisterMetricsWriter.
|
||||
func UnregisterAllMetrics() {
|
||||
defaultSet.UnregisterAllMetrics()
|
||||
}
|
||||
|
||||
// ListMetricNames returns sorted list of all the metric names from default set.
|
||||
func ListMetricNames() []string {
|
||||
return defaultSet.ListMetricNames()
|
||||
}
|
||||
|
||||
// GetDefaultSet returns the default metrics set.
|
||||
func GetDefaultSet() *Set {
|
||||
return defaultSet
|
||||
}
|
||||
|
||||
// ExposeMetadata allows enabling adding TYPE and HELP metadata to the exposed metrics globally.
|
||||
//
|
||||
// It is safe to call this method multiple times. It is allowed to change it in runtime.
|
||||
// ExposeMetadata is set to false by default.
|
||||
func ExposeMetadata(v bool) {
|
||||
n := 0
|
||||
if v {
|
||||
n = 1
|
||||
}
|
||||
atomic.StoreUint32(&exposeMetadata, uint32(n))
|
||||
}
|
||||
|
||||
func isMetadataEnabled() bool {
|
||||
n := atomic.LoadUint32(&exposeMetadata)
|
||||
return n != 0
|
||||
}
|
||||
|
||||
var exposeMetadata uint32
|
||||
|
||||
func isCounterName(name string) bool {
|
||||
return strings.HasSuffix(name, "_total")
|
||||
}
|
||||
|
||||
// WriteGaugeUint64 writes gauge metric with the given name and value to w in Prometheus text exposition format.
|
||||
func WriteGaugeUint64(w io.Writer, name string, value uint64) {
|
||||
writeMetricUint64(w, name, "gauge", value)
|
||||
}
|
||||
|
||||
// WriteGaugeFloat64 writes gauge metric with the given name and value to w in Prometheus text exposition format.
|
||||
func WriteGaugeFloat64(w io.Writer, name string, value float64) {
|
||||
writeMetricFloat64(w, name, "gauge", value)
|
||||
}
|
||||
|
||||
// WriteCounterUint64 writes counter metric with the given name and value to w in Prometheus text exposition format.
|
||||
func WriteCounterUint64(w io.Writer, name string, value uint64) {
|
||||
writeMetricUint64(w, name, "counter", value)
|
||||
}
|
||||
|
||||
// WriteCounterFloat64 writes counter metric with the given name and value to w in Prometheus text exposition format.
|
||||
func WriteCounterFloat64(w io.Writer, name string, value float64) {
|
||||
writeMetricFloat64(w, name, "counter", value)
|
||||
}
|
||||
|
||||
func writeMetricUint64(w io.Writer, metricName, metricType string, value uint64) {
|
||||
WriteMetadataIfNeeded(w, metricName, metricType)
|
||||
fmt.Fprintf(w, "%s %d\n", metricName, value)
|
||||
}
|
||||
|
||||
func writeMetricFloat64(w io.Writer, metricName, metricType string, value float64) {
|
||||
WriteMetadataIfNeeded(w, metricName, metricType)
|
||||
fmt.Fprintf(w, "%s %g\n", metricName, value)
|
||||
}
|
||||
|
||||
// WriteMetadataIfNeeded writes HELP and TYPE metadata for the given metricName and metricType if this is globally enabled via ExposeMetadata().
|
||||
//
|
||||
// If the metadata exposition isn't enabled, then this function is no-op.
|
||||
func WriteMetadataIfNeeded(w io.Writer, metricName, metricType string) {
|
||||
if !isMetadataEnabled() {
|
||||
return
|
||||
}
|
||||
metricFamily := getMetricFamily(metricName)
|
||||
fmt.Fprintf(w, "# HELP %s\n", metricFamily)
|
||||
fmt.Fprintf(w, "# TYPE %s %s\n", metricFamily, metricType)
|
||||
}
|
||||
|
||||
func getMetricFamily(metricName string) string {
|
||||
n := strings.IndexByte(metricName, '{')
|
||||
if n < 0 {
|
||||
return metricName
|
||||
}
|
||||
return metricName[:n]
|
||||
}
|
275
vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go
generated
vendored
Normal file
275
vendor/github.com/VictoriaMetrics/metrics/process_metrics_linux.go
generated
vendored
Normal file
@ -0,0 +1,275 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// See https://github.com/prometheus/procfs/blob/a4ac0826abceb44c40fc71daed2b301db498b93e/proc_stat.go#L40 .
|
||||
const userHZ = 100
|
||||
|
||||
// See http://man7.org/linux/man-pages/man5/proc.5.html
|
||||
type procStat struct {
|
||||
State byte
|
||||
Ppid int
|
||||
Pgrp int
|
||||
Session int
|
||||
TtyNr int
|
||||
Tpgid int
|
||||
Flags uint
|
||||
Minflt uint
|
||||
Cminflt uint
|
||||
Majflt uint
|
||||
Cmajflt uint
|
||||
Utime uint
|
||||
Stime uint
|
||||
Cutime int
|
||||
Cstime int
|
||||
Priority int
|
||||
Nice int
|
||||
NumThreads int
|
||||
ItrealValue int
|
||||
Starttime uint64
|
||||
Vsize uint
|
||||
Rss int
|
||||
}
|
||||
|
||||
func writeProcessMetrics(w io.Writer) {
|
||||
statFilepath := "/proc/self/stat"
|
||||
data, err := ioutil.ReadFile(statFilepath)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot open %s: %s", statFilepath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Search for the end of command.
|
||||
n := bytes.LastIndex(data, []byte(") "))
|
||||
if n < 0 {
|
||||
log.Printf("ERROR: metrics: cannot find command in parentheses in %q read from %s", data, statFilepath)
|
||||
return
|
||||
}
|
||||
data = data[n+2:]
|
||||
|
||||
var p procStat
|
||||
bb := bytes.NewBuffer(data)
|
||||
_, err = fmt.Fscanf(bb, "%c %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
|
||||
&p.State, &p.Ppid, &p.Pgrp, &p.Session, &p.TtyNr, &p.Tpgid, &p.Flags, &p.Minflt, &p.Cminflt, &p.Majflt, &p.Cmajflt,
|
||||
&p.Utime, &p.Stime, &p.Cutime, &p.Cstime, &p.Priority, &p.Nice, &p.NumThreads, &p.ItrealValue, &p.Starttime, &p.Vsize, &p.Rss)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot parse %q read from %s: %s", data, statFilepath, err)
|
||||
return
|
||||
}
|
||||
|
||||
// It is expensive obtaining `process_open_fds` when big number of file descriptors is opened,
|
||||
// so don't do it here.
|
||||
// See writeFDMetrics instead.
|
||||
|
||||
utime := float64(p.Utime) / userHZ
|
||||
stime := float64(p.Stime) / userHZ
|
||||
WriteCounterFloat64(w, "process_cpu_seconds_system_total", stime)
|
||||
WriteCounterFloat64(w, "process_cpu_seconds_total", utime+stime)
|
||||
WriteCounterFloat64(w, "process_cpu_seconds_user_total", utime)
|
||||
WriteCounterUint64(w, "process_major_pagefaults_total", uint64(p.Majflt))
|
||||
WriteCounterUint64(w, "process_minor_pagefaults_total", uint64(p.Minflt))
|
||||
WriteGaugeUint64(w, "process_num_threads", uint64(p.NumThreads))
|
||||
WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(p.Rss)*4096)
|
||||
WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTimeSeconds))
|
||||
WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(p.Vsize))
|
||||
writeProcessMemMetrics(w)
|
||||
writeIOMetrics(w)
|
||||
}
|
||||
|
||||
var procSelfIOErrLogged uint32
|
||||
|
||||
func writeIOMetrics(w io.Writer) {
|
||||
ioFilepath := "/proc/self/io"
|
||||
data, err := ioutil.ReadFile(ioFilepath)
|
||||
if err != nil {
|
||||
// Do not spam the logs with errors - this error cannot be fixed without process restart.
|
||||
// See https://github.com/VictoriaMetrics/metrics/issues/42
|
||||
if atomic.CompareAndSwapUint32(&procSelfIOErrLogged, 0, 1) {
|
||||
log.Printf("ERROR: metrics: cannot read process_io_* metrics from %q, so these metrics won't be updated until the error is fixed; "+
|
||||
"see https://github.com/VictoriaMetrics/metrics/issues/42 ; The error: %s", ioFilepath, err)
|
||||
}
|
||||
}
|
||||
|
||||
getInt := func(s string) int64 {
|
||||
n := strings.IndexByte(s, ' ')
|
||||
if n < 0 {
|
||||
log.Printf("ERROR: metrics: cannot find whitespace in %q at %q", s, ioFilepath)
|
||||
return 0
|
||||
}
|
||||
v, err := strconv.ParseInt(s[n+1:], 10, 64)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot parse %q at %q: %s", s, ioFilepath, err)
|
||||
return 0
|
||||
}
|
||||
return v
|
||||
}
|
||||
var rchar, wchar, syscr, syscw, readBytes, writeBytes int64
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, s := range lines {
|
||||
s = strings.TrimSpace(s)
|
||||
switch {
|
||||
case strings.HasPrefix(s, "rchar: "):
|
||||
rchar = getInt(s)
|
||||
case strings.HasPrefix(s, "wchar: "):
|
||||
wchar = getInt(s)
|
||||
case strings.HasPrefix(s, "syscr: "):
|
||||
syscr = getInt(s)
|
||||
case strings.HasPrefix(s, "syscw: "):
|
||||
syscw = getInt(s)
|
||||
case strings.HasPrefix(s, "read_bytes: "):
|
||||
readBytes = getInt(s)
|
||||
case strings.HasPrefix(s, "write_bytes: "):
|
||||
writeBytes = getInt(s)
|
||||
}
|
||||
}
|
||||
WriteGaugeUint64(w, "process_io_read_bytes_total", uint64(rchar))
|
||||
WriteGaugeUint64(w, "process_io_written_bytes_total", uint64(wchar))
|
||||
WriteGaugeUint64(w, "process_io_read_syscalls_total", uint64(syscr))
|
||||
WriteGaugeUint64(w, "process_io_write_syscalls_total", uint64(syscw))
|
||||
WriteGaugeUint64(w, "process_io_storage_read_bytes_total", uint64(readBytes))
|
||||
WriteGaugeUint64(w, "process_io_storage_written_bytes_total", uint64(writeBytes))
|
||||
}
|
||||
|
||||
var startTimeSeconds = time.Now().Unix()
|
||||
|
||||
// writeFDMetrics writes process_max_fds and process_open_fds metrics to w.
|
||||
func writeFDMetrics(w io.Writer) {
|
||||
totalOpenFDs, err := getOpenFDsCount("/proc/self/fd")
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot determine open file descriptors count: %s", err)
|
||||
return
|
||||
}
|
||||
maxOpenFDs, err := getMaxFilesLimit("/proc/self/limits")
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot determine the limit on open file descritors: %s", err)
|
||||
return
|
||||
}
|
||||
WriteGaugeUint64(w, "process_max_fds", maxOpenFDs)
|
||||
WriteGaugeUint64(w, "process_open_fds", totalOpenFDs)
|
||||
}
|
||||
|
||||
func getOpenFDsCount(path string) (uint64, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
var totalOpenFDs uint64
|
||||
for {
|
||||
names, err := f.Readdirnames(512)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("unexpected error at Readdirnames: %s", err)
|
||||
}
|
||||
totalOpenFDs += uint64(len(names))
|
||||
}
|
||||
return totalOpenFDs, nil
|
||||
}
|
||||
|
||||
func getMaxFilesLimit(path string) (uint64, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
const prefix = "Max open files"
|
||||
for _, s := range lines {
|
||||
if !strings.HasPrefix(s, prefix) {
|
||||
continue
|
||||
}
|
||||
text := strings.TrimSpace(s[len(prefix):])
|
||||
// Extract soft limit.
|
||||
n := strings.IndexByte(text, ' ')
|
||||
if n < 0 {
|
||||
return 0, fmt.Errorf("cannot extract soft limit from %q", s)
|
||||
}
|
||||
text = text[:n]
|
||||
if text == "unlimited" {
|
||||
return 1<<64 - 1, nil
|
||||
}
|
||||
limit, err := strconv.ParseUint(text, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot parse soft limit from %q: %s", s, err)
|
||||
}
|
||||
return limit, nil
|
||||
}
|
||||
return 0, fmt.Errorf("cannot find max open files limit")
|
||||
}
|
||||
|
||||
// https://man7.org/linux/man-pages/man5/procfs.5.html
|
||||
type memStats struct {
|
||||
vmPeak uint64
|
||||
rssPeak uint64
|
||||
rssAnon uint64
|
||||
rssFile uint64
|
||||
rssShmem uint64
|
||||
}
|
||||
|
||||
func writeProcessMemMetrics(w io.Writer) {
|
||||
ms, err := getMemStats("/proc/self/status")
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot determine memory status: %s", err)
|
||||
return
|
||||
}
|
||||
WriteGaugeUint64(w, "process_virtual_memory_peak_bytes", ms.vmPeak)
|
||||
WriteGaugeUint64(w, "process_resident_memory_peak_bytes", ms.rssPeak)
|
||||
WriteGaugeUint64(w, "process_resident_memory_anon_bytes", ms.rssAnon)
|
||||
WriteGaugeUint64(w, "process_resident_memory_file_bytes", ms.rssFile)
|
||||
WriteGaugeUint64(w, "process_resident_memory_shared_bytes", ms.rssShmem)
|
||||
|
||||
}
|
||||
|
||||
func getMemStats(path string) (*memStats, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ms memStats
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, s := range lines {
|
||||
if !strings.HasPrefix(s, "Vm") && !strings.HasPrefix(s, "Rss") {
|
||||
continue
|
||||
}
|
||||
// Extract key value.
|
||||
line := strings.Fields(s)
|
||||
if len(line) != 3 {
|
||||
return nil, fmt.Errorf("unexpected number of fields found in %q; got %d; want %d", s, len(line), 3)
|
||||
}
|
||||
memStatName := line[0]
|
||||
memStatValue := line[1]
|
||||
value, err := strconv.ParseUint(memStatValue, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse number from %q: %w", s, err)
|
||||
}
|
||||
if line[2] != "kB" {
|
||||
return nil, fmt.Errorf("expecting kB value in %q; got %q", s, line[2])
|
||||
}
|
||||
value *= 1024
|
||||
switch memStatName {
|
||||
case "VmPeak:":
|
||||
ms.vmPeak = value
|
||||
case "VmHWM:":
|
||||
ms.rssPeak = value
|
||||
case "RssAnon:":
|
||||
ms.rssAnon = value
|
||||
case "RssFile:":
|
||||
ms.rssFile = value
|
||||
case "RssShmem:":
|
||||
ms.rssShmem = value
|
||||
}
|
||||
}
|
||||
return &ms, nil
|
||||
}
|
16
vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go
generated
vendored
Normal file
16
vendor/github.com/VictoriaMetrics/metrics/process_metrics_other.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
//go:build !linux && !windows
|
||||
// +build !linux,!windows
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func writeProcessMetrics(w io.Writer) {
|
||||
// TODO: implement it
|
||||
}
|
||||
|
||||
func writeFDMetrics(w io.Writer) {
|
||||
// TODO: implement it.
|
||||
}
|
84
vendor/github.com/VictoriaMetrics/metrics/process_metrics_windows.go
generated
vendored
Normal file
84
vendor/github.com/VictoriaMetrics/metrics/process_metrics_windows.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modpsapi = syscall.NewLazyDLL("psapi.dll")
|
||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo
|
||||
procGetProcessMemoryInfo = modpsapi.NewProc("GetProcessMemoryInfo")
|
||||
procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount")
|
||||
)
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex
|
||||
type processMemoryCounters struct {
|
||||
_ uint32
|
||||
PageFaultCount uint32
|
||||
PeakWorkingSetSize uintptr
|
||||
WorkingSetSize uintptr
|
||||
QuotaPeakPagedPoolUsage uintptr
|
||||
QuotaPagedPoolUsage uintptr
|
||||
QuotaPeakNonPagedPoolUsage uintptr
|
||||
QuotaNonPagedPoolUsage uintptr
|
||||
PagefileUsage uintptr
|
||||
PeakPagefileUsage uintptr
|
||||
PrivateUsage uintptr
|
||||
}
|
||||
|
||||
func writeProcessMetrics(w io.Writer) {
|
||||
h := windows.CurrentProcess()
|
||||
var startTime, exitTime, stime, utime windows.Filetime
|
||||
err := windows.GetProcessTimes(h, &startTime, &exitTime, &stime, &utime)
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics: cannot read process times: %s", err)
|
||||
return
|
||||
}
|
||||
var mc processMemoryCounters
|
||||
r1, _, err := procGetProcessMemoryInfo.Call(
|
||||
uintptr(h),
|
||||
uintptr(unsafe.Pointer(&mc)),
|
||||
unsafe.Sizeof(mc),
|
||||
)
|
||||
if r1 != 1 {
|
||||
log.Printf("ERROR: metrics: cannot read process memory information: %s", err)
|
||||
return
|
||||
}
|
||||
stimeSeconds := float64(uint64(stime.HighDateTime)<<32+uint64(stime.LowDateTime)) / 1e7
|
||||
utimeSeconds := float64(uint64(utime.HighDateTime)<<32+uint64(utime.LowDateTime)) / 1e7
|
||||
WriteCounterFloat64(w, "process_cpu_seconds_system_total", stimeSeconds)
|
||||
WriteCounterFloat64(w, "process_cpu_seconds_total", stimeSeconds+utimeSeconds)
|
||||
WriteCounterFloat64(w, "process_cpu_seconds_user_total", stimeSeconds)
|
||||
WriteCounterUint64(w, "process_pagefaults_total", uint64(mc.PageFaultCount))
|
||||
WriteGaugeUint64(w, "process_start_time_seconds", uint64(startTime.Nanoseconds())/1e9)
|
||||
WriteGaugeUint64(w, "process_virtual_memory_bytes", uint64(mc.PrivateUsage))
|
||||
WriteGaugeUint64(w, "process_resident_memory_peak_bytes", uint64(mc.PeakWorkingSetSize))
|
||||
WriteGaugeUint64(w, "process_resident_memory_bytes", uint64(mc.WorkingSetSize))
|
||||
}
|
||||
|
||||
func writeFDMetrics(w io.Writer) {
|
||||
h := windows.CurrentProcess()
|
||||
var count uint32
|
||||
r1, _, err := procGetProcessHandleCount.Call(
|
||||
uintptr(h),
|
||||
uintptr(unsafe.Pointer(&count)),
|
||||
)
|
||||
if r1 != 1 {
|
||||
log.Printf("ERROR: metrics: cannot determine open file descriptors count: %s", err)
|
||||
return
|
||||
}
|
||||
// it seems to be hard-coded limit for 64-bit systems
|
||||
// https://learn.microsoft.com/en-us/archive/blogs/markrussinovich/pushing-the-limits-of-windows-handles#maximum-number-of-handles
|
||||
WriteGaugeUint64(w, "process_max_fds", 16777216)
|
||||
WriteGaugeUint64(w, "process_open_fds", uint64(count))
|
||||
}
|
498
vendor/github.com/VictoriaMetrics/metrics/push.go
generated
vendored
Normal file
498
vendor/github.com/VictoriaMetrics/metrics/push.go
generated
vendored
Normal file
@ -0,0 +1,498 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"compress/gzip"
|
||||
)
|
||||
|
||||
// PushOptions is the list of options, which may be applied to InitPushWithOptions().
|
||||
type PushOptions struct {
|
||||
// ExtraLabels is an optional comma-separated list of `label="value"` labels, which must be added to all the metrics before pushing them to pushURL.
|
||||
ExtraLabels string
|
||||
|
||||
// Headers is an optional list of HTTP headers to add to every push request to pushURL.
|
||||
//
|
||||
// Every item in the list must have the form `Header: value`. For example, `Authorization: Custom my-top-secret`.
|
||||
Headers []string
|
||||
|
||||
// Whether to disable HTTP request body compression before sending the metrics to pushURL.
|
||||
//
|
||||
// By default the compression is enabled.
|
||||
DisableCompression bool
|
||||
|
||||
// Optional WaitGroup for waiting until all the push workers created with this WaitGroup are stopped.
|
||||
WaitGroup *sync.WaitGroup
|
||||
}
|
||||
|
||||
// InitPushWithOptions sets up periodic push for globally registered metrics to the given pushURL with the given interval.
|
||||
//
|
||||
// The periodic push is stopped when ctx is canceled.
|
||||
// It is possible to wait until the background metrics push worker is stopped on a WaitGroup passed via opts.WaitGroup.
|
||||
//
|
||||
// If pushProcessMetrics is set to true, then 'process_*' and `go_*` metrics are also pushed to pushURL.
|
||||
//
|
||||
// opts may contain additional configuration options if non-nil.
|
||||
//
|
||||
// The metrics are pushed to pushURL in Prometheus text exposition format.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPushWithOptions multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
func InitPushWithOptions(ctx context.Context, pushURL string, interval time.Duration, pushProcessMetrics bool, opts *PushOptions) error {
|
||||
writeMetrics := func(w io.Writer) {
|
||||
WritePrometheus(w, pushProcessMetrics)
|
||||
}
|
||||
return InitPushExtWithOptions(ctx, pushURL, interval, writeMetrics, opts)
|
||||
}
|
||||
|
||||
// InitPushProcessMetrics sets up periodic push for 'process_*' metrics to the given pushURL with the given interval.
|
||||
//
|
||||
// extraLabels may contain comma-separated list of `label="value"` labels, which will be added
|
||||
// to all the metrics before pushing them to pushURL.
|
||||
//
|
||||
// The metrics are pushed to pushURL in Prometheus text exposition format.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPushProcessMetrics multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
func InitPushProcessMetrics(pushURL string, interval time.Duration, extraLabels string) error {
|
||||
return InitPushExt(pushURL, interval, extraLabels, WriteProcessMetrics)
|
||||
}
|
||||
|
||||
// InitPush sets up periodic push for globally registered metrics to the given pushURL with the given interval.
|
||||
//
|
||||
// extraLabels may contain comma-separated list of `label="value"` labels, which will be added
|
||||
// to all the metrics before pushing them to pushURL.
|
||||
//
|
||||
// If pushProcessMetrics is set to true, then 'process_*' and `go_*` metrics are also pushed to pushURL.
|
||||
//
|
||||
// The metrics are pushed to pushURL in Prometheus text exposition format.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPush multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
func InitPush(pushURL string, interval time.Duration, extraLabels string, pushProcessMetrics bool) error {
|
||||
writeMetrics := func(w io.Writer) {
|
||||
WritePrometheus(w, pushProcessMetrics)
|
||||
}
|
||||
return InitPushExt(pushURL, interval, extraLabels, writeMetrics)
|
||||
}
|
||||
|
||||
// PushMetrics pushes globally registered metrics to pushURL.
|
||||
//
|
||||
// If pushProcessMetrics is set to true, then 'process_*' and `go_*` metrics are also pushed to pushURL.
|
||||
//
|
||||
// opts may contain additional configuration options if non-nil.
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
func PushMetrics(ctx context.Context, pushURL string, pushProcessMetrics bool, opts *PushOptions) error {
|
||||
writeMetrics := func(w io.Writer) {
|
||||
WritePrometheus(w, pushProcessMetrics)
|
||||
}
|
||||
return PushMetricsExt(ctx, pushURL, writeMetrics, opts)
|
||||
}
|
||||
|
||||
// InitPushWithOptions sets up periodic push for metrics from s to the given pushURL with the given interval.
|
||||
//
|
||||
// The periodic push is stopped when the ctx is canceled.
|
||||
// It is possible to wait until the background metrics push worker is stopped on a WaitGroup passed via opts.WaitGroup.
|
||||
//
|
||||
// opts may contain additional configuration options if non-nil.
|
||||
//
|
||||
// The metrics are pushed to pushURL in Prometheus text exposition format.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPushWithOptions multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
func (s *Set) InitPushWithOptions(ctx context.Context, pushURL string, interval time.Duration, opts *PushOptions) error {
|
||||
return InitPushExtWithOptions(ctx, pushURL, interval, s.WritePrometheus, opts)
|
||||
}
|
||||
|
||||
// InitPush sets up periodic push for metrics from s to the given pushURL with the given interval.
|
||||
//
|
||||
// extraLabels may contain comma-separated list of `label="value"` labels, which will be added
|
||||
// to all the metrics before pushing them to pushURL.
|
||||
//
|
||||
// The metrics are pushed to pushURL in Prometheus text exposition format.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPush multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
func (s *Set) InitPush(pushURL string, interval time.Duration, extraLabels string) error {
|
||||
return InitPushExt(pushURL, interval, extraLabels, s.WritePrometheus)
|
||||
}
|
||||
|
||||
// PushMetrics pushes s metrics to pushURL.
|
||||
//
|
||||
// opts may contain additional configuration options if non-nil.
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
func (s *Set) PushMetrics(ctx context.Context, pushURL string, opts *PushOptions) error {
|
||||
return PushMetricsExt(ctx, pushURL, s.WritePrometheus, opts)
|
||||
}
|
||||
|
||||
// InitPushExt sets up periodic push for metrics obtained by calling writeMetrics with the given interval.
|
||||
//
|
||||
// extraLabels may contain comma-separated list of `label="value"` labels, which will be added
|
||||
// to all the metrics before pushing them to pushURL.
|
||||
//
|
||||
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPushExt multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
//
|
||||
// It is OK calling InitPushExt multiple times with different writeMetrics -
|
||||
// in this case all the metrics generated by writeMetrics callbacks are written to pushURL.
|
||||
func InitPushExt(pushURL string, interval time.Duration, extraLabels string, writeMetrics func(w io.Writer)) error {
|
||||
opts := &PushOptions{
|
||||
ExtraLabels: extraLabels,
|
||||
}
|
||||
return InitPushExtWithOptions(context.Background(), pushURL, interval, writeMetrics, opts)
|
||||
}
|
||||
|
||||
// InitPushExtWithOptions sets up periodic push for metrics obtained by calling writeMetrics with the given interval.
|
||||
//
|
||||
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// The periodic push is stopped when the ctx is canceled.
|
||||
// It is possible to wait until the background metrics push worker is stopped on a WaitGroup passed via opts.WaitGroup.
|
||||
//
|
||||
// opts may contain additional configuration options if non-nil.
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
//
|
||||
// It is OK calling InitPushExtWithOptions multiple times with different pushURL -
|
||||
// in this case metrics are pushed to all the provided pushURL urls.
|
||||
//
|
||||
// It is OK calling InitPushExtWithOptions multiple times with different writeMetrics -
|
||||
// in this case all the metrics generated by writeMetrics callbacks are written to pushURL.
|
||||
func InitPushExtWithOptions(ctx context.Context, pushURL string, interval time.Duration, writeMetrics func(w io.Writer), opts *PushOptions) error {
|
||||
pc, err := newPushContext(pushURL, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate interval
|
||||
if interval <= 0 {
|
||||
return fmt.Errorf("interval must be positive; got %s", interval)
|
||||
}
|
||||
pushMetricsSet.GetOrCreateFloatCounter(fmt.Sprintf(`metrics_push_interval_seconds{url=%q}`, pc.pushURLRedacted)).Set(interval.Seconds())
|
||||
|
||||
var wg *sync.WaitGroup
|
||||
if opts != nil {
|
||||
wg = opts.WaitGroup
|
||||
if wg != nil {
|
||||
wg.Add(1)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
stopCh := ctx.Done()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ctxLocal, cancel := context.WithTimeout(ctx, interval+time.Second)
|
||||
err := pc.pushMetrics(ctxLocal, writeMetrics)
|
||||
cancel()
|
||||
if err != nil {
|
||||
log.Printf("ERROR: metrics.push: %s", err)
|
||||
}
|
||||
case <-stopCh:
|
||||
if wg != nil {
|
||||
wg.Done()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushMetricsExt pushes metrics generated by wirteMetrics to pushURL.
|
||||
//
|
||||
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// opts may contain additional configuration options if non-nil.
|
||||
//
|
||||
// It is recommended pushing metrics to /api/v1/import/prometheus endpoint according to
|
||||
// https://docs.victoriametrics.com/#how-to-import-data-in-prometheus-exposition-format
|
||||
func PushMetricsExt(ctx context.Context, pushURL string, writeMetrics func(w io.Writer), opts *PushOptions) error {
|
||||
pc, err := newPushContext(pushURL, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pc.pushMetrics(ctx, writeMetrics)
|
||||
}
|
||||
|
||||
type pushContext struct {
|
||||
pushURL *url.URL
|
||||
pushURLRedacted string
|
||||
extraLabels string
|
||||
headers http.Header
|
||||
disableCompression bool
|
||||
|
||||
client *http.Client
|
||||
|
||||
pushesTotal *Counter
|
||||
bytesPushedTotal *Counter
|
||||
pushBlockSize *Histogram
|
||||
pushDuration *Histogram
|
||||
pushErrors *Counter
|
||||
}
|
||||
|
||||
func newPushContext(pushURL string, opts *PushOptions) (*pushContext, error) {
|
||||
if opts == nil {
|
||||
opts = &PushOptions{}
|
||||
}
|
||||
|
||||
// validate pushURL
|
||||
pu, err := url.Parse(pushURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse pushURL=%q: %w", pushURL, err)
|
||||
}
|
||||
if pu.Scheme != "http" && pu.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported scheme in pushURL=%q; expecting 'http' or 'https'", pushURL)
|
||||
}
|
||||
if pu.Host == "" {
|
||||
return nil, fmt.Errorf("missing host in pushURL=%q", pushURL)
|
||||
}
|
||||
|
||||
// validate ExtraLabels
|
||||
extraLabels := opts.ExtraLabels
|
||||
if err := validateTags(extraLabels); err != nil {
|
||||
return nil, fmt.Errorf("invalid extraLabels=%q: %w", extraLabels, err)
|
||||
}
|
||||
|
||||
// validate Headers
|
||||
headers := make(http.Header)
|
||||
for _, h := range opts.Headers {
|
||||
n := strings.IndexByte(h, ':')
|
||||
if n < 0 {
|
||||
return nil, fmt.Errorf("missing `:` delimiter in the header %q", h)
|
||||
}
|
||||
name := strings.TrimSpace(h[:n])
|
||||
value := strings.TrimSpace(h[n+1:])
|
||||
headers.Add(name, value)
|
||||
}
|
||||
|
||||
pushURLRedacted := pu.Redacted()
|
||||
client := &http.Client{}
|
||||
return &pushContext{
|
||||
pushURL: pu,
|
||||
pushURLRedacted: pushURLRedacted,
|
||||
extraLabels: extraLabels,
|
||||
headers: headers,
|
||||
disableCompression: opts.DisableCompression,
|
||||
|
||||
client: client,
|
||||
|
||||
pushesTotal: pushMetricsSet.GetOrCreateCounter(fmt.Sprintf(`metrics_push_total{url=%q}`, pushURLRedacted)),
|
||||
bytesPushedTotal: pushMetricsSet.GetOrCreateCounter(fmt.Sprintf(`metrics_push_bytes_pushed_total{url=%q}`, pushURLRedacted)),
|
||||
pushBlockSize: pushMetricsSet.GetOrCreateHistogram(fmt.Sprintf(`metrics_push_block_size_bytes{url=%q}`, pushURLRedacted)),
|
||||
pushDuration: pushMetricsSet.GetOrCreateHistogram(fmt.Sprintf(`metrics_push_duration_seconds{url=%q}`, pushURLRedacted)),
|
||||
pushErrors: pushMetricsSet.GetOrCreateCounter(fmt.Sprintf(`metrics_push_errors_total{url=%q}`, pushURLRedacted)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (pc *pushContext) pushMetrics(ctx context.Context, writeMetrics func(w io.Writer)) error {
|
||||
bb := getBytesBuffer()
|
||||
defer putBytesBuffer(bb)
|
||||
|
||||
writeMetrics(bb)
|
||||
|
||||
if len(pc.extraLabels) > 0 {
|
||||
bbTmp := getBytesBuffer()
|
||||
bbTmp.B = append(bbTmp.B[:0], bb.B...)
|
||||
bb.B = addExtraLabels(bb.B[:0], bbTmp.B, pc.extraLabels)
|
||||
putBytesBuffer(bbTmp)
|
||||
}
|
||||
if !pc.disableCompression {
|
||||
bbTmp := getBytesBuffer()
|
||||
bbTmp.B = append(bbTmp.B[:0], bb.B...)
|
||||
bb.B = bb.B[:0]
|
||||
zw := getGzipWriter(bb)
|
||||
if _, err := zw.Write(bbTmp.B); err != nil {
|
||||
panic(fmt.Errorf("BUG: cannot write %d bytes to gzip writer: %s", len(bbTmp.B), err))
|
||||
}
|
||||
if err := zw.Close(); err != nil {
|
||||
panic(fmt.Errorf("BUG: cannot flush metrics to gzip writer: %s", err))
|
||||
}
|
||||
putGzipWriter(zw)
|
||||
putBytesBuffer(bbTmp)
|
||||
}
|
||||
|
||||
// Update metrics
|
||||
pc.pushesTotal.Inc()
|
||||
blockLen := len(bb.B)
|
||||
pc.bytesPushedTotal.Add(blockLen)
|
||||
pc.pushBlockSize.Update(float64(blockLen))
|
||||
|
||||
// Prepare the request to sent to pc.pushURL
|
||||
reqBody := bytes.NewReader(bb.B)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", pc.pushURL.String(), reqBody)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("BUG: metrics.push: cannot initialize request for metrics push to %q: %w", pc.pushURLRedacted, err))
|
||||
}
|
||||
|
||||
// Set the needed headers
|
||||
for name, values := range pc.headers {
|
||||
for _, value := range values {
|
||||
req.Header.Add(name, value)
|
||||
}
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
if !pc.disableCompression {
|
||||
req.Header.Set("Content-Encoding", "gzip")
|
||||
}
|
||||
|
||||
// Perform the request
|
||||
startTime := time.Now()
|
||||
resp, err := pc.client.Do(req)
|
||||
pc.pushDuration.UpdateDuration(startTime)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return nil
|
||||
}
|
||||
pc.pushErrors.Inc()
|
||||
return fmt.Errorf("cannot push metrics to %q: %s", pc.pushURLRedacted, err)
|
||||
}
|
||||
if resp.StatusCode/100 != 2 {
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
pc.pushErrors.Inc()
|
||||
return fmt.Errorf("unexpected status code in response from %q: %d; expecting 2xx; response body: %q", pc.pushURLRedacted, resp.StatusCode, body)
|
||||
}
|
||||
_ = resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
var pushMetricsSet = NewSet()
|
||||
|
||||
func writePushMetrics(w io.Writer) {
|
||||
pushMetricsSet.WritePrometheus(w)
|
||||
}
|
||||
|
||||
func addExtraLabels(dst, src []byte, extraLabels string) []byte {
|
||||
for len(src) > 0 {
|
||||
var line []byte
|
||||
n := bytes.IndexByte(src, '\n')
|
||||
if n >= 0 {
|
||||
line = src[:n]
|
||||
src = src[n+1:]
|
||||
} else {
|
||||
line = src
|
||||
src = nil
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
// Skip empy lines
|
||||
continue
|
||||
}
|
||||
if bytes.HasPrefix(line, bashBytes) {
|
||||
// Copy comments as is
|
||||
dst = append(dst, line...)
|
||||
dst = append(dst, '\n')
|
||||
continue
|
||||
}
|
||||
n = bytes.IndexByte(line, '{')
|
||||
if n >= 0 {
|
||||
dst = append(dst, line[:n+1]...)
|
||||
dst = append(dst, extraLabels...)
|
||||
dst = append(dst, ',')
|
||||
dst = append(dst, line[n+1:]...)
|
||||
} else {
|
||||
n = bytes.LastIndexByte(line, ' ')
|
||||
if n < 0 {
|
||||
panic(fmt.Errorf("BUG: missing whitespace between metric name and metric value in Prometheus text exposition line %q", line))
|
||||
}
|
||||
dst = append(dst, line[:n]...)
|
||||
dst = append(dst, '{')
|
||||
dst = append(dst, extraLabels...)
|
||||
dst = append(dst, '}')
|
||||
dst = append(dst, line[n:]...)
|
||||
}
|
||||
dst = append(dst, '\n')
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
var bashBytes = []byte("#")
|
||||
|
||||
func getBytesBuffer() *bytesBuffer {
|
||||
v := bytesBufferPool.Get()
|
||||
if v == nil {
|
||||
return &bytesBuffer{}
|
||||
}
|
||||
return v.(*bytesBuffer)
|
||||
}
|
||||
|
||||
func putBytesBuffer(bb *bytesBuffer) {
|
||||
bb.B = bb.B[:0]
|
||||
bytesBufferPool.Put(bb)
|
||||
}
|
||||
|
||||
var bytesBufferPool sync.Pool
|
||||
|
||||
type bytesBuffer struct {
|
||||
B []byte
|
||||
}
|
||||
|
||||
func (bb *bytesBuffer) Write(p []byte) (int, error) {
|
||||
bb.B = append(bb.B, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func getGzipWriter(w io.Writer) *gzip.Writer {
|
||||
v := gzipWriterPool.Get()
|
||||
if v == nil {
|
||||
return gzip.NewWriter(w)
|
||||
}
|
||||
zw := v.(*gzip.Writer)
|
||||
zw.Reset(w)
|
||||
return zw
|
||||
}
|
||||
|
||||
func putGzipWriter(zw *gzip.Writer) {
|
||||
zw.Reset(io.Discard)
|
||||
gzipWriterPool.Put(zw)
|
||||
}
|
||||
|
||||
var gzipWriterPool sync.Pool
|
575
vendor/github.com/VictoriaMetrics/metrics/set.go
generated
vendored
Normal file
575
vendor/github.com/VictoriaMetrics/metrics/set.go
generated
vendored
Normal file
@ -0,0 +1,575 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set is a set of metrics.
|
||||
//
|
||||
// Metrics belonging to a set are exported separately from global metrics.
|
||||
//
|
||||
// Set.WritePrometheus must be called for exporting metrics from the set.
|
||||
type Set struct {
|
||||
mu sync.Mutex
|
||||
a []*namedMetric
|
||||
m map[string]*namedMetric
|
||||
summaries []*Summary
|
||||
|
||||
metricsWriters []func(w io.Writer)
|
||||
}
|
||||
|
||||
// NewSet creates new set of metrics.
|
||||
//
|
||||
// Pass the set to RegisterSet() function in order to export its metrics via global WritePrometheus() call.
|
||||
func NewSet() *Set {
|
||||
return &Set{
|
||||
m: make(map[string]*namedMetric),
|
||||
}
|
||||
}
|
||||
|
||||
// WritePrometheus writes all the metrics from s to w in Prometheus format.
|
||||
func (s *Set) WritePrometheus(w io.Writer) {
|
||||
// Collect all the metrics in in-memory buffer in order to prevent from long locking due to slow w.
|
||||
var bb bytes.Buffer
|
||||
lessFunc := func(i, j int) bool {
|
||||
return s.a[i].name < s.a[j].name
|
||||
}
|
||||
s.mu.Lock()
|
||||
for _, sm := range s.summaries {
|
||||
sm.updateQuantiles()
|
||||
}
|
||||
if !sort.SliceIsSorted(s.a, lessFunc) {
|
||||
sort.Slice(s.a, lessFunc)
|
||||
}
|
||||
sa := append([]*namedMetric(nil), s.a...)
|
||||
metricsWriters := s.metricsWriters
|
||||
s.mu.Unlock()
|
||||
|
||||
prevMetricFamily := ""
|
||||
for _, nm := range sa {
|
||||
metricFamily := getMetricFamily(nm.name)
|
||||
if metricFamily != prevMetricFamily {
|
||||
// write meta info only once per metric family
|
||||
metricType := nm.metric.metricType()
|
||||
WriteMetadataIfNeeded(&bb, nm.name, metricType)
|
||||
prevMetricFamily = metricFamily
|
||||
}
|
||||
// Call marshalTo without the global lock, since certain metric types such as Gauge
|
||||
// can call a callback, which, in turn, can try calling s.mu.Lock again.
|
||||
nm.metric.marshalTo(nm.name, &bb)
|
||||
}
|
||||
w.Write(bb.Bytes())
|
||||
|
||||
for _, writeMetrics := range metricsWriters {
|
||||
writeMetrics(w)
|
||||
}
|
||||
}
|
||||
|
||||
// NewHistogram creates and returns new histogram in s with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned histogram is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewHistogram(name string) *Histogram {
|
||||
h := &Histogram{}
|
||||
s.registerMetric(name, h)
|
||||
return h
|
||||
}
|
||||
|
||||
// GetOrCreateHistogram returns registered histogram in s with the given name
|
||||
// or creates new histogram if s doesn't contain histogram with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned histogram is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewHistogram instead of GetOrCreateHistogram.
|
||||
func (s *Set) GetOrCreateHistogram(name string) *Histogram {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing histogram.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Histogram{},
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
h, ok := nm.metric.(*Histogram)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Histogram. It is %T", name, nm.metric))
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// NewCounter registers and returns new counter with the given name in the s.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewCounter(name string) *Counter {
|
||||
c := &Counter{}
|
||||
s.registerMetric(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetOrCreateCounter returns registered counter in s with the given name
|
||||
// or creates new counter if s doesn't contain counter with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned counter is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewCounter instead of GetOrCreateCounter.
|
||||
func (s *Set) GetOrCreateCounter(name string) *Counter {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing counter.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Counter{},
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
c, ok := nm.metric.(*Counter)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewFloatCounter registers and returns new FloatCounter with the given name in the s.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned FloatCounter is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewFloatCounter(name string) *FloatCounter {
|
||||
c := &FloatCounter{}
|
||||
s.registerMetric(name, c)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetOrCreateFloatCounter returns registered FloatCounter in s with the given name
|
||||
// or creates new FloatCounter if s doesn't contain FloatCounter with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned FloatCounter is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewFloatCounter instead of GetOrCreateFloatCounter.
|
||||
func (s *Set) GetOrCreateFloatCounter(name string) *FloatCounter {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing counter.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &FloatCounter{},
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
c, ok := nm.metric.(*FloatCounter)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Counter. It is %T", name, nm.metric))
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// NewGauge registers and returns gauge with the given name in s, which calls f
|
||||
// to obtain gauge value.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// f must be safe for concurrent calls.
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewGauge(name string, f func() float64) *Gauge {
|
||||
g := &Gauge{
|
||||
f: f,
|
||||
}
|
||||
s.registerMetric(name, g)
|
||||
return g
|
||||
}
|
||||
|
||||
// GetOrCreateGauge returns registered gauge with the given name in s
|
||||
// or creates new gauge if s doesn't contain gauge with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned gauge is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewGauge instead of GetOrCreateGauge.
|
||||
func (s *Set) GetOrCreateGauge(name string, f func() float64) *Gauge {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing gauge.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: &Gauge{
|
||||
f: f,
|
||||
},
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
g, ok := nm.metric.(*Gauge)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Gauge. It is %T", name, nm.metric))
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
// NewSummary creates and returns new summary with the given name in s.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewSummary(name string) *Summary {
|
||||
return s.NewSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
}
|
||||
|
||||
// NewSummaryExt creates and returns new summary in s with the given name,
|
||||
// window and quantiles.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func (s *Set) NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
sm := newSummary(window, quantiles)
|
||||
|
||||
s.mu.Lock()
|
||||
// defer will unlock in case of panic
|
||||
// checks in tests
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.mustRegisterLocked(name, sm, false)
|
||||
registerSummaryLocked(sm)
|
||||
s.registerSummaryQuantilesLocked(name, sm)
|
||||
s.summaries = append(s.summaries, sm)
|
||||
return sm
|
||||
}
|
||||
|
||||
// GetOrCreateSummary returns registered summary with the given name in s
|
||||
// or creates new summary if s doesn't contain summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewSummary instead of GetOrCreateSummary.
|
||||
func (s *Set) GetOrCreateSummary(name string) *Summary {
|
||||
return s.GetOrCreateSummaryExt(name, defaultSummaryWindow, defaultSummaryQuantiles)
|
||||
}
|
||||
|
||||
// GetOrCreateSummaryExt returns registered summary with the given name,
|
||||
// window and quantiles in s or creates new summary if s doesn't
|
||||
// contain summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt.
|
||||
func (s *Set) GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
s.mu.Lock()
|
||||
nm := s.m[name]
|
||||
s.mu.Unlock()
|
||||
if nm == nil {
|
||||
// Slow path - create and register missing summary.
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
sm := newSummary(window, quantiles)
|
||||
nmNew := &namedMetric{
|
||||
name: name,
|
||||
metric: sm,
|
||||
}
|
||||
s.mu.Lock()
|
||||
nm = s.m[name]
|
||||
if nm == nil {
|
||||
nm = nmNew
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
registerSummaryLocked(sm)
|
||||
s.registerSummaryQuantilesLocked(name, sm)
|
||||
}
|
||||
s.summaries = append(s.summaries, sm)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
sm, ok := nm.metric.(*Summary)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("BUG: metric %q isn't a Summary. It is %T", name, nm.metric))
|
||||
}
|
||||
if sm.window != window {
|
||||
panic(fmt.Errorf("BUG: invalid window requested for the summary %q; requested %s; need %s", name, window, sm.window))
|
||||
}
|
||||
if !isEqualQuantiles(sm.quantiles, quantiles) {
|
||||
panic(fmt.Errorf("BUG: invalid quantiles requested from the summary %q; requested %v; need %v", name, quantiles, sm.quantiles))
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
func (s *Set) registerSummaryQuantilesLocked(name string, sm *Summary) {
|
||||
for i, q := range sm.quantiles {
|
||||
quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q))
|
||||
qv := &quantileValue{
|
||||
sm: sm,
|
||||
idx: i,
|
||||
}
|
||||
s.mustRegisterLocked(quantileValueName, qv, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Set) registerMetric(name string, m metric) {
|
||||
if err := validateMetric(name); err != nil {
|
||||
panic(fmt.Errorf("BUG: invalid metric name %q: %s", name, err))
|
||||
}
|
||||
s.mu.Lock()
|
||||
// defer will unlock in case of panic
|
||||
// checks in test
|
||||
defer s.mu.Unlock()
|
||||
s.mustRegisterLocked(name, m, false)
|
||||
}
|
||||
|
||||
// mustRegisterLocked registers given metric with the given name.
|
||||
//
|
||||
// Panics if the given name was already registered before.
|
||||
func (s *Set) mustRegisterLocked(name string, m metric, isAux bool) {
|
||||
nm, ok := s.m[name]
|
||||
if !ok {
|
||||
nm = &namedMetric{
|
||||
name: name,
|
||||
metric: m,
|
||||
isAux: isAux,
|
||||
}
|
||||
s.m[name] = nm
|
||||
s.a = append(s.a, nm)
|
||||
}
|
||||
if ok {
|
||||
panic(fmt.Errorf("BUG: metric %q is already registered", name))
|
||||
}
|
||||
}
|
||||
|
||||
// UnregisterMetric removes metric with the given name from s.
|
||||
//
|
||||
// True is returned if the metric has been removed.
|
||||
// False is returned if the given metric is missing in s.
|
||||
func (s *Set) UnregisterMetric(name string) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
nm, ok := s.m[name]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if nm.isAux {
|
||||
// Do not allow deleting auxiliary metrics such as summary_metric{quantile="..."}
|
||||
// Such metrics must be deleted via parent metric name, e.g. summary_metric .
|
||||
return false
|
||||
}
|
||||
return s.unregisterMetricLocked(nm)
|
||||
}
|
||||
|
||||
func (s *Set) unregisterMetricLocked(nm *namedMetric) bool {
|
||||
name := nm.name
|
||||
delete(s.m, name)
|
||||
|
||||
deleteFromList := func(metricName string) {
|
||||
for i, nm := range s.a {
|
||||
if nm.name == metricName {
|
||||
s.a = append(s.a[:i], s.a[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("BUG: cannot find metric %q in the list of registered metrics", name))
|
||||
}
|
||||
|
||||
// remove metric from s.a
|
||||
deleteFromList(name)
|
||||
|
||||
sm, ok := nm.metric.(*Summary)
|
||||
if !ok {
|
||||
// There is no need in cleaning up non-summary metrics.
|
||||
return true
|
||||
}
|
||||
|
||||
// cleanup registry from per-quantile metrics
|
||||
for _, q := range sm.quantiles {
|
||||
quantileValueName := addTag(name, fmt.Sprintf(`quantile="%g"`, q))
|
||||
delete(s.m, quantileValueName)
|
||||
deleteFromList(quantileValueName)
|
||||
}
|
||||
|
||||
// Remove sm from s.summaries
|
||||
found := false
|
||||
for i, xsm := range s.summaries {
|
||||
if xsm == sm {
|
||||
s.summaries = append(s.summaries[:i], s.summaries[i+1:]...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
panic(fmt.Errorf("BUG: cannot find summary %q in the list of registered summaries", name))
|
||||
}
|
||||
unregisterSummary(sm)
|
||||
return true
|
||||
}
|
||||
|
||||
// UnregisterAllMetrics de-registers all metrics registered in s.
|
||||
//
|
||||
// It also de-registers writeMetrics callbacks passed to RegisterMetricsWriter.
|
||||
func (s *Set) UnregisterAllMetrics() {
|
||||
metricNames := s.ListMetricNames()
|
||||
for _, name := range metricNames {
|
||||
s.UnregisterMetric(name)
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.metricsWriters = nil
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// ListMetricNames returns sorted list of all the metrics in s.
|
||||
//
|
||||
// The returned list doesn't include metrics generated by metricsWriter passed to RegisterMetricsWriter.
|
||||
func (s *Set) ListMetricNames() []string {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
metricNames := make([]string, 0, len(s.m))
|
||||
for _, nm := range s.m {
|
||||
if nm.isAux {
|
||||
continue
|
||||
}
|
||||
metricNames = append(metricNames, nm.name)
|
||||
}
|
||||
sort.Strings(metricNames)
|
||||
return metricNames
|
||||
}
|
||||
|
||||
// RegisterMetricsWriter registers writeMetrics callback for including metrics in the output generated by s.WritePrometheus.
|
||||
//
|
||||
// The writeMetrics callback must write metrics to w in Prometheus text exposition format without timestamps and trailing comments.
|
||||
// The last line generated by writeMetrics must end with \n.
|
||||
// See https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md#text-based-format
|
||||
//
|
||||
// It is OK to reguster multiple writeMetrics callbacks - all of them will be called sequentially for gererating the output at s.WritePrometheus.
|
||||
func (s *Set) RegisterMetricsWriter(writeMetrics func(w io.Writer)) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.metricsWriters = append(s.metricsWriters, writeMetrics)
|
||||
}
|
262
vendor/github.com/VictoriaMetrics/metrics/summary.go
generated
vendored
Normal file
262
vendor/github.com/VictoriaMetrics/metrics/summary.go
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/valyala/histogram"
|
||||
)
|
||||
|
||||
const defaultSummaryWindow = 5 * time.Minute
|
||||
|
||||
var defaultSummaryQuantiles = []float64{0.5, 0.9, 0.97, 0.99, 1}
|
||||
|
||||
// Summary implements summary.
|
||||
type Summary struct {
|
||||
mu sync.Mutex
|
||||
|
||||
curr *histogram.Fast
|
||||
next *histogram.Fast
|
||||
|
||||
quantiles []float64
|
||||
quantileValues []float64
|
||||
|
||||
sum float64
|
||||
count uint64
|
||||
|
||||
window time.Duration
|
||||
}
|
||||
|
||||
// NewSummary creates and returns new summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func NewSummary(name string) *Summary {
|
||||
return defaultSet.NewSummary(name)
|
||||
}
|
||||
|
||||
// NewSummaryExt creates and returns new summary with the given name,
|
||||
// window and quantiles.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
func NewSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
return defaultSet.NewSummaryExt(name, window, quantiles)
|
||||
}
|
||||
|
||||
func newSummary(window time.Duration, quantiles []float64) *Summary {
|
||||
// Make a copy of quantiles in order to prevent from their modification by the caller.
|
||||
quantiles = append([]float64{}, quantiles...)
|
||||
validateQuantiles(quantiles)
|
||||
sm := &Summary{
|
||||
curr: histogram.NewFast(),
|
||||
next: histogram.NewFast(),
|
||||
quantiles: quantiles,
|
||||
quantileValues: make([]float64, len(quantiles)),
|
||||
window: window,
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
func validateQuantiles(quantiles []float64) {
|
||||
for _, q := range quantiles {
|
||||
if q < 0 || q > 1 {
|
||||
panic(fmt.Errorf("BUG: quantile must be in the range [0..1]; got %v", q))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the summary.
|
||||
func (sm *Summary) Update(v float64) {
|
||||
sm.mu.Lock()
|
||||
sm.curr.Update(v)
|
||||
sm.next.Update(v)
|
||||
sm.sum += v
|
||||
sm.count++
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
|
||||
// UpdateDuration updates request duration based on the given startTime.
|
||||
func (sm *Summary) UpdateDuration(startTime time.Time) {
|
||||
d := time.Since(startTime).Seconds()
|
||||
sm.Update(d)
|
||||
}
|
||||
|
||||
func (sm *Summary) marshalTo(prefix string, w io.Writer) {
|
||||
// Marshal only *_sum and *_count values.
|
||||
// Quantile values should be already updated by the caller via sm.updateQuantiles() call.
|
||||
// sm.quantileValues will be marshaled later via quantileValue.marshalTo.
|
||||
sm.mu.Lock()
|
||||
sum := sm.sum
|
||||
count := sm.count
|
||||
sm.mu.Unlock()
|
||||
|
||||
if count > 0 {
|
||||
name, filters := splitMetricName(prefix)
|
||||
if float64(int64(sum)) == sum {
|
||||
// Marshal integer sum without scientific notation
|
||||
fmt.Fprintf(w, "%s_sum%s %d\n", name, filters, int64(sum))
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s_sum%s %g\n", name, filters, sum)
|
||||
}
|
||||
fmt.Fprintf(w, "%s_count%s %d\n", name, filters, count)
|
||||
}
|
||||
}
|
||||
|
||||
func (sm *Summary) metricType() string {
|
||||
return "summary"
|
||||
}
|
||||
|
||||
func splitMetricName(name string) (string, string) {
|
||||
n := strings.IndexByte(name, '{')
|
||||
if n < 0 {
|
||||
return name, ""
|
||||
}
|
||||
return name[:n], name[n:]
|
||||
}
|
||||
|
||||
func (sm *Summary) updateQuantiles() {
|
||||
sm.mu.Lock()
|
||||
sm.quantileValues = sm.curr.Quantiles(sm.quantileValues[:0], sm.quantiles)
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetOrCreateSummary returns registered summary with the given name
|
||||
// or creates new summary if the registry doesn't contain summary with
|
||||
// the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewSummary instead of GetOrCreateSummary.
|
||||
func GetOrCreateSummary(name string) *Summary {
|
||||
return defaultSet.GetOrCreateSummary(name)
|
||||
}
|
||||
|
||||
// GetOrCreateSummaryExt returns registered summary with the given name,
|
||||
// window and quantiles or creates new summary if the registry doesn't
|
||||
// contain summary with the given name.
|
||||
//
|
||||
// name must be valid Prometheus-compatible metric with possible labels.
|
||||
// For instance,
|
||||
//
|
||||
// - foo
|
||||
// - foo{bar="baz"}
|
||||
// - foo{bar="baz",aaa="b"}
|
||||
//
|
||||
// The returned summary is safe to use from concurrent goroutines.
|
||||
//
|
||||
// Performance tip: prefer NewSummaryExt instead of GetOrCreateSummaryExt.
|
||||
func GetOrCreateSummaryExt(name string, window time.Duration, quantiles []float64) *Summary {
|
||||
return defaultSet.GetOrCreateSummaryExt(name, window, quantiles)
|
||||
}
|
||||
|
||||
func isEqualQuantiles(a, b []float64) bool {
|
||||
// Do not use relfect.DeepEqual, since it is slower than the direct comparison.
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type quantileValue struct {
|
||||
sm *Summary
|
||||
idx int
|
||||
}
|
||||
|
||||
func (qv *quantileValue) marshalTo(prefix string, w io.Writer) {
|
||||
qv.sm.mu.Lock()
|
||||
v := qv.sm.quantileValues[qv.idx]
|
||||
qv.sm.mu.Unlock()
|
||||
if !math.IsNaN(v) {
|
||||
fmt.Fprintf(w, "%s %g\n", prefix, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (qv *quantileValue) metricType() string {
|
||||
return "unsupported"
|
||||
}
|
||||
|
||||
func addTag(name, tag string) string {
|
||||
if len(name) == 0 || name[len(name)-1] != '}' {
|
||||
return fmt.Sprintf("%s{%s}", name, tag)
|
||||
}
|
||||
return fmt.Sprintf("%s,%s}", name[:len(name)-1], tag)
|
||||
}
|
||||
|
||||
func registerSummaryLocked(sm *Summary) {
|
||||
window := sm.window
|
||||
summariesLock.Lock()
|
||||
summaries[window] = append(summaries[window], sm)
|
||||
if len(summaries[window]) == 1 {
|
||||
go summariesSwapCron(window)
|
||||
}
|
||||
summariesLock.Unlock()
|
||||
}
|
||||
|
||||
func unregisterSummary(sm *Summary) {
|
||||
window := sm.window
|
||||
summariesLock.Lock()
|
||||
sms := summaries[window]
|
||||
found := false
|
||||
for i, xsm := range sms {
|
||||
if xsm == sm {
|
||||
sms = append(sms[:i], sms[i+1:]...)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
panic(fmt.Errorf("BUG: cannot find registered summary %p", sm))
|
||||
}
|
||||
summaries[window] = sms
|
||||
summariesLock.Unlock()
|
||||
}
|
||||
|
||||
func summariesSwapCron(window time.Duration) {
|
||||
for {
|
||||
time.Sleep(window / 2)
|
||||
summariesLock.Lock()
|
||||
for _, sm := range summaries[window] {
|
||||
sm.mu.Lock()
|
||||
tmp := sm.curr
|
||||
sm.curr = sm.next
|
||||
sm.next = tmp
|
||||
sm.next.Reset()
|
||||
sm.mu.Unlock()
|
||||
}
|
||||
summariesLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
summaries = map[time.Duration][]*Summary{}
|
||||
summariesLock sync.Mutex
|
||||
)
|
84
vendor/github.com/VictoriaMetrics/metrics/validator.go
generated
vendored
Normal file
84
vendor/github.com/VictoriaMetrics/metrics/validator.go
generated
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func validateMetric(s string) error {
|
||||
if len(s) == 0 {
|
||||
return fmt.Errorf("metric cannot be empty")
|
||||
}
|
||||
n := strings.IndexByte(s, '{')
|
||||
if n < 0 {
|
||||
return validateIdent(s)
|
||||
}
|
||||
ident := s[:n]
|
||||
s = s[n+1:]
|
||||
if err := validateIdent(ident); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) == 0 || s[len(s)-1] != '}' {
|
||||
return fmt.Errorf("missing closing curly brace at the end of %q", ident)
|
||||
}
|
||||
return validateTags(s[:len(s)-1])
|
||||
}
|
||||
|
||||
func validateTags(s string) error {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
n := strings.IndexByte(s, '=')
|
||||
if n < 0 {
|
||||
return fmt.Errorf("missing `=` after %q", s)
|
||||
}
|
||||
ident := s[:n]
|
||||
s = s[n+1:]
|
||||
if err := validateIdent(ident); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(s) == 0 || s[0] != '"' {
|
||||
return fmt.Errorf("missing starting `\"` for %q value; tail=%q", ident, s)
|
||||
}
|
||||
s = s[1:]
|
||||
again:
|
||||
n = strings.IndexByte(s, '"')
|
||||
if n < 0 {
|
||||
return fmt.Errorf("missing trailing `\"` for %q value; tail=%q", ident, s)
|
||||
}
|
||||
m := n
|
||||
for m > 0 && s[m-1] == '\\' {
|
||||
m--
|
||||
}
|
||||
if (n-m)%2 == 1 {
|
||||
s = s[n+1:]
|
||||
goto again
|
||||
}
|
||||
s = s[n+1:]
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasPrefix(s, ",") {
|
||||
return fmt.Errorf("missing `,` after %q value; tail=%q", ident, s)
|
||||
}
|
||||
s = skipSpace(s[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func skipSpace(s string) string {
|
||||
for len(s) > 0 && s[0] == ' ' {
|
||||
s = s[1:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func validateIdent(s string) error {
|
||||
if !identRegexp.MatchString(s) {
|
||||
return fmt.Errorf("invalid identifier %q", s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var identRegexp = regexp.MustCompile("^[a-zA-Z_:.][a-zA-Z0-9_:.]*$")
|
4
vendor/github.com/caarlos0/env/v10/.gitignore
generated
vendored
Normal file
4
vendor/github.com/caarlos0/env/v10/.gitignore
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
coverage.txt
|
||||
bin
|
||||
card.png
|
||||
dist
|
8
vendor/github.com/caarlos0/env/v10/.golangci.yml
generated
vendored
Normal file
8
vendor/github.com/caarlos0/env/v10/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
linters:
|
||||
enable:
|
||||
- thelper
|
||||
- gofumpt
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- wastedassign
|
3
vendor/github.com/caarlos0/env/v10/.goreleaser.yml
generated
vendored
Normal file
3
vendor/github.com/caarlos0/env/v10/.goreleaser.yml
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
includes:
|
||||
- from_url:
|
||||
url: https://raw.githubusercontent.com/caarlos0/.goreleaserfiles/main/lib.yml
|
7
vendor/github.com/caarlos0/env/v10/.mailmap
generated
vendored
Normal file
7
vendor/github.com/caarlos0/env/v10/.mailmap
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos A Becker <caarlos0@gmail.com>
|
||||
Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos A Becker <caarlos0@users.noreply.github.com>
|
||||
Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
|
||||
Carlos Alexandro Becker <caarlos0@users.noreply.github.com> Carlos Becker <caarlos0@gmail.com>
|
||||
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
|
||||
actions-user <actions@github.com> github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
|
21
vendor/github.com/caarlos0/env/v10/LICENSE.md
generated
vendored
Normal file
21
vendor/github.com/caarlos0/env/v10/LICENSE.md
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-2022 Carlos Alexandro Becker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
37
vendor/github.com/caarlos0/env/v10/Makefile
generated
vendored
Normal file
37
vendor/github.com/caarlos0/env/v10/Makefile
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
SOURCE_FILES?=./...
|
||||
TEST_PATTERN?=.
|
||||
|
||||
export GO111MODULE := on
|
||||
|
||||
setup:
|
||||
go mod tidy
|
||||
.PHONY: setup
|
||||
|
||||
build:
|
||||
go build
|
||||
.PHONY: build
|
||||
|
||||
test:
|
||||
go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
|
||||
.PHONY: test
|
||||
|
||||
cover: test
|
||||
go tool cover -html=coverage.txt
|
||||
.PHONY: cover
|
||||
|
||||
fmt:
|
||||
gofumpt -w -l .
|
||||
.PHONY: fmt
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
.PHONY: lint
|
||||
|
||||
ci: build test
|
||||
.PHONY: ci
|
||||
|
||||
card:
|
||||
wget -O card.png -c "https://og.caarlos0.dev/**env**: parse envs to structs.png?theme=light&md=1&fontSize=100px&images=https://github.com/caarlos0.png"
|
||||
.PHONY: card
|
||||
|
||||
.DEFAULT_GOAL := ci
|
610
vendor/github.com/caarlos0/env/v10/README.md
generated
vendored
Normal file
610
vendor/github.com/caarlos0/env/v10/README.md
generated
vendored
Normal file
@ -0,0 +1,610 @@
|
||||
# env
|
||||
|
||||
[![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
|
||||
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
|
||||
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v10)
|
||||
|
||||
A simple and zero-dependencies library to parse environment variables into
|
||||
`struct`s.
|
||||
|
||||
## Example
|
||||
|
||||
Get the module with:
|
||||
|
||||
```sh
|
||||
go get github.com/caarlos0/env/v10
|
||||
```
|
||||
|
||||
The usage looks like this:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Home string `env:"HOME"`
|
||||
Port int `env:"PORT" envDefault:"3000"`
|
||||
Password string `env:"PASSWORD,unset"`
|
||||
IsProduction bool `env:"PRODUCTION"`
|
||||
Duration time.Duration `env:"DURATION"`
|
||||
Hosts []string `env:"HOSTS" envSeparator:":"`
|
||||
TempFolder string `env:"TEMP_FOLDER,expand" envDefault:"${HOME}/tmp"`
|
||||
StringInts map[string]int `env:"MAP_STRING_INT"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
You can run it like this:
|
||||
|
||||
```sh
|
||||
$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s MAP_STRING_INT=k1:1,k2:2 go run main.go
|
||||
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s StringInts:map[k1:1 k2:2]}
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> **This is important!**
|
||||
|
||||
- _Unexported fields_ are **ignored**
|
||||
|
||||
## Supported types and defaults
|
||||
|
||||
Out of the box all built-in types are supported, plus a few others that
|
||||
are commonly used.
|
||||
|
||||
Complete list:
|
||||
|
||||
- `string`
|
||||
- `bool`
|
||||
- `int`
|
||||
- `int8`
|
||||
- `int16`
|
||||
- `int32`
|
||||
- `int64`
|
||||
- `uint`
|
||||
- `uint8`
|
||||
- `uint16`
|
||||
- `uint32`
|
||||
- `uint64`
|
||||
- `float32`
|
||||
- `float64`
|
||||
- `time.Duration`
|
||||
- `encoding.TextUnmarshaler`
|
||||
- `url.URL`
|
||||
|
||||
Pointers, slices and slices of pointers, and maps of those types are also
|
||||
supported.
|
||||
|
||||
You can also use/define a [custom parser func](#custom-parser-funcs) for any
|
||||
other type you want.
|
||||
|
||||
You can also use custom keys and values in your maps, as long as you provide a
|
||||
parser function for them.
|
||||
|
||||
If you set the `envDefault` tag for something, this value will be used in the
|
||||
case of absence of it in the environment.
|
||||
|
||||
By default, slice types will split the environment value on `,`; you can change
|
||||
this behavior by setting the `envSeparator` tag. For map types, the default
|
||||
separator between key and value is `:` and `,` for key-value pairs.
|
||||
The behavior can be changed by setting the `envKeyValSeparator` and
|
||||
`envSeparator` tags accordingly.
|
||||
|
||||
## Custom Parser Funcs
|
||||
|
||||
If you have a type that is not supported out of the box by the lib, you are able
|
||||
to use (or define) and pass custom parsers (and their associated `reflect.Type`)
|
||||
to the `env.ParseWithOptions()` function.
|
||||
|
||||
In addition to accepting a struct pointer (same as `Parse()`), this function
|
||||
also accepts a `Options{}`, and you can set your custom parsers in the `FuncMap`
|
||||
field.
|
||||
|
||||
If you add a custom parser for, say `Foo`, it will also be used to parse
|
||||
`*Foo` and `[]Foo` types.
|
||||
|
||||
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v10)
|
||||
for more info.
|
||||
|
||||
### A note about `TextUnmarshaler` and `time.Time`
|
||||
|
||||
Env supports by default anything that implements the `TextUnmarshaler` interface.
|
||||
That includes things like `time.Time` for example.
|
||||
The upside is that depending on the format you need, you don't need to change
|
||||
anything.
|
||||
The downside is that if you do need time in another format, you'll need to
|
||||
create your own type.
|
||||
|
||||
Its fairly straightforward:
|
||||
|
||||
```go
|
||||
type MyTime time.Time
|
||||
|
||||
func (t *MyTime) UnmarshalText(text []byte) error {
|
||||
tt, err := time.Parse("2006-01-02", string(text))
|
||||
*t = MyTime(tt)
|
||||
return err
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SomeTime MyTime `env:"SOME_TIME"`
|
||||
}
|
||||
```
|
||||
|
||||
And then you can parse `Config` with `env.Parse`.
|
||||
|
||||
## Required fields
|
||||
|
||||
The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to
|
||||
ensure that some environment variable is set. In the example above, an error is
|
||||
returned if the `config` struct is changed to:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,required"`
|
||||
}
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> Note that being set is not the same as being empty.
|
||||
> If the variable is set, but empty, the field will have its type's default
|
||||
> value.
|
||||
> This also means that custom parser funcs will not be invoked.
|
||||
|
||||
## Expand vars
|
||||
|
||||
If you set the `expand` option, environment variables (either in `${var}` or
|
||||
`$var` format) in the string will be replaced according with the actual value
|
||||
of the variable. For example:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,expand"`
|
||||
}
|
||||
```
|
||||
|
||||
This also works with `envDefault`:
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Host string `env:"HOST" envDefault:"localhost"`
|
||||
Port int `env:"PORT" envDefault:"3000"`
|
||||
Address string `env:"ADDRESS,expand" envDefault:"$HOST:${PORT}"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
results in this:
|
||||
|
||||
```sh
|
||||
$ PORT=8080 go run main.go
|
||||
{Host:localhost Port:8080 Address:localhost:8080}
|
||||
```
|
||||
|
||||
## Not Empty fields
|
||||
|
||||
While `required` demands the environment variable to be set, it doesn't check
|
||||
its value. If you want to make sure the environment is set and not empty, you
|
||||
need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,notEmpty"`
|
||||
}
|
||||
```
|
||||
|
||||
## Unset environment variable after reading it
|
||||
|
||||
The `env` tag option `unset` (e.g., `env:"tagKey,unset"`) can be added
|
||||
to ensure that some environment variable is unset after reading it.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
type config struct {
|
||||
SecretKey string `env:"SECRET_KEY,unset"`
|
||||
}
|
||||
```
|
||||
|
||||
## From file
|
||||
|
||||
The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added
|
||||
in order to indicate that the value of the variable shall be loaded from a
|
||||
file.
|
||||
The path of that file is given by the environment variable associated with it:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
Secret string `env:"SECRET,file"`
|
||||
Password string `env:"PASSWORD,file" envDefault:"/tmp/password"`
|
||||
Certificate string `env:"CERTIFICATE,file,expand" envDefault:"${CERTIFICATE_FILE}"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := config{}
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
```sh
|
||||
$ echo qwerty > /tmp/secret
|
||||
$ echo dvorak > /tmp/password
|
||||
$ echo coleman > /tmp/certificate
|
||||
|
||||
$ SECRET=/tmp/secret \
|
||||
CERTIFICATE_FILE=/tmp/certificate \
|
||||
go run main.go
|
||||
{Secret:qwerty Password:dvorak Certificate:coleman}
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### Use field names as environment variables by default
|
||||
|
||||
If you don't want to set the `env` tag on every field, you can use the
|
||||
`UseFieldNameByDefault` option.
|
||||
|
||||
It will use the field name as environment variable name.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string // will use $USERNAME
|
||||
Password string // will use $PASSWORD
|
||||
UserFullName string // will use $USER_FULL_NAME
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := env.Options{UseFieldNameByDefault: true}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.ParseWithOptions(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
### Environment
|
||||
|
||||
By setting the `Options.Environment` map you can tell `Parse` to add those
|
||||
`keys` and `values` as `env` vars before parsing is done.
|
||||
These `envs` are stored in the map and never actually set by `os.Setenv`.
|
||||
This option effectively makes `env` ignore the OS environment variables: only
|
||||
the ones provided in the option are used.
|
||||
|
||||
This can make your testing scenarios a bit more clean and easy to handle.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := env.Options{Environment: map[string]string{
|
||||
"PASSWORD": "MY_PASSWORD",
|
||||
}}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.ParseWithOptions(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
### Changing default tag name
|
||||
|
||||
You can change what tag name to use for setting the env vars by setting the
|
||||
`Options.TagName` variable.
|
||||
|
||||
For example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Password string `json:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := env.Options{TagName: "json"}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.ParseWithOptions(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
### Prefixes
|
||||
|
||||
You can prefix sub-structs env tags, as well as a whole `env.Parse` call.
|
||||
|
||||
Here's an example flexing it a bit:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Home string `env:"HOME"`
|
||||
}
|
||||
|
||||
type ComplexConfig struct {
|
||||
Foo Config `envPrefix:"FOO_"`
|
||||
Clean Config
|
||||
Bar Config `envPrefix:"BAR_"`
|
||||
Blah string `env:"BLAH"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &ComplexConfig{}
|
||||
opts := env.Options{
|
||||
Prefix: "T_",
|
||||
Environment: map[string]string{
|
||||
"T_FOO_HOME": "/foo",
|
||||
"T_BAR_HOME": "/bar",
|
||||
"T_BLAH": "blahhh",
|
||||
"T_HOME": "/clean",
|
||||
},
|
||||
}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.ParseWithOptions(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
### On set hooks
|
||||
|
||||
You might want to listen to value sets and, for example, log something or do
|
||||
some other kind of logic.
|
||||
You can do this by passing a `OnSet` option:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := env.Options{
|
||||
OnSet: func(tag string, value interface{}, isDefault bool) {
|
||||
fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault)
|
||||
},
|
||||
}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.ParseWithOptions(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
## Making all fields to required
|
||||
|
||||
You can make all fields that don't have a default value be required by setting
|
||||
the `RequiredIfNoDef: true` in the `Options`.
|
||||
|
||||
For example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := &Config{}
|
||||
opts := env.Options{RequiredIfNoDef: true}
|
||||
|
||||
// Load env vars.
|
||||
if err := env.ParseWithOptions(cfg, opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Print the loaded data.
|
||||
fmt.Printf("%+v\n", cfg)
|
||||
}
|
||||
```
|
||||
|
||||
## Defaults from code
|
||||
|
||||
You may define default value also in code, by initialising the config data
|
||||
before it's filled by `env.Parse`.
|
||||
Default values defined as struct tags will overwrite existing values during
|
||||
Parse.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg := Config{
|
||||
Username: "test",
|
||||
Password: "123456",
|
||||
}
|
||||
|
||||
if err := env.Parse(&cfg); err != nil {
|
||||
fmt.Println("failed:", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
|
||||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
You can handle the errors the library throws like so:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/caarlos0/env/v10"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Username string `env:"USERNAME" envDefault:"admin"`
|
||||
Password string `env:"PASSWORD"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
var cfg Config
|
||||
err := env.Parse(&cfg)
|
||||
if e, ok := err.(*env.AggregateError); ok {
|
||||
for _, er := range e.Errors {
|
||||
switch v := er.(type) {
|
||||
case env.ParseError:
|
||||
// handle it
|
||||
case env.NotStructPtrError:
|
||||
// handle it
|
||||
case env.NoParserError:
|
||||
// handle it
|
||||
case env.NoSupportedTagOptionError:
|
||||
// handle it
|
||||
default:
|
||||
fmt.Printf("Unknown error type %v", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
|
||||
}
|
||||
```
|
||||
|
||||
> **Info**
|
||||
>
|
||||
> If you want to check if an specific error is in the chain, you can also use
|
||||
> `errors.Is()`.
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)
|
614
vendor/github.com/caarlos0/env/v10/env.go
generated
vendored
Normal file
614
vendor/github.com/caarlos0/env/v10/env.go
generated
vendored
Normal file
@ -0,0 +1,614 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// nolint: gochecknoglobals
|
||||
var (
|
||||
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
|
||||
reflect.Bool: func(v string) (interface{}, error) {
|
||||
return strconv.ParseBool(v)
|
||||
},
|
||||
reflect.String: func(v string) (interface{}, error) {
|
||||
return v, nil
|
||||
},
|
||||
reflect.Int: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 32)
|
||||
return int(i), err
|
||||
},
|
||||
reflect.Int16: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 16)
|
||||
return int16(i), err
|
||||
},
|
||||
reflect.Int32: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 32)
|
||||
return int32(i), err
|
||||
},
|
||||
reflect.Int64: func(v string) (interface{}, error) {
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
},
|
||||
reflect.Int8: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(v, 10, 8)
|
||||
return int8(i), err
|
||||
},
|
||||
reflect.Uint: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 32)
|
||||
return uint(i), err
|
||||
},
|
||||
reflect.Uint16: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 16)
|
||||
return uint16(i), err
|
||||
},
|
||||
reflect.Uint32: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 32)
|
||||
return uint32(i), err
|
||||
},
|
||||
reflect.Uint64: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 64)
|
||||
return i, err
|
||||
},
|
||||
reflect.Uint8: func(v string) (interface{}, error) {
|
||||
i, err := strconv.ParseUint(v, 10, 8)
|
||||
return uint8(i), err
|
||||
},
|
||||
reflect.Float64: func(v string) (interface{}, error) {
|
||||
return strconv.ParseFloat(v, 64)
|
||||
},
|
||||
reflect.Float32: func(v string) (interface{}, error) {
|
||||
f, err := strconv.ParseFloat(v, 32)
|
||||
return float32(f), err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func defaultTypeParsers() map[reflect.Type]ParserFunc {
|
||||
return map[reflect.Type]ParserFunc{
|
||||
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
|
||||
u, err := url.Parse(v)
|
||||
if err != nil {
|
||||
return nil, newParseValueError("unable to parse URL", err)
|
||||
}
|
||||
return *u, nil
|
||||
},
|
||||
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
|
||||
s, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return nil, newParseValueError("unable to parse duration", err)
|
||||
}
|
||||
return s, err
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ParserFunc defines the signature of a function that can be used within `CustomParsers`.
|
||||
type ParserFunc func(v string) (interface{}, error)
|
||||
|
||||
// OnSetFn is a hook that can be run when a value is set.
|
||||
type OnSetFn func(tag string, value interface{}, isDefault bool)
|
||||
|
||||
// processFieldFn is a function which takes all information about a field and processes it.
|
||||
type processFieldFn func(refField reflect.Value, refTypeField reflect.StructField, opts Options, fieldParams FieldParams) error
|
||||
|
||||
// Options for the parser.
|
||||
type Options struct {
|
||||
// Environment keys and values that will be accessible for the service.
|
||||
Environment map[string]string
|
||||
|
||||
// TagName specifies another tagname to use rather than the default env.
|
||||
TagName string
|
||||
|
||||
// RequiredIfNoDef automatically sets all env as required if they do not
|
||||
// declare 'envDefault'.
|
||||
RequiredIfNoDef bool
|
||||
|
||||
// OnSet allows to run a function when a value is set.
|
||||
OnSet OnSetFn
|
||||
|
||||
// Prefix define a prefix for each key.
|
||||
Prefix string
|
||||
|
||||
// UseFieldNameByDefault defines whether or not env should use the field
|
||||
// name by default if the `env` key is missing.
|
||||
UseFieldNameByDefault bool
|
||||
|
||||
// Custom parse functions for different types.
|
||||
FuncMap map[reflect.Type]ParserFunc
|
||||
|
||||
// Used internally. maps the env variable key to its resolved string value. (for env var expansion)
|
||||
rawEnvVars map[string]string
|
||||
}
|
||||
|
||||
func (opts *Options) getRawEnv(s string) string {
|
||||
val := opts.rawEnvVars[s]
|
||||
if val == "" {
|
||||
return opts.Environment[s]
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func defaultOptions() Options {
|
||||
return Options{
|
||||
TagName: "env",
|
||||
Environment: toMap(os.Environ()),
|
||||
FuncMap: defaultTypeParsers(),
|
||||
rawEnvVars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func customOptions(opt Options) Options {
|
||||
defOpts := defaultOptions()
|
||||
if opt.TagName == "" {
|
||||
opt.TagName = defOpts.TagName
|
||||
}
|
||||
if opt.Environment == nil {
|
||||
opt.Environment = defOpts.Environment
|
||||
}
|
||||
if opt.FuncMap == nil {
|
||||
opt.FuncMap = map[reflect.Type]ParserFunc{}
|
||||
}
|
||||
if opt.rawEnvVars == nil {
|
||||
opt.rawEnvVars = defOpts.rawEnvVars
|
||||
}
|
||||
for k, v := range defOpts.FuncMap {
|
||||
if _, exists := opt.FuncMap[k]; !exists {
|
||||
opt.FuncMap[k] = v
|
||||
}
|
||||
}
|
||||
return opt
|
||||
}
|
||||
|
||||
func optionsWithEnvPrefix(field reflect.StructField, opts Options) Options {
|
||||
return Options{
|
||||
Environment: opts.Environment,
|
||||
TagName: opts.TagName,
|
||||
RequiredIfNoDef: opts.RequiredIfNoDef,
|
||||
OnSet: opts.OnSet,
|
||||
Prefix: opts.Prefix + field.Tag.Get("envPrefix"),
|
||||
UseFieldNameByDefault: opts.UseFieldNameByDefault,
|
||||
FuncMap: opts.FuncMap,
|
||||
rawEnvVars: opts.rawEnvVars,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses a struct containing `env` tags and loads its values from
|
||||
// environment variables.
|
||||
func Parse(v interface{}) error {
|
||||
return parseInternal(v, setField, defaultOptions())
|
||||
}
|
||||
|
||||
// ParseWithOptions parses a struct containing `env` tags and loads its values from
|
||||
// environment variables.
|
||||
func ParseWithOptions(v interface{}, opts Options) error {
|
||||
return parseInternal(v, setField, customOptions(opts))
|
||||
}
|
||||
|
||||
// GetFieldParams parses a struct containing `env` tags and returns information about
|
||||
// tags it found.
|
||||
func GetFieldParams(v interface{}) ([]FieldParams, error) {
|
||||
return GetFieldParamsWithOptions(v, defaultOptions())
|
||||
}
|
||||
|
||||
// GetFieldParamsWithOptions parses a struct containing `env` tags and returns information about
|
||||
// tags it found.
|
||||
func GetFieldParamsWithOptions(v interface{}, opts Options) ([]FieldParams, error) {
|
||||
var result []FieldParams
|
||||
err := parseInternal(
|
||||
v,
|
||||
func(_ reflect.Value, _ reflect.StructField, _ Options, fieldParams FieldParams) error {
|
||||
if fieldParams.OwnKey != "" {
|
||||
result = append(result, fieldParams)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
customOptions(opts),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseInternal(v interface{}, processField processFieldFn, opts Options) error {
|
||||
ptrRef := reflect.ValueOf(v)
|
||||
if ptrRef.Kind() != reflect.Ptr {
|
||||
return newAggregateError(NotStructPtrError{})
|
||||
}
|
||||
ref := ptrRef.Elem()
|
||||
if ref.Kind() != reflect.Struct {
|
||||
return newAggregateError(NotStructPtrError{})
|
||||
}
|
||||
|
||||
return doParse(ref, processField, opts)
|
||||
}
|
||||
|
||||
func doParse(ref reflect.Value, processField processFieldFn, opts Options) error {
|
||||
refType := ref.Type()
|
||||
|
||||
var agrErr AggregateError
|
||||
|
||||
for i := 0; i < refType.NumField(); i++ {
|
||||
refField := ref.Field(i)
|
||||
refTypeField := refType.Field(i)
|
||||
|
||||
if err := doParseField(refField, refTypeField, processField, opts); err != nil {
|
||||
if val, ok := err.(AggregateError); ok {
|
||||
agrErr.Errors = append(agrErr.Errors, val.Errors...)
|
||||
} else {
|
||||
agrErr.Errors = append(agrErr.Errors, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(agrErr.Errors) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return agrErr
|
||||
}
|
||||
|
||||
func doParseField(refField reflect.Value, refTypeField reflect.StructField, processField processFieldFn, opts Options) error {
|
||||
if !refField.CanSet() {
|
||||
return nil
|
||||
}
|
||||
if reflect.Ptr == refField.Kind() && !refField.IsNil() {
|
||||
return parseInternal(refField.Interface(), processField, optionsWithEnvPrefix(refTypeField, opts))
|
||||
}
|
||||
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
|
||||
return parseInternal(refField.Addr().Interface(), processField, optionsWithEnvPrefix(refTypeField, opts))
|
||||
}
|
||||
|
||||
params, err := parseFieldParams(refTypeField, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := processField(refField, refTypeField, opts, params); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reflect.Struct == refField.Kind() {
|
||||
return doParse(refField, processField, optionsWithEnvPrefix(refTypeField, opts))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setField(refField reflect.Value, refTypeField reflect.StructField, opts Options, fieldParams FieldParams) error {
|
||||
value, err := get(fieldParams, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
return set(refField, refTypeField, value, opts.FuncMap)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const underscore rune = '_'
|
||||
|
||||
func toEnvName(input string) string {
|
||||
var output []rune
|
||||
for i, c := range input {
|
||||
if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c {
|
||||
output = append(output, underscore)
|
||||
}
|
||||
output = append(output, unicode.ToUpper(c))
|
||||
}
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// FieldParams contains information about parsed field tags.
|
||||
type FieldParams struct {
|
||||
OwnKey string
|
||||
Key string
|
||||
DefaultValue string
|
||||
HasDefaultValue bool
|
||||
Required bool
|
||||
LoadFile bool
|
||||
Unset bool
|
||||
NotEmpty bool
|
||||
Expand bool
|
||||
}
|
||||
|
||||
func parseFieldParams(field reflect.StructField, opts Options) (FieldParams, error) {
|
||||
ownKey, tags := parseKeyForOption(field.Tag.Get(opts.TagName))
|
||||
if ownKey == "" && opts.UseFieldNameByDefault {
|
||||
ownKey = toEnvName(field.Name)
|
||||
}
|
||||
|
||||
defaultValue, hasDefaultValue := field.Tag.Lookup("envDefault")
|
||||
|
||||
result := FieldParams{
|
||||
OwnKey: ownKey,
|
||||
Key: opts.Prefix + ownKey,
|
||||
Required: opts.RequiredIfNoDef,
|
||||
DefaultValue: defaultValue,
|
||||
HasDefaultValue: hasDefaultValue,
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
switch tag {
|
||||
case "":
|
||||
continue
|
||||
case "file":
|
||||
result.LoadFile = true
|
||||
case "required":
|
||||
result.Required = true
|
||||
case "unset":
|
||||
result.Unset = true
|
||||
case "notEmpty":
|
||||
result.NotEmpty = true
|
||||
case "expand":
|
||||
result.Expand = true
|
||||
default:
|
||||
return FieldParams{}, newNoSupportedTagOptionError(tag)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func get(fieldParams FieldParams, opts Options) (val string, err error) {
|
||||
var exists, isDefault bool
|
||||
|
||||
val, exists, isDefault = getOr(fieldParams.Key, fieldParams.DefaultValue, fieldParams.HasDefaultValue, opts.Environment)
|
||||
|
||||
if fieldParams.Expand {
|
||||
val = os.Expand(val, opts.getRawEnv)
|
||||
}
|
||||
|
||||
opts.rawEnvVars[fieldParams.OwnKey] = val
|
||||
|
||||
if fieldParams.Unset {
|
||||
defer os.Unsetenv(fieldParams.Key)
|
||||
}
|
||||
|
||||
if fieldParams.Required && !exists && len(fieldParams.OwnKey) > 0 {
|
||||
return "", newEnvVarIsNotSet(fieldParams.Key)
|
||||
}
|
||||
|
||||
if fieldParams.NotEmpty && val == "" {
|
||||
return "", newEmptyEnvVarError(fieldParams.Key)
|
||||
}
|
||||
|
||||
if fieldParams.LoadFile && val != "" {
|
||||
filename := val
|
||||
val, err = getFromFile(filename)
|
||||
if err != nil {
|
||||
return "", newLoadFileContentError(filename, fieldParams.Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.OnSet != nil {
|
||||
if fieldParams.OwnKey != "" {
|
||||
opts.OnSet(fieldParams.Key, val, isDefault)
|
||||
}
|
||||
}
|
||||
return val, err
|
||||
}
|
||||
|
||||
// split the env tag's key into the expected key and desired option, if any.
|
||||
func parseKeyForOption(key string) (string, []string) {
|
||||
opts := strings.Split(key, ",")
|
||||
return opts[0], opts[1:]
|
||||
}
|
||||
|
||||
func getFromFile(filename string) (value string, err error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func getOr(key, defaultValue string, defExists bool, envs map[string]string) (val string, exists bool, isDefault bool) {
|
||||
value, exists := envs[key]
|
||||
switch {
|
||||
case (!exists || key == "") && defExists:
|
||||
return defaultValue, true, true
|
||||
case exists && value == "" && defExists:
|
||||
return defaultValue, true, true
|
||||
case !exists:
|
||||
return "", false, false
|
||||
}
|
||||
|
||||
return value, true, false
|
||||
}
|
||||
|
||||
func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error {
|
||||
if tm := asTextUnmarshaler(field); tm != nil {
|
||||
if err := tm.UnmarshalText([]byte(value)); err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
typee := sf.Type
|
||||
fieldee := field
|
||||
if typee.Kind() == reflect.Ptr {
|
||||
typee = typee.Elem()
|
||||
fieldee = field.Elem()
|
||||
}
|
||||
|
||||
parserFunc, ok := funcMap[typee]
|
||||
if ok {
|
||||
val, err := parserFunc(value)
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
fieldee.Set(reflect.ValueOf(val))
|
||||
return nil
|
||||
}
|
||||
|
||||
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
|
||||
if ok {
|
||||
val, err := parserFunc(value)
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
fieldee.Set(reflect.ValueOf(val).Convert(typee))
|
||||
return nil
|
||||
}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.Slice:
|
||||
return handleSlice(field, value, sf, funcMap)
|
||||
case reflect.Map:
|
||||
return handleMap(field, value, sf, funcMap)
|
||||
}
|
||||
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
|
||||
func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
|
||||
separator := sf.Tag.Get("envSeparator")
|
||||
if separator == "" {
|
||||
separator = ","
|
||||
}
|
||||
parts := strings.Split(value, separator)
|
||||
|
||||
typee := sf.Type.Elem()
|
||||
if typee.Kind() == reflect.Ptr {
|
||||
typee = typee.Elem()
|
||||
}
|
||||
|
||||
if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok {
|
||||
return parseTextUnmarshalers(field, parts, sf)
|
||||
}
|
||||
|
||||
parserFunc, ok := funcMap[typee]
|
||||
if !ok {
|
||||
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
|
||||
if !ok {
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
}
|
||||
|
||||
result := reflect.MakeSlice(sf.Type, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
r, err := parserFunc(part)
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
v := reflect.ValueOf(r).Convert(typee)
|
||||
if sf.Type.Elem().Kind() == reflect.Ptr {
|
||||
v = reflect.New(typee)
|
||||
v.Elem().Set(reflect.ValueOf(r).Convert(typee))
|
||||
}
|
||||
result = reflect.Append(result, v)
|
||||
}
|
||||
field.Set(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
|
||||
keyType := sf.Type.Key()
|
||||
keyParserFunc, ok := funcMap[keyType]
|
||||
if !ok {
|
||||
keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()]
|
||||
if !ok {
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
}
|
||||
|
||||
elemType := sf.Type.Elem()
|
||||
elemParserFunc, ok := funcMap[elemType]
|
||||
if !ok {
|
||||
elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()]
|
||||
if !ok {
|
||||
return newNoParserError(sf)
|
||||
}
|
||||
}
|
||||
|
||||
separator := sf.Tag.Get("envSeparator")
|
||||
if separator == "" {
|
||||
separator = ","
|
||||
}
|
||||
|
||||
keyValSeparator := sf.Tag.Get("envKeyValSeparator")
|
||||
if keyValSeparator == "" {
|
||||
keyValSeparator = ":"
|
||||
}
|
||||
|
||||
result := reflect.MakeMap(sf.Type)
|
||||
for _, part := range strings.Split(value, separator) {
|
||||
pairs := strings.Split(part, keyValSeparator)
|
||||
if len(pairs) != 2 {
|
||||
return newParseError(sf, fmt.Errorf(`%q should be in "key%svalue" format`, part, keyValSeparator))
|
||||
}
|
||||
|
||||
key, err := keyParserFunc(pairs[0])
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
elem, err := elemParserFunc(pairs[1])
|
||||
if err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
|
||||
result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType))
|
||||
}
|
||||
|
||||
field.Set(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
|
||||
if reflect.Ptr == field.Kind() {
|
||||
if field.IsNil() {
|
||||
field.Set(reflect.New(field.Type().Elem()))
|
||||
}
|
||||
} else if field.CanAddr() {
|
||||
field = field.Addr()
|
||||
}
|
||||
|
||||
tm, ok := field.Interface().(encoding.TextUnmarshaler)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return tm
|
||||
}
|
||||
|
||||
func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error {
|
||||
s := len(data)
|
||||
elemType := field.Type().Elem()
|
||||
slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s)
|
||||
for i, v := range data {
|
||||
sv := slice.Index(i)
|
||||
kind := sv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
sv = reflect.New(elemType.Elem())
|
||||
} else {
|
||||
sv = sv.Addr()
|
||||
}
|
||||
tm := sv.Interface().(encoding.TextUnmarshaler)
|
||||
if err := tm.UnmarshalText([]byte(v)); err != nil {
|
||||
return newParseError(sf, err)
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
slice.Index(i).Set(sv)
|
||||
}
|
||||
}
|
||||
|
||||
field.Set(slice)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToMap Converts list of env vars as provided by os.Environ() to map you
|
||||
// can use as Options.Environment field
|
||||
func ToMap(env []string) map[string]string {
|
||||
return toMap(env)
|
||||
}
|
16
vendor/github.com/caarlos0/env/v10/env_tomap.go
generated
vendored
Normal file
16
vendor/github.com/caarlos0/env/v10/env_tomap.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
//go:build !windows
|
||||
|
||||
package env
|
||||
|
||||
import "strings"
|
||||
|
||||
func toMap(env []string) map[string]string {
|
||||
r := map[string]string{}
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
if len(p) == 2 {
|
||||
r[p[0]] = p[1]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
29
vendor/github.com/caarlos0/env/v10/env_tomap_windows.go
generated
vendored
Normal file
29
vendor/github.com/caarlos0/env/v10/env_tomap_windows.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
//go:build windows
|
||||
|
||||
package env
|
||||
|
||||
import "strings"
|
||||
|
||||
func toMap(env []string) map[string]string {
|
||||
r := map[string]string{}
|
||||
for _, e := range env {
|
||||
p := strings.SplitN(e, "=", 2)
|
||||
|
||||
// On Windows, environment variables can start with '='. If so, Split at next character.
|
||||
// See env_windows.go in the Go source: https://github.com/golang/go/blob/master/src/syscall/env_windows.go#L58
|
||||
prefixEqualSign := false
|
||||
if len(e) > 0 && e[0] == '=' {
|
||||
e = e[1:]
|
||||
prefixEqualSign = true
|
||||
}
|
||||
p = strings.SplitN(e, "=", 2)
|
||||
if prefixEqualSign {
|
||||
p[0] = "=" + p[0]
|
||||
}
|
||||
|
||||
if len(p) == 2 {
|
||||
r[p[0]] = p[1]
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
164
vendor/github.com/caarlos0/env/v10/error.go
generated
vendored
Normal file
164
vendor/github.com/caarlos0/env/v10/error.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// An aggregated error wrapper to combine gathered errors. This allows either to display all errors or convert them individually
|
||||
// List of the available errors
|
||||
// ParseError
|
||||
// NotStructPtrError
|
||||
// NoParserError
|
||||
// NoSupportedTagOptionError
|
||||
// EnvVarIsNotSetError
|
||||
// EmptyEnvVarError
|
||||
// LoadFileContentError
|
||||
// ParseValueError
|
||||
type AggregateError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func newAggregateError(initErr error) error {
|
||||
return AggregateError{
|
||||
[]error{
|
||||
initErr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (e AggregateError) Error() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("env:")
|
||||
|
||||
for _, err := range e.Errors {
|
||||
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
|
||||
}
|
||||
|
||||
return strings.TrimRight(sb.String(), ";")
|
||||
}
|
||||
|
||||
// Is conforms with errors.Is.
|
||||
func (e AggregateError) Is(err error) bool {
|
||||
for _, ie := range e.Errors {
|
||||
if reflect.TypeOf(ie) == reflect.TypeOf(err) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// The error occurs when it's impossible to convert the value for given type.
|
||||
type ParseError struct {
|
||||
Name string
|
||||
Type reflect.Type
|
||||
Err error
|
||||
}
|
||||
|
||||
func newParseError(sf reflect.StructField, err error) error {
|
||||
return ParseError{sf.Name, sf.Type, err}
|
||||
}
|
||||
|
||||
func (e ParseError) Error() string {
|
||||
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err)
|
||||
}
|
||||
|
||||
// The error occurs when pass something that is not a pointer to a Struct to Parse
|
||||
type NotStructPtrError struct{}
|
||||
|
||||
func (e NotStructPtrError) Error() string {
|
||||
return "expected a pointer to a Struct"
|
||||
}
|
||||
|
||||
// This error occurs when there is no parser provided for given type
|
||||
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
|
||||
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
|
||||
type NoParserError struct {
|
||||
Name string
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
func newNoParserError(sf reflect.StructField) error {
|
||||
return NoParserError{sf.Name, sf.Type}
|
||||
}
|
||||
|
||||
func (e NoParserError) Error() string {
|
||||
return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type)
|
||||
}
|
||||
|
||||
// This error occurs when the given tag is not supported
|
||||
// In-built supported tags: "", "file", "required", "unset", "notEmpty", "expand", "envDefault", "envSeparator"
|
||||
// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name
|
||||
type NoSupportedTagOptionError struct {
|
||||
Tag string
|
||||
}
|
||||
|
||||
func newNoSupportedTagOptionError(tag string) error {
|
||||
return NoSupportedTagOptionError{tag}
|
||||
}
|
||||
|
||||
func (e NoSupportedTagOptionError) Error() string {
|
||||
return fmt.Sprintf("tag option %q not supported", e.Tag)
|
||||
}
|
||||
|
||||
// This error occurs when the required variable is not set
|
||||
// Read about required fields: https://github.com/caarlos0/env#required-fields
|
||||
type EnvVarIsNotSetError struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func newEnvVarIsNotSet(key string) error {
|
||||
return EnvVarIsNotSetError{key}
|
||||
}
|
||||
|
||||
func (e EnvVarIsNotSetError) Error() string {
|
||||
return fmt.Sprintf(`required environment variable %q is not set`, e.Key)
|
||||
}
|
||||
|
||||
// This error occurs when the variable which must be not empty is existing but has an empty value
|
||||
// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields
|
||||
type EmptyEnvVarError struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func newEmptyEnvVarError(key string) error {
|
||||
return EmptyEnvVarError{key}
|
||||
}
|
||||
|
||||
func (e EmptyEnvVarError) Error() string {
|
||||
return fmt.Sprintf("environment variable %q should not be empty", e.Key)
|
||||
}
|
||||
|
||||
// This error occurs when it's impossible to load the value from the file
|
||||
// Read about From file feature: https://github.com/caarlos0/env#from-file
|
||||
type LoadFileContentError struct {
|
||||
Filename string
|
||||
Key string
|
||||
Err error
|
||||
}
|
||||
|
||||
func newLoadFileContentError(filename, key string, err error) error {
|
||||
return LoadFileContentError{filename, key, err}
|
||||
}
|
||||
|
||||
func (e LoadFileContentError) Error() string {
|
||||
return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err)
|
||||
}
|
||||
|
||||
// This error occurs when it's impossible to convert value using given parser
|
||||
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
|
||||
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
|
||||
type ParseValueError struct {
|
||||
Msg string
|
||||
Err error
|
||||
}
|
||||
|
||||
func newParseValueError(message string, err error) error {
|
||||
return ParseValueError{message, err}
|
||||
}
|
||||
|
||||
func (e ParseValueError) Error() string {
|
||||
return fmt.Sprintf("%s: %v", e.Msg, e.Err)
|
||||
}
|
21
vendor/github.com/lmittmann/tint/LICENSE
generated
vendored
Normal file
21
vendor/github.com/lmittmann/tint/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 lmittmann
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
88
vendor/github.com/lmittmann/tint/README.md
generated
vendored
Normal file
88
vendor/github.com/lmittmann/tint/README.md
generated
vendored
Normal file
@ -0,0 +1,88 @@
|
||||
# `tint`: 🌈 **slog.Handler** that writes tinted logs
|
||||
|
||||
[![Go Reference](https://pkg.go.dev/badge/github.com/lmittmann/tint.svg)](https://pkg.go.dev/github.com/lmittmann/tint#section-documentation)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/lmittmann/tint)](https://goreportcard.com/report/github.com/lmittmann/tint)
|
||||
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/lmittmann/tint/assets/3458786/3d42f8d5-8bdf-40db-a16a-1939c88689cb">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/lmittmann/tint/assets/3458786/3d42f8d5-8bdf-40db-a16a-1939c88689cb">
|
||||
<img src="https://github.com/lmittmann/tint/assets/3458786/3d42f8d5-8bdf-40db-a16a-1939c88689cb">
|
||||
</picture>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
Package `tint` implements a zero-dependency [`slog.Handler`](https://pkg.go.dev/log/slog#Handler)
|
||||
that writes tinted (colorized) logs. Its output format is inspired by the `zerolog.ConsoleWriter` and
|
||||
[`slog.TextHandler`](https://pkg.go.dev/log/slog#TextHandler).
|
||||
|
||||
The output format can be customized using [`Options`](https://pkg.go.dev/github.com/lmittmann/tint#Options)
|
||||
which is a drop-in replacement for [`slog.HandlerOptions`](https://pkg.go.dev/log/slog#HandlerOptions).
|
||||
|
||||
```
|
||||
go get github.com/lmittmann/tint
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
w := os.Stderr
|
||||
|
||||
// create a new logger
|
||||
logger := slog.New(tint.NewHandler(w, nil))
|
||||
|
||||
// set global logger with custom options
|
||||
slog.SetDefault(slog.New(
|
||||
tint.NewHandler(w, &tint.Options{
|
||||
Level: slog.LevelDebug,
|
||||
TimeFormat: time.Kitchen,
|
||||
}),
|
||||
))
|
||||
```
|
||||
|
||||
### Customize Attributes
|
||||
|
||||
`ReplaceAttr` can be used to alter or drop attributes. If set, it is called on
|
||||
each non-group attribute before it is logged. See [`slog.HandlerOptions`](https://pkg.go.dev/log/slog#HandlerOptions)
|
||||
for details.
|
||||
|
||||
```go
|
||||
// create a new logger that doesn't write the time
|
||||
w := os.Stderr
|
||||
logger := slog.New(
|
||||
tint.NewHandler(w, &tint.Options{
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.TimeKey && len(groups) == 0 {
|
||||
return slog.Attr{}
|
||||
}
|
||||
return a
|
||||
},
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
### Automatically Enable Colors
|
||||
|
||||
Colors are enabled by default and can be disabled using the `Options.NoColor`
|
||||
attribute. To automatically enable colors based on the terminal capabilities,
|
||||
use e.g. the [`go-isatty`](https://github.com/mattn/go-isatty) package.
|
||||
|
||||
```go
|
||||
w := os.Stderr
|
||||
logger := slog.New(
|
||||
tint.NewHandler(w, &tint.Options{
|
||||
NoColor: !isatty.IsTerminal(w.Fd()),
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
### Windows Support
|
||||
|
||||
Color support on Windows can be added by using e.g. the
|
||||
[`go-colorable`](https://github.com/mattn/go-colorable) package.
|
||||
|
||||
```go
|
||||
w := os.Stderr
|
||||
logger := slog.New(
|
||||
tint.NewHandler(colorable.NewColorable(w), nil),
|
||||
)
|
||||
```
|
46
vendor/github.com/lmittmann/tint/buffer.go
generated
vendored
Normal file
46
vendor/github.com/lmittmann/tint/buffer.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package tint
|
||||
|
||||
import "sync"
|
||||
|
||||
type buffer []byte
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() any {
|
||||
b := make(buffer, 0, 1024)
|
||||
return (*buffer)(&b)
|
||||
},
|
||||
}
|
||||
|
||||
func newBuffer() *buffer {
|
||||
return bufPool.Get().(*buffer)
|
||||
}
|
||||
|
||||
func (b *buffer) Free() {
|
||||
// To reduce peak allocation, return only smaller buffers to the pool.
|
||||
const maxBufferSize = 16 << 10
|
||||
if cap(*b) <= maxBufferSize {
|
||||
*b = (*b)[:0]
|
||||
bufPool.Put(b)
|
||||
}
|
||||
}
|
||||
func (b *buffer) Write(bytes []byte) (int, error) {
|
||||
*b = append(*b, bytes...)
|
||||
return len(bytes), nil
|
||||
}
|
||||
|
||||
func (b *buffer) WriteByte(char byte) error {
|
||||
*b = append(*b, char)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buffer) WriteString(str string) (int, error) {
|
||||
*b = append(*b, str...)
|
||||
return len(str), nil
|
||||
}
|
||||
|
||||
func (b *buffer) WriteStringIf(ok bool, str string) (int, error) {
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
return b.WriteString(str)
|
||||
}
|
438
vendor/github.com/lmittmann/tint/handler.go
generated
vendored
Normal file
438
vendor/github.com/lmittmann/tint/handler.go
generated
vendored
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
Package tint implements a zero-dependency [slog.Handler] that writes tinted
|
||||
(colorized) logs. The output format is inspired by the [zerolog.ConsoleWriter]
|
||||
and [slog.TextHandler].
|
||||
|
||||
The output format can be customized using [Options], which is a drop-in
|
||||
replacement for [slog.HandlerOptions].
|
||||
|
||||
# Customize Attributes
|
||||
|
||||
Options.ReplaceAttr can be used to alter or drop attributes. If set, it is
|
||||
called on each non-group attribute before it is logged.
|
||||
See [slog.HandlerOptions] for details.
|
||||
|
||||
w := os.Stderr
|
||||
logger := slog.New(
|
||||
tint.NewHandler(w, &tint.Options{
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.TimeKey && len(groups) == 0 {
|
||||
return slog.Attr{}
|
||||
}
|
||||
return a
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
# Automatically Enable Colors
|
||||
|
||||
Colors are enabled by default and can be disabled using the Options.NoColor
|
||||
attribute. To automatically enable colors based on the terminal capabilities,
|
||||
use e.g. the [go-isatty] package.
|
||||
|
||||
w := os.Stderr
|
||||
logger := slog.New(
|
||||
tint.NewHandler(w, &tint.Options{
|
||||
NoColor: !isatty.IsTerminal(w.Fd()),
|
||||
}),
|
||||
)
|
||||
|
||||
# Windows Support
|
||||
|
||||
Color support on Windows can be added by using e.g. the [go-colorable] package.
|
||||
|
||||
w := os.Stderr
|
||||
logger := slog.New(
|
||||
tint.NewHandler(colorable.NewColorable(w), nil),
|
||||
)
|
||||
|
||||
[zerolog.ConsoleWriter]: https://pkg.go.dev/github.com/rs/zerolog#ConsoleWriter
|
||||
[go-isatty]: https://pkg.go.dev/github.com/mattn/go-isatty
|
||||
[go-colorable]: https://pkg.go.dev/github.com/mattn/go-colorable
|
||||
*/
|
||||
package tint
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// ANSI modes
|
||||
const (
|
||||
ansiReset = "\033[0m"
|
||||
ansiFaint = "\033[2m"
|
||||
ansiResetFaint = "\033[22m"
|
||||
ansiBrightRed = "\033[91m"
|
||||
ansiBrightGreen = "\033[92m"
|
||||
ansiBrightYellow = "\033[93m"
|
||||
ansiBrightRedFaint = "\033[91;2m"
|
||||
)
|
||||
|
||||
const errKey = "err"
|
||||
|
||||
var (
|
||||
defaultLevel = slog.LevelInfo
|
||||
defaultTimeFormat = time.StampMilli
|
||||
)
|
||||
|
||||
// Options for a slog.Handler that writes tinted logs. A zero Options consists
|
||||
// entirely of default values.
|
||||
//
|
||||
// Options can be used as a drop-in replacement for [slog.HandlerOptions].
|
||||
type Options struct {
|
||||
// Enable source code location (Default: false)
|
||||
AddSource bool
|
||||
|
||||
// Minimum level to log (Default: slog.LevelInfo)
|
||||
Level slog.Leveler
|
||||
|
||||
// ReplaceAttr is called to rewrite each non-group attribute before it is logged.
|
||||
// See https://pkg.go.dev/log/slog#HandlerOptions for details.
|
||||
ReplaceAttr func(groups []string, attr slog.Attr) slog.Attr
|
||||
|
||||
// Time format (Default: time.StampMilli)
|
||||
TimeFormat string
|
||||
|
||||
// Disable color (Default: false)
|
||||
NoColor bool
|
||||
}
|
||||
|
||||
// NewHandler creates a [slog.Handler] that writes tinted logs to Writer w,
|
||||
// using the default options. If opts is nil, the default options are used.
|
||||
func NewHandler(w io.Writer, opts *Options) slog.Handler {
|
||||
h := &handler{
|
||||
w: w,
|
||||
level: defaultLevel,
|
||||
timeFormat: defaultTimeFormat,
|
||||
}
|
||||
if opts == nil {
|
||||
return h
|
||||
}
|
||||
|
||||
h.addSource = opts.AddSource
|
||||
if opts.Level != nil {
|
||||
h.level = opts.Level
|
||||
}
|
||||
h.replaceAttr = opts.ReplaceAttr
|
||||
if opts.TimeFormat != "" {
|
||||
h.timeFormat = opts.TimeFormat
|
||||
}
|
||||
h.noColor = opts.NoColor
|
||||
return h
|
||||
}
|
||||
|
||||
// handler implements a [slog.Handler].
|
||||
type handler struct {
|
||||
attrsPrefix string
|
||||
groupPrefix string
|
||||
groups []string
|
||||
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
|
||||
addSource bool
|
||||
level slog.Leveler
|
||||
replaceAttr func([]string, slog.Attr) slog.Attr
|
||||
timeFormat string
|
||||
noColor bool
|
||||
}
|
||||
|
||||
func (h *handler) clone() *handler {
|
||||
return &handler{
|
||||
attrsPrefix: h.attrsPrefix,
|
||||
groupPrefix: h.groupPrefix,
|
||||
groups: h.groups,
|
||||
w: h.w,
|
||||
addSource: h.addSource,
|
||||
level: h.level,
|
||||
replaceAttr: h.replaceAttr,
|
||||
timeFormat: h.timeFormat,
|
||||
noColor: h.noColor,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return level >= h.level.Level()
|
||||
}
|
||||
|
||||
func (h *handler) Handle(_ context.Context, r slog.Record) error {
|
||||
// get a buffer from the sync pool
|
||||
buf := newBuffer()
|
||||
defer buf.Free()
|
||||
|
||||
rep := h.replaceAttr
|
||||
|
||||
// write time
|
||||
if !r.Time.IsZero() {
|
||||
val := r.Time.Round(0) // strip monotonic to match Attr behavior
|
||||
if rep == nil {
|
||||
h.appendTime(buf, r.Time)
|
||||
buf.WriteByte(' ')
|
||||
} else if a := rep(nil /* groups */, slog.Time(slog.TimeKey, val)); a.Key != "" {
|
||||
if a.Value.Kind() == slog.KindTime {
|
||||
h.appendTime(buf, a.Value.Time())
|
||||
} else {
|
||||
h.appendValue(buf, a.Value, false)
|
||||
}
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
|
||||
// write level
|
||||
if rep == nil {
|
||||
h.appendLevel(buf, r.Level)
|
||||
buf.WriteByte(' ')
|
||||
} else if a := rep(nil /* groups */, slog.Any(slog.LevelKey, r.Level)); a.Key != "" {
|
||||
h.appendValue(buf, a.Value, false)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
// write source
|
||||
if h.addSource {
|
||||
fs := runtime.CallersFrames([]uintptr{r.PC})
|
||||
f, _ := fs.Next()
|
||||
if f.File != "" {
|
||||
src := &slog.Source{
|
||||
Function: f.Function,
|
||||
File: f.File,
|
||||
Line: f.Line,
|
||||
}
|
||||
|
||||
if rep == nil {
|
||||
h.appendSource(buf, src)
|
||||
buf.WriteByte(' ')
|
||||
} else if a := rep(nil /* groups */, slog.Any(slog.SourceKey, src)); a.Key != "" {
|
||||
h.appendValue(buf, a.Value, false)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write message
|
||||
if rep == nil {
|
||||
buf.WriteString(r.Message)
|
||||
buf.WriteByte(' ')
|
||||
} else if a := rep(nil /* groups */, slog.String(slog.MessageKey, r.Message)); a.Key != "" {
|
||||
h.appendValue(buf, a.Value, false)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
|
||||
// write handler attributes
|
||||
if len(h.attrsPrefix) > 0 {
|
||||
buf.WriteString(h.attrsPrefix)
|
||||
}
|
||||
|
||||
// write attributes
|
||||
r.Attrs(func(attr slog.Attr) bool {
|
||||
h.appendAttr(buf, attr, h.groupPrefix, h.groups)
|
||||
return true
|
||||
})
|
||||
|
||||
if len(*buf) == 0 {
|
||||
return nil
|
||||
}
|
||||
(*buf)[len(*buf)-1] = '\n' // replace last space with newline
|
||||
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
_, err := h.w.Write(*buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *handler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
if len(attrs) == 0 {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
|
||||
buf := newBuffer()
|
||||
defer buf.Free()
|
||||
|
||||
// write attributes to buffer
|
||||
for _, attr := range attrs {
|
||||
h.appendAttr(buf, attr, h.groupPrefix, h.groups)
|
||||
}
|
||||
h2.attrsPrefix = h.attrsPrefix + string(*buf)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *handler) WithGroup(name string) slog.Handler {
|
||||
if name == "" {
|
||||
return h
|
||||
}
|
||||
h2 := h.clone()
|
||||
h2.groupPrefix += name + "."
|
||||
h2.groups = append(h2.groups, name)
|
||||
return h2
|
||||
}
|
||||
|
||||
func (h *handler) appendTime(buf *buffer, t time.Time) {
|
||||
buf.WriteStringIf(!h.noColor, ansiFaint)
|
||||
*buf = t.AppendFormat(*buf, h.timeFormat)
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
}
|
||||
|
||||
func (h *handler) appendLevel(buf *buffer, level slog.Level) {
|
||||
switch {
|
||||
case level < slog.LevelInfo:
|
||||
buf.WriteString("DBG")
|
||||
appendLevelDelta(buf, level-slog.LevelDebug)
|
||||
case level < slog.LevelWarn:
|
||||
buf.WriteStringIf(!h.noColor, ansiBrightGreen)
|
||||
buf.WriteString("INF")
|
||||
appendLevelDelta(buf, level-slog.LevelInfo)
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
case level < slog.LevelError:
|
||||
buf.WriteStringIf(!h.noColor, ansiBrightYellow)
|
||||
buf.WriteString("WRN")
|
||||
appendLevelDelta(buf, level-slog.LevelWarn)
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
default:
|
||||
buf.WriteStringIf(!h.noColor, ansiBrightRed)
|
||||
buf.WriteString("ERR")
|
||||
appendLevelDelta(buf, level-slog.LevelError)
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
}
|
||||
}
|
||||
|
||||
func appendLevelDelta(buf *buffer, delta slog.Level) {
|
||||
if delta == 0 {
|
||||
return
|
||||
} else if delta > 0 {
|
||||
buf.WriteByte('+')
|
||||
}
|
||||
*buf = strconv.AppendInt(*buf, int64(delta), 10)
|
||||
}
|
||||
|
||||
func (h *handler) appendSource(buf *buffer, src *slog.Source) {
|
||||
dir, file := filepath.Split(src.File)
|
||||
|
||||
buf.WriteStringIf(!h.noColor, ansiFaint)
|
||||
buf.WriteString(filepath.Join(filepath.Base(dir), file))
|
||||
buf.WriteByte(':')
|
||||
buf.WriteString(strconv.Itoa(src.Line))
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
}
|
||||
|
||||
func (h *handler) appendAttr(buf *buffer, attr slog.Attr, groupsPrefix string, groups []string) {
|
||||
attr.Value = attr.Value.Resolve()
|
||||
if rep := h.replaceAttr; rep != nil && attr.Value.Kind() != slog.KindGroup {
|
||||
attr = rep(groups, attr)
|
||||
attr.Value = attr.Value.Resolve()
|
||||
}
|
||||
|
||||
if attr.Equal(slog.Attr{}) {
|
||||
return
|
||||
}
|
||||
|
||||
if attr.Value.Kind() == slog.KindGroup {
|
||||
if attr.Key != "" {
|
||||
groupsPrefix += attr.Key + "."
|
||||
groups = append(groups, attr.Key)
|
||||
}
|
||||
for _, groupAttr := range attr.Value.Group() {
|
||||
h.appendAttr(buf, groupAttr, groupsPrefix, groups)
|
||||
}
|
||||
} else if err, ok := attr.Value.Any().(tintError); ok {
|
||||
// append tintError
|
||||
h.appendTintError(buf, err, groupsPrefix)
|
||||
buf.WriteByte(' ')
|
||||
} else {
|
||||
h.appendKey(buf, attr.Key, groupsPrefix)
|
||||
h.appendValue(buf, attr.Value, true)
|
||||
buf.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) appendKey(buf *buffer, key, groups string) {
|
||||
buf.WriteStringIf(!h.noColor, ansiFaint)
|
||||
appendString(buf, groups+key, true)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
}
|
||||
|
||||
func (h *handler) appendValue(buf *buffer, v slog.Value, quote bool) {
|
||||
switch v.Kind() {
|
||||
case slog.KindString:
|
||||
appendString(buf, v.String(), quote)
|
||||
case slog.KindInt64:
|
||||
*buf = strconv.AppendInt(*buf, v.Int64(), 10)
|
||||
case slog.KindUint64:
|
||||
*buf = strconv.AppendUint(*buf, v.Uint64(), 10)
|
||||
case slog.KindFloat64:
|
||||
*buf = strconv.AppendFloat(*buf, v.Float64(), 'g', -1, 64)
|
||||
case slog.KindBool:
|
||||
*buf = strconv.AppendBool(*buf, v.Bool())
|
||||
case slog.KindDuration:
|
||||
appendString(buf, v.Duration().String(), quote)
|
||||
case slog.KindTime:
|
||||
appendString(buf, v.Time().String(), quote)
|
||||
case slog.KindAny:
|
||||
switch cv := v.Any().(type) {
|
||||
case slog.Level:
|
||||
h.appendLevel(buf, cv)
|
||||
case encoding.TextMarshaler:
|
||||
data, err := cv.MarshalText()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
appendString(buf, string(data), quote)
|
||||
case *slog.Source:
|
||||
h.appendSource(buf, cv)
|
||||
default:
|
||||
appendString(buf, fmt.Sprintf("%+v", v.Any()), quote)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) appendTintError(buf *buffer, err error, groupsPrefix string) {
|
||||
buf.WriteStringIf(!h.noColor, ansiBrightRedFaint)
|
||||
appendString(buf, groupsPrefix+errKey, true)
|
||||
buf.WriteByte('=')
|
||||
buf.WriteStringIf(!h.noColor, ansiResetFaint)
|
||||
appendString(buf, err.Error(), true)
|
||||
buf.WriteStringIf(!h.noColor, ansiReset)
|
||||
}
|
||||
|
||||
func appendString(buf *buffer, s string, quote bool) {
|
||||
if quote && needsQuoting(s) {
|
||||
*buf = strconv.AppendQuote(*buf, s)
|
||||
} else {
|
||||
buf.WriteString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func needsQuoting(s string) bool {
|
||||
if len(s) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, r := range s {
|
||||
if unicode.IsSpace(r) || r == '"' || r == '=' || !unicode.IsPrint(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type tintError struct{ error }
|
||||
|
||||
// Err returns a tinted (colorized) [slog.Attr] that will be written in red color
|
||||
// by the [tint.Handler]. When used with any other [slog.Handler], it behaves as
|
||||
//
|
||||
// slog.Any("err", err)
|
||||
func Err(err error) slog.Attr {
|
||||
if err != nil {
|
||||
err = tintError{err}
|
||||
}
|
||||
return slog.Any(errKey, err)
|
||||
}
|
24
vendor/github.com/matryer/is/.gitignore
generated
vendored
Normal file
24
vendor/github.com/matryer/is/.gitignore
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
40
vendor/github.com/matryer/is/.travis.yml
generated
vendored
Normal file
40
vendor/github.com/matryer/is/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
language: go
|
||||
|
||||
sudo: required
|
||||
|
||||
go:
|
||||
# "1.x" always refers to the latest Go version, inc. the patch release.
|
||||
# e.g. "1.x" is 1.13 until 1.13.1 is available.
|
||||
- 1.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
- tip
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- os: windows
|
||||
go: tip
|
||||
exclude:
|
||||
# OSX 1.6.4 is not present in travis.
|
||||
# https://github.com/travis-ci/travis-ci/issues/10309
|
||||
- go: 1.6.x
|
||||
os: osx
|
||||
|
||||
install:
|
||||
- go get -d -v ./...
|
||||
|
||||
script:
|
||||
- go build -v ./...
|
||||
- go test ./...
|
21
vendor/github.com/matryer/is/LICENSE
generated
vendored
Normal file
21
vendor/github.com/matryer/is/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2018 Mat Ryer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
43
vendor/github.com/matryer/is/README.md
generated
vendored
Normal file
43
vendor/github.com/matryer/is/README.md
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
# is [![GoDoc](https://godoc.org/github.com/matryer/is?status.png)](http://godoc.org/github.com/matryer/is) [![Go Report Card](https://goreportcard.com/badge/github.com/matryer/is)](https://goreportcard.com/report/github.com/matryer/is) [![Build Status](https://travis-ci.org/matryer/is.svg?branch=master)](https://travis-ci.org/matryer/is)
|
||||
Professional lightweight testing mini-framework for Go.
|
||||
|
||||
* Easy to write and read
|
||||
* [Beautifully simple API](https://pkg.go.dev/github.com/matryer/is) with everything you need: `is.Equal`, `is.True`, `is.NoErr`, and `is.Fail`
|
||||
* Use comments to add descriptions (which show up when tests fail)
|
||||
|
||||
Failures are very easy to read:
|
||||
|
||||
![Examples of failures](https://github.com/matryer/is/raw/master/misc/delicious-failures.png)
|
||||
|
||||
### Usage
|
||||
|
||||
The following code shows a range of useful ways you can use
|
||||
the helper methods:
|
||||
|
||||
```go
|
||||
func Test(t *testing.T) {
|
||||
|
||||
is := is.New(t)
|
||||
|
||||
signedin, err := isSignedIn(ctx)
|
||||
is.NoErr(err) // isSignedIn error
|
||||
is.Equal(signedin, true) // must be signed in
|
||||
|
||||
body := readBody(r)
|
||||
is.True(strings.Contains(body, "Hi there"))
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Color
|
||||
|
||||
To turn off the colors, run `go test` with the `-nocolor` flag,
|
||||
or with the env var [`NO_COLOR` (with any value)](https://no-color.org).
|
||||
|
||||
```
|
||||
go test -nocolor
|
||||
```
|
||||
|
||||
```
|
||||
NO_COLOR=1 go test
|
||||
```
|
64
vendor/github.com/matryer/is/is-1.7.go
generated
vendored
Normal file
64
vendor/github.com/matryer/is/is-1.7.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
// +build go1.7
|
||||
|
||||
package is
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Helper marks the calling function as a test helper function.
|
||||
// When printing file and line information, that function will be skipped.
|
||||
//
|
||||
// Available with Go 1.7 and later.
|
||||
func (is *I) Helper() {
|
||||
is.helpers[callerName(1)] = struct{}{}
|
||||
}
|
||||
|
||||
// callerName gives the function name (qualified with a package path)
|
||||
// for the caller after skip frames (where 0 means the current function).
|
||||
func callerName(skip int) string {
|
||||
// Make room for the skip PC.
|
||||
var pc [1]uintptr
|
||||
n := runtime.Callers(skip+2, pc[:]) // skip + runtime.Callers + callerName
|
||||
if n == 0 {
|
||||
panic("is: zero callers found")
|
||||
}
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
frame, _ := frames.Next()
|
||||
return frame.Function
|
||||
}
|
||||
|
||||
// The maximum number of stack frames to go through when skipping helper functions for
|
||||
// the purpose of decorating log messages.
|
||||
const maxStackLen = 50
|
||||
|
||||
var reIsSourceFile = regexp.MustCompile(`is(-1.7)?\.go$`)
|
||||
|
||||
func (is *I) callerinfo() (path string, line int, ok bool) {
|
||||
var pc [maxStackLen]uintptr
|
||||
// Skip two extra frames to account for this function
|
||||
// and runtime.Callers itself.
|
||||
n := runtime.Callers(2, pc[:])
|
||||
if n == 0 {
|
||||
panic("is: zero callers found")
|
||||
}
|
||||
frames := runtime.CallersFrames(pc[:n])
|
||||
var firstFrame, frame runtime.Frame
|
||||
for more := true; more; {
|
||||
frame, more = frames.Next()
|
||||
if reIsSourceFile.MatchString(frame.File) {
|
||||
continue
|
||||
}
|
||||
if firstFrame.PC == 0 {
|
||||
firstFrame = frame
|
||||
}
|
||||
if _, ok := is.helpers[frame.Function]; ok {
|
||||
// Frame is inside a helper function.
|
||||
continue
|
||||
}
|
||||
return frame.File, frame.Line, true
|
||||
}
|
||||
// If no "non-helper" frame is found, the first non is frame is returned.
|
||||
return firstFrame.File, firstFrame.Line, true
|
||||
}
|
23
vendor/github.com/matryer/is/is-before-1.7.go
generated
vendored
Normal file
23
vendor/github.com/matryer/is/is-before-1.7.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
// +build !go1.7
|
||||
|
||||
package is
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var reIsSourceFile = regexp.MustCompile("is(-before-1.7)?\\.go$")
|
||||
|
||||
func (is *I) callerinfo() (path string, line int, ok bool) {
|
||||
for i := 0; ; i++ {
|
||||
_, path, line, ok = runtime.Caller(i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if reIsSourceFile.MatchString(path) {
|
||||
continue
|
||||
}
|
||||
return path, line, true
|
||||
}
|
||||
}
|
403
vendor/github.com/matryer/is/is.go
generated
vendored
Normal file
403
vendor/github.com/matryer/is/is.go
generated
vendored
Normal file
@ -0,0 +1,403 @@
|
||||
// Package is provides a lightweight extension to the
|
||||
// standard library's testing capabilities.
|
||||
//
|
||||
// Comments on the assertion lines are used to add
|
||||
// a description.
|
||||
//
|
||||
// The following failing test:
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// a, b := 1, 2
|
||||
// is.Equal(a, b) // expect to be the same
|
||||
// }
|
||||
//
|
||||
// Will output:
|
||||
//
|
||||
// your_test.go:123: 1 != 2 // expect to be the same
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// The following code shows a range of useful ways you can use
|
||||
// the helper methods:
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// // always start tests with this
|
||||
// is := is.New(t)
|
||||
//
|
||||
// signedin, err := isSignedIn(ctx)
|
||||
// is.NoErr(err) // isSignedIn error
|
||||
// is.Equal(signedin, true) // must be signed in
|
||||
//
|
||||
// body := readBody(r)
|
||||
// is.True(strings.Contains(body, "Hi there"))
|
||||
// }
|
||||
package is
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// T reports when failures occur.
|
||||
// testing.T implements this interface.
|
||||
type T interface {
|
||||
// Fail indicates that the test has failed but
|
||||
// allowed execution to continue.
|
||||
// Fail is called in relaxed mode (via NewRelaxed).
|
||||
Fail()
|
||||
// FailNow indicates that the test has failed and
|
||||
// aborts the test.
|
||||
// FailNow is called in strict mode (via New).
|
||||
FailNow()
|
||||
}
|
||||
|
||||
// I is the test helper harness.
|
||||
type I struct {
|
||||
t T
|
||||
fail func()
|
||||
out io.Writer
|
||||
colorful bool
|
||||
|
||||
helpers map[string]struct{} // functions to be skipped when writing file/line info
|
||||
}
|
||||
|
||||
var noColorFlag bool
|
||||
|
||||
func init() {
|
||||
var envNoColor bool
|
||||
|
||||
// prefer https://no-color.org (with any value)
|
||||
if _, ok := os.LookupEnv("NO_COLOR"); ok {
|
||||
envNoColor = true
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv("IS_NO_COLOR"); ok {
|
||||
if b, err := strconv.ParseBool(v); err == nil {
|
||||
envNoColor = b
|
||||
}
|
||||
}
|
||||
|
||||
flag.BoolVar(&noColorFlag, "nocolor", envNoColor, "turns off colors")
|
||||
}
|
||||
|
||||
// New makes a new testing helper using the specified
|
||||
// T through which failures will be reported.
|
||||
// In strict mode, failures call T.FailNow causing the test
|
||||
// to be aborted. See NewRelaxed for alternative behavior.
|
||||
func New(t T) *I {
|
||||
return &I{t, t.FailNow, os.Stdout, !noColorFlag, map[string]struct{}{}}
|
||||
}
|
||||
|
||||
// NewRelaxed makes a new testing helper using the specified
|
||||
// T through which failures will be reported.
|
||||
// In relaxed mode, failures call T.Fail allowing
|
||||
// multiple failures per test.
|
||||
func NewRelaxed(t T) *I {
|
||||
return &I{t, t.Fail, os.Stdout, !noColorFlag, map[string]struct{}{}}
|
||||
}
|
||||
|
||||
func (is *I) log(args ...interface{}) {
|
||||
s := is.decorate(fmt.Sprint(args...))
|
||||
fmt.Fprintf(is.out, s)
|
||||
is.fail()
|
||||
}
|
||||
|
||||
func (is *I) logf(format string, args ...interface{}) {
|
||||
is.log(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Fail immediately fails the test.
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// is.Fail() // TODO: write this test
|
||||
// }
|
||||
//
|
||||
// In relaxed mode, execution will continue after a call to
|
||||
// Fail, but that test will still fail.
|
||||
func (is *I) Fail() {
|
||||
is.log("failed")
|
||||
}
|
||||
|
||||
// True asserts that the expression is true. The expression
|
||||
// code itself will be reported if the assertion fails.
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// val := method()
|
||||
// is.True(val != nil) // val should never be nil
|
||||
// }
|
||||
//
|
||||
// Will output:
|
||||
//
|
||||
// your_test.go:123: not true: val != nil
|
||||
func (is *I) True(expression bool) {
|
||||
if !expression {
|
||||
is.log("not true: $ARGS")
|
||||
}
|
||||
}
|
||||
|
||||
// Equal asserts that a and b are equal.
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// a := greet("Mat")
|
||||
// is.Equal(a, "Hi Mat") // greeting
|
||||
// }
|
||||
//
|
||||
// Will output:
|
||||
//
|
||||
// your_test.go:123: Hey Mat != Hi Mat // greeting
|
||||
func (is *I) Equal(a, b interface{}) {
|
||||
if areEqual(a, b) {
|
||||
return
|
||||
}
|
||||
if isNil(a) || isNil(b) {
|
||||
is.logf("%s != %s", is.valWithType(a), is.valWithType(b))
|
||||
} else if reflect.ValueOf(a).Type() == reflect.ValueOf(b).Type() {
|
||||
is.logf("%v != %v", a, b)
|
||||
} else {
|
||||
is.logf("%s != %s", is.valWithType(a), is.valWithType(b))
|
||||
}
|
||||
}
|
||||
|
||||
// New is a method wrapper around the New function.
|
||||
// It allows you to write subtests using a similar
|
||||
// pattern:
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// t.Run("sub", func(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// // TODO: test
|
||||
// })
|
||||
// }
|
||||
func (is *I) New(t T) *I {
|
||||
return New(t)
|
||||
}
|
||||
|
||||
// NewRelaxed is a method wrapper around the NewRelaxed
|
||||
// method. It allows you to write subtests using a similar
|
||||
// pattern:
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.NewRelaxed(t)
|
||||
// t.Run("sub", func(t *testing.T) {
|
||||
// is := is.NewRelaxed(t)
|
||||
// // TODO: test
|
||||
// })
|
||||
// }
|
||||
func (is *I) NewRelaxed(t T) *I {
|
||||
return NewRelaxed(t)
|
||||
}
|
||||
|
||||
func (is *I) valWithType(v interface{}) string {
|
||||
if isNil(v) {
|
||||
return "<nil>"
|
||||
}
|
||||
if is.colorful {
|
||||
return fmt.Sprintf("%[1]s%[3]T(%[2]s%[3]v%[1]s)%[2]s", colorType, colorNormal, v)
|
||||
}
|
||||
return fmt.Sprintf("%[1]T(%[1]v)", v)
|
||||
}
|
||||
|
||||
// NoErr asserts that err is nil.
|
||||
//
|
||||
// func Test(t *testing.T) {
|
||||
// is := is.New(t)
|
||||
// val, err := getVal()
|
||||
// is.NoErr(err) // getVal error
|
||||
// is.True(len(val) > 10) // val cannot be short
|
||||
// }
|
||||
//
|
||||
// Will output:
|
||||
//
|
||||
// your_test.go:123: err: not found // getVal error
|
||||
func (is *I) NoErr(err error) {
|
||||
if err != nil {
|
||||
is.logf("err: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// isNil gets whether the object is nil or not.
|
||||
func isNil(object interface{}) bool {
|
||||
if object == nil {
|
||||
return true
|
||||
}
|
||||
value := reflect.ValueOf(object)
|
||||
kind := value.Kind()
|
||||
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// areEqual gets whether a equals b or not.
|
||||
func areEqual(a, b interface{}) bool {
|
||||
if isNil(a) && isNil(b) {
|
||||
return true
|
||||
}
|
||||
if isNil(a) || isNil(b) {
|
||||
return false
|
||||
}
|
||||
if reflect.DeepEqual(a, b) {
|
||||
return true
|
||||
}
|
||||
aValue := reflect.ValueOf(a)
|
||||
bValue := reflect.ValueOf(b)
|
||||
return aValue == bValue
|
||||
}
|
||||
|
||||
// loadComment gets the Go comment from the specified line
|
||||
// in the specified file.
|
||||
func loadComment(path string, line int) (string, bool) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer f.Close()
|
||||
s := bufio.NewScanner(f)
|
||||
i := 1
|
||||
for s.Scan() {
|
||||
if i != line {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
text := s.Text()
|
||||
commentI := strings.Index(text, "// ")
|
||||
if commentI == -1 {
|
||||
return "", false // no comment
|
||||
}
|
||||
text = text[commentI+2:]
|
||||
text = strings.TrimSpace(text)
|
||||
return text, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// loadArguments gets the arguments from the function call
|
||||
// on the specified line of the file.
|
||||
func loadArguments(path string, line int) (string, bool) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
defer f.Close()
|
||||
s := bufio.NewScanner(f)
|
||||
i := 1
|
||||
for s.Scan() {
|
||||
if i != line {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
text := s.Text()
|
||||
braceI := strings.Index(text, "(")
|
||||
if braceI == -1 {
|
||||
return "", false
|
||||
}
|
||||
text = text[braceI+1:]
|
||||
cs := bufio.NewScanner(strings.NewReader(text))
|
||||
cs.Split(bufio.ScanBytes)
|
||||
j := 0
|
||||
c := 1
|
||||
for cs.Scan() {
|
||||
switch cs.Text() {
|
||||
case ")":
|
||||
c--
|
||||
case "(":
|
||||
c++
|
||||
}
|
||||
if c == 0 {
|
||||
break
|
||||
}
|
||||
j++
|
||||
}
|
||||
text = text[:j]
|
||||
return text, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// decorate prefixes the string with the file and line of the call site
|
||||
// and inserts the final newline if needed and indentation tabs for formatting.
|
||||
// this function was copied from the testing framework and modified.
|
||||
func (is *I) decorate(s string) string {
|
||||
path, lineNumber, ok := is.callerinfo() // decorate + log + public function.
|
||||
file := filepath.Base(path)
|
||||
if ok {
|
||||
// Truncate file name at last file name separator.
|
||||
if index := strings.LastIndex(file, "/"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
}
|
||||
} else {
|
||||
file = "???"
|
||||
lineNumber = 1
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
// Every line is indented at least one tab.
|
||||
buf.WriteByte('\t')
|
||||
if is.colorful {
|
||||
buf.WriteString(colorFile)
|
||||
}
|
||||
fmt.Fprintf(buf, "%s:%d: ", file, lineNumber)
|
||||
if is.colorful {
|
||||
buf.WriteString(colorNormal)
|
||||
}
|
||||
|
||||
s = escapeFormatString(s)
|
||||
|
||||
lines := strings.Split(s, "\n")
|
||||
if l := len(lines); l > 1 && lines[l-1] == "" {
|
||||
lines = lines[:l-1]
|
||||
}
|
||||
for i, line := range lines {
|
||||
if i > 0 {
|
||||
// Second and subsequent lines are indented an extra tab.
|
||||
buf.WriteString("\n\t\t")
|
||||
}
|
||||
// expand arguments (if $ARGS is present)
|
||||
if strings.Contains(line, "$ARGS") {
|
||||
args, _ := loadArguments(path, lineNumber)
|
||||
line = strings.Replace(line, "$ARGS", args, -1)
|
||||
}
|
||||
buf.WriteString(line)
|
||||
}
|
||||
comment, ok := loadComment(path, lineNumber)
|
||||
if ok {
|
||||
if is.colorful {
|
||||
buf.WriteString(colorComment)
|
||||
}
|
||||
buf.WriteString(" // ")
|
||||
comment = escapeFormatString(comment)
|
||||
buf.WriteString(comment)
|
||||
if is.colorful {
|
||||
buf.WriteString(colorNormal)
|
||||
}
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// escapeFormatString escapes strings for use in formatted functions like Sprintf.
|
||||
func escapeFormatString(fmt string) string {
|
||||
return strings.Replace(fmt, "%", "%%", -1)
|
||||
}
|
||||
|
||||
const (
|
||||
colorNormal = "\u001b[39m"
|
||||
colorComment = "\u001b[31m"
|
||||
colorFile = "\u001b[90m"
|
||||
colorType = "\u001b[90m"
|
||||
)
|
16
vendor/github.com/valyala/fastrand/.travis.yml
generated
vendored
Normal file
16
vendor/github.com/valyala/fastrand/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7
|
||||
- 1.8
|
||||
|
||||
script:
|
||||
# build test for supported platforms
|
||||
- GOOS=linux go build
|
||||
- GOOS=darwin go build
|
||||
- GOOS=freebsd go build
|
||||
- GOARCH=386 go build
|
||||
|
||||
# run tests on a standard platform
|
||||
- go test -v ./...
|
||||
|
21
vendor/github.com/valyala/fastrand/LICENSE
generated
vendored
Normal file
21
vendor/github.com/valyala/fastrand/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Aliaksandr Valialkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
76
vendor/github.com/valyala/fastrand/README.md
generated
vendored
Normal file
76
vendor/github.com/valyala/fastrand/README.md
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
[![Build Status](https://travis-ci.org/valyala/fastrand.svg)](https://travis-ci.org/valyala/fastrand)
|
||||
[![GoDoc](https://godoc.org/github.com/valyala/fastrand?status.svg)](http://godoc.org/github.com/valyala/fastrand)
|
||||
[![Go Report](https://goreportcard.com/badge/github.com/valyala/fastrand)](https://goreportcard.com/report/github.com/valyala/fastrand)
|
||||
|
||||
|
||||
# fastrand
|
||||
|
||||
Fast pseudorandom number generator.
|
||||
|
||||
|
||||
# Features
|
||||
|
||||
- Optimized for speed.
|
||||
- Performance scales on multiple CPUs.
|
||||
|
||||
# How does it work?
|
||||
|
||||
It abuses [sync.Pool](https://golang.org/pkg/sync/#Pool) for maintaining
|
||||
"per-CPU" pseudorandom number generators.
|
||||
|
||||
TODO: firgure out how to use real per-CPU pseudorandom number generators.
|
||||
|
||||
|
||||
# Benchmark results
|
||||
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=1 go test -bench=. github.com/valyala/fastrand
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastrand
|
||||
BenchmarkUint32n 50000000 29.7 ns/op
|
||||
BenchmarkRNGUint32n 200000000 6.50 ns/op
|
||||
BenchmarkRNGUint32nWithLock 100000000 21.5 ns/op
|
||||
BenchmarkMathRandInt31n 50000000 31.8 ns/op
|
||||
BenchmarkMathRandRNGInt31n 100000000 17.9 ns/op
|
||||
BenchmarkMathRandRNGInt31nWithLock 50000000 30.2 ns/op
|
||||
PASS
|
||||
ok github.com/valyala/fastrand 10.634s
|
||||
```
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=2 go test -bench=. github.com/valyala/fastrand
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastrand
|
||||
BenchmarkUint32n-2 100000000 17.6 ns/op
|
||||
BenchmarkRNGUint32n-2 500000000 3.36 ns/op
|
||||
BenchmarkRNGUint32nWithLock-2 50000000 32.0 ns/op
|
||||
BenchmarkMathRandInt31n-2 20000000 51.2 ns/op
|
||||
BenchmarkMathRandRNGInt31n-2 100000000 11.0 ns/op
|
||||
BenchmarkMathRandRNGInt31nWithLock-2 20000000 91.0 ns/op
|
||||
PASS
|
||||
ok github.com/valyala/fastrand 9.543s
|
||||
```
|
||||
|
||||
```
|
||||
$ GOMAXPROCS=4 go test -bench=. github.com/valyala/fastrand
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/valyala/fastrand
|
||||
BenchmarkUint32n-4 100000000 14.2 ns/op
|
||||
BenchmarkRNGUint32n-4 500000000 3.30 ns/op
|
||||
BenchmarkRNGUint32nWithLock-4 20000000 88.7 ns/op
|
||||
BenchmarkMathRandInt31n-4 10000000 145 ns/op
|
||||
BenchmarkMathRandRNGInt31n-4 200000000 8.35 ns/op
|
||||
BenchmarkMathRandRNGInt31nWithLock-4 20000000 102 ns/op
|
||||
PASS
|
||||
ok github.com/valyala/fastrand 11.534s
|
||||
```
|
||||
|
||||
As you can see, [fastrand.Uint32n](https://godoc.org/github.com/valyala/fastrand#Uint32n)
|
||||
scales on multiple CPUs, while [rand.Int31n](https://golang.org/pkg/math/rand/#Int31n)
|
||||
doesn't scale. Their performance is comparable on `GOMAXPROCS=1`,
|
||||
but `fastrand.Uint32n` runs 3x faster than `rand.Int31n` on `GOMAXPROCS=2`
|
||||
and 10x faster than `rand.Int31n` on `GOMAXPROCS=4`.
|
79
vendor/github.com/valyala/fastrand/fastrand.go
generated
vendored
Normal file
79
vendor/github.com/valyala/fastrand/fastrand.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
// Package fastrand implements fast pesudorandom number generator
|
||||
// that should scale well on multi-CPU systems.
|
||||
//
|
||||
// Use crypto/rand instead of this package for generating
|
||||
// cryptographically secure random numbers.
|
||||
package fastrand
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Uint32 returns pseudorandom uint32.
|
||||
//
|
||||
// It is safe calling this function from concurrent goroutines.
|
||||
func Uint32() uint32 {
|
||||
v := rngPool.Get()
|
||||
if v == nil {
|
||||
v = &RNG{}
|
||||
}
|
||||
r := v.(*RNG)
|
||||
x := r.Uint32()
|
||||
rngPool.Put(r)
|
||||
return x
|
||||
}
|
||||
|
||||
var rngPool sync.Pool
|
||||
|
||||
// Uint32n returns pseudorandom uint32 in the range [0..maxN).
|
||||
//
|
||||
// It is safe calling this function from concurrent goroutines.
|
||||
func Uint32n(maxN uint32) uint32 {
|
||||
x := Uint32()
|
||||
// See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
return uint32((uint64(x) * uint64(maxN)) >> 32)
|
||||
}
|
||||
|
||||
// RNG is a pseudorandom number generator.
|
||||
//
|
||||
// It is unsafe to call RNG methods from concurrent goroutines.
|
||||
type RNG struct {
|
||||
x uint32
|
||||
}
|
||||
|
||||
// Uint32 returns pseudorandom uint32.
|
||||
//
|
||||
// It is unsafe to call this method from concurrent goroutines.
|
||||
func (r *RNG) Uint32() uint32 {
|
||||
for r.x == 0 {
|
||||
r.x = getRandomUint32()
|
||||
}
|
||||
|
||||
// See https://en.wikipedia.org/wiki/Xorshift
|
||||
x := r.x
|
||||
x ^= x << 13
|
||||
x ^= x >> 17
|
||||
x ^= x << 5
|
||||
r.x = x
|
||||
return x
|
||||
}
|
||||
|
||||
// Uint32n returns pseudorandom uint32 in the range [0..maxN).
|
||||
//
|
||||
// It is unsafe to call this method from concurrent goroutines.
|
||||
func (r *RNG) Uint32n(maxN uint32) uint32 {
|
||||
x := r.Uint32()
|
||||
// See http://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/
|
||||
return uint32((uint64(x) * uint64(maxN)) >> 32)
|
||||
}
|
||||
|
||||
// Seed sets the r state to n.
|
||||
func (r *RNG) Seed(n uint32) {
|
||||
r.x = n
|
||||
}
|
||||
|
||||
func getRandomUint32() uint32 {
|
||||
x := time.Now().UnixNano()
|
||||
return uint32((x >> 32) ^ x)
|
||||
}
|
21
vendor/github.com/valyala/histogram/LICENSE
generated
vendored
Normal file
21
vendor/github.com/valyala/histogram/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 Aliaksandr Valialkin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
9
vendor/github.com/valyala/histogram/README.md
generated
vendored
Normal file
9
vendor/github.com/valyala/histogram/README.md
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
[![GoDoc](https://godoc.org/github.com/valyala/histogram?status.svg)](http://godoc.org/github.com/valyala/histogram)
|
||||
[![Go Report](https://goreportcard.com/badge/github.com/valyala/histogram)](https://goreportcard.com/report/github.com/valyala/histogram)
|
||||
|
||||
|
||||
# histogram
|
||||
|
||||
Fast histograms for Go.
|
||||
|
||||
See [docs](https://godoc.org/github.com/valyala/histogram).
|
131
vendor/github.com/valyala/histogram/histogram.go
generated
vendored
Normal file
131
vendor/github.com/valyala/histogram/histogram.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// Package histogram provides building blocks for fast histograms.
|
||||
package histogram
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/valyala/fastrand"
|
||||
)
|
||||
|
||||
var (
|
||||
infNeg = math.Inf(-1)
|
||||
infPos = math.Inf(1)
|
||||
nan = math.NaN()
|
||||
)
|
||||
|
||||
// Fast is a fast histogram.
|
||||
//
|
||||
// It cannot be used from concurrently running goroutines without
|
||||
// external synchronization.
|
||||
type Fast struct {
|
||||
max float64
|
||||
min float64
|
||||
count uint64
|
||||
|
||||
a []float64
|
||||
tmp []float64
|
||||
rng fastrand.RNG
|
||||
}
|
||||
|
||||
// NewFast returns new fast histogram.
|
||||
func NewFast() *Fast {
|
||||
f := &Fast{}
|
||||
f.Reset()
|
||||
return f
|
||||
}
|
||||
|
||||
// Reset resets the histogram.
|
||||
func (f *Fast) Reset() {
|
||||
f.max = infNeg
|
||||
f.min = infPos
|
||||
f.count = 0
|
||||
if len(f.a) > 0 {
|
||||
f.a = f.a[:0]
|
||||
f.tmp = f.tmp[:0]
|
||||
} else {
|
||||
// Free up memory occupied by unused histogram.
|
||||
f.a = nil
|
||||
f.tmp = nil
|
||||
}
|
||||
// Reset rng state in order to get repeatable results
|
||||
// for the same sequence of values passed to Fast.Update.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/1612
|
||||
f.rng.Seed(1)
|
||||
}
|
||||
|
||||
// Update updates the f with v.
|
||||
func (f *Fast) Update(v float64) {
|
||||
if v > f.max {
|
||||
f.max = v
|
||||
}
|
||||
if v < f.min {
|
||||
f.min = v
|
||||
}
|
||||
|
||||
f.count++
|
||||
if len(f.a) < maxSamples {
|
||||
f.a = append(f.a, v)
|
||||
return
|
||||
}
|
||||
if n := int(f.rng.Uint32n(uint32(f.count))); n < len(f.a) {
|
||||
f.a[n] = v
|
||||
}
|
||||
}
|
||||
|
||||
const maxSamples = 1000
|
||||
|
||||
// Quantile returns the quantile value for the given phi.
|
||||
func (f *Fast) Quantile(phi float64) float64 {
|
||||
f.tmp = append(f.tmp[:0], f.a...)
|
||||
sort.Float64s(f.tmp)
|
||||
return f.quantile(phi)
|
||||
}
|
||||
|
||||
// Quantiles appends quantile values to dst for the given phis.
|
||||
func (f *Fast) Quantiles(dst, phis []float64) []float64 {
|
||||
f.tmp = append(f.tmp[:0], f.a...)
|
||||
sort.Float64s(f.tmp)
|
||||
for _, phi := range phis {
|
||||
q := f.quantile(phi)
|
||||
dst = append(dst, q)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func (f *Fast) quantile(phi float64) float64 {
|
||||
if len(f.tmp) == 0 || math.IsNaN(phi) {
|
||||
return nan
|
||||
}
|
||||
if phi <= 0 {
|
||||
return f.min
|
||||
}
|
||||
if phi >= 1 {
|
||||
return f.max
|
||||
}
|
||||
idx := uint(phi*float64(len(f.tmp)-1) + 0.5)
|
||||
if idx >= uint(len(f.tmp)) {
|
||||
idx = uint(len(f.tmp) - 1)
|
||||
}
|
||||
return f.tmp[idx]
|
||||
}
|
||||
|
||||
// GetFast returns a histogram from a pool.
|
||||
func GetFast() *Fast {
|
||||
v := fastPool.Get()
|
||||
if v == nil {
|
||||
return NewFast()
|
||||
}
|
||||
return v.(*Fast)
|
||||
}
|
||||
|
||||
// PutFast puts hf to the pool.
|
||||
//
|
||||
// hf cannot be used after this call.
|
||||
func PutFast(f *Fast) {
|
||||
f.Reset()
|
||||
fastPool.Put(f)
|
||||
}
|
||||
|
||||
var fastPool sync.Pool
|
27
vendor/golang.org/x/sys/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/sys/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
22
vendor/golang.org/x/sys/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/sys/PATENTS
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the Go project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of Go, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of Go. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of Go or any code incorporated within this
|
||||
implementation of Go constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of Go
|
||||
shall terminate as of the date such litigation is filed.
|
12
vendor/golang.org/x/sys/windows/aliases.go
generated
vendored
Normal file
12
vendor/golang.org/x/sys/windows/aliases.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && go1.9
|
||||
|
||||
package windows
|
||||
|
||||
import "syscall"
|
||||
|
||||
type Errno = syscall.Errno
|
||||
type SysProcAttr = syscall.SysProcAttr
|
416
vendor/golang.org/x/sys/windows/dll_windows.go
generated
vendored
Normal file
416
vendor/golang.org/x/sys/windows/dll_windows.go
generated
vendored
Normal file
@ -0,0 +1,416 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// We need to use LoadLibrary and GetProcAddress from the Go runtime, because
|
||||
// the these symbols are loaded by the system linker and are required to
|
||||
// dynamically load additional symbols. Note that in the Go runtime, these
|
||||
// return syscall.Handle and syscall.Errno, but these are the same, in fact,
|
||||
// as windows.Handle and windows.Errno, and we intend to keep these the same.
|
||||
|
||||
//go:linkname syscall_loadlibrary syscall.loadlibrary
|
||||
func syscall_loadlibrary(filename *uint16) (handle Handle, err Errno)
|
||||
|
||||
//go:linkname syscall_getprocaddress syscall.getprocaddress
|
||||
func syscall_getprocaddress(handle Handle, procname *uint8) (proc uintptr, err Errno)
|
||||
|
||||
// DLLError describes reasons for DLL load failures.
|
||||
type DLLError struct {
|
||||
Err error
|
||||
ObjName string
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *DLLError) Error() string { return e.Msg }
|
||||
|
||||
func (e *DLLError) Unwrap() error { return e.Err }
|
||||
|
||||
// A DLL implements access to a single DLL.
|
||||
type DLL struct {
|
||||
Name string
|
||||
Handle Handle
|
||||
}
|
||||
|
||||
// LoadDLL loads DLL file into memory.
|
||||
//
|
||||
// Warning: using LoadDLL without an absolute path name is subject to
|
||||
// DLL preloading attacks. To safely load a system DLL, use LazyDLL
|
||||
// with System set to true, or use LoadLibraryEx directly.
|
||||
func LoadDLL(name string) (dll *DLL, err error) {
|
||||
namep, err := UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h, e := syscall_loadlibrary(namep)
|
||||
if e != 0 {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to load " + name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
d := &DLL{
|
||||
Name: name,
|
||||
Handle: h,
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// MustLoadDLL is like LoadDLL but panics if load operation failes.
|
||||
func MustLoadDLL(name string) *DLL {
|
||||
d, e := LoadDLL(name)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// FindProc searches DLL d for procedure named name and returns *Proc
|
||||
// if found. It returns an error if search fails.
|
||||
func (d *DLL) FindProc(name string) (proc *Proc, err error) {
|
||||
namep, err := BytePtrFromString(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a, e := syscall_getprocaddress(d.Handle, namep)
|
||||
if e != 0 {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
p := &Proc{
|
||||
Dll: d,
|
||||
Name: name,
|
||||
addr: a,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// MustFindProc is like FindProc but panics if search fails.
|
||||
func (d *DLL) MustFindProc(name string) *Proc {
|
||||
p, e := d.FindProc(name)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// FindProcByOrdinal searches DLL d for procedure by ordinal and returns *Proc
|
||||
// if found. It returns an error if search fails.
|
||||
func (d *DLL) FindProcByOrdinal(ordinal uintptr) (proc *Proc, err error) {
|
||||
a, e := GetProcAddressByOrdinal(d.Handle, ordinal)
|
||||
name := "#" + itoa(int(ordinal))
|
||||
if e != nil {
|
||||
return nil, &DLLError{
|
||||
Err: e,
|
||||
ObjName: name,
|
||||
Msg: "Failed to find " + name + " procedure in " + d.Name + ": " + e.Error(),
|
||||
}
|
||||
}
|
||||
p := &Proc{
|
||||
Dll: d,
|
||||
Name: name,
|
||||
addr: a,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// MustFindProcByOrdinal is like FindProcByOrdinal but panics if search fails.
|
||||
func (d *DLL) MustFindProcByOrdinal(ordinal uintptr) *Proc {
|
||||
p, e := d.FindProcByOrdinal(ordinal)
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Release unloads DLL d from memory.
|
||||
func (d *DLL) Release() (err error) {
|
||||
return FreeLibrary(d.Handle)
|
||||
}
|
||||
|
||||
// A Proc implements access to a procedure inside a DLL.
|
||||
type Proc struct {
|
||||
Dll *DLL
|
||||
Name string
|
||||
addr uintptr
|
||||
}
|
||||
|
||||
// Addr returns the address of the procedure represented by p.
|
||||
// The return value can be passed to Syscall to run the procedure.
|
||||
func (p *Proc) Addr() uintptr {
|
||||
return p.addr
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||
// are supplied.
|
||||
//
|
||||
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||
// Callers must inspect the primary return value to decide whether an error occurred
|
||||
// (according to the semantics of the specific function being called) before consulting
|
||||
// the error. The error will be guaranteed to contain windows.Errno.
|
||||
func (p *Proc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||
switch len(a) {
|
||||
case 0:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), 0, 0, 0)
|
||||
case 1:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], 0, 0)
|
||||
case 2:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], 0)
|
||||
case 3:
|
||||
return syscall.Syscall(p.Addr(), uintptr(len(a)), a[0], a[1], a[2])
|
||||
case 4:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], 0, 0)
|
||||
case 5:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], 0)
|
||||
case 6:
|
||||
return syscall.Syscall6(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5])
|
||||
case 7:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], 0, 0)
|
||||
case 8:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], 0)
|
||||
case 9:
|
||||
return syscall.Syscall9(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8])
|
||||
case 10:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], 0, 0)
|
||||
case 11:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], 0)
|
||||
case 12:
|
||||
return syscall.Syscall12(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11])
|
||||
case 13:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], 0, 0)
|
||||
case 14:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], 0)
|
||||
case 15:
|
||||
return syscall.Syscall15(p.Addr(), uintptr(len(a)), a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14])
|
||||
default:
|
||||
panic("Call " + p.Name + " with too many arguments " + itoa(len(a)) + ".")
|
||||
}
|
||||
}
|
||||
|
||||
// A LazyDLL implements access to a single DLL.
|
||||
// It will delay the load of the DLL until the first
|
||||
// call to its Handle method or to one of its
|
||||
// LazyProc's Addr method.
|
||||
type LazyDLL struct {
|
||||
Name string
|
||||
|
||||
// System determines whether the DLL must be loaded from the
|
||||
// Windows System directory, bypassing the normal DLL search
|
||||
// path.
|
||||
System bool
|
||||
|
||||
mu sync.Mutex
|
||||
dll *DLL // non nil once DLL is loaded
|
||||
}
|
||||
|
||||
// Load loads DLL file d.Name into memory. It returns an error if fails.
|
||||
// Load will not try to load DLL, if it is already loaded into memory.
|
||||
func (d *LazyDLL) Load() error {
|
||||
// Non-racy version of:
|
||||
// if d.dll != nil {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll))) != nil {
|
||||
return nil
|
||||
}
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
if d.dll != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// kernel32.dll is special, since it's where LoadLibraryEx comes from.
|
||||
// The kernel already special-cases its name, so it's always
|
||||
// loaded from system32.
|
||||
var dll *DLL
|
||||
var err error
|
||||
if d.Name == "kernel32.dll" {
|
||||
dll, err = LoadDLL(d.Name)
|
||||
} else {
|
||||
dll, err = loadLibraryEx(d.Name, d.System)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Non-racy version of:
|
||||
// d.dll = dll
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&d.dll)), unsafe.Pointer(dll))
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustLoad is like Load but panics if search fails.
|
||||
func (d *LazyDLL) mustLoad() {
|
||||
e := d.Load()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle returns d's module handle.
|
||||
func (d *LazyDLL) Handle() uintptr {
|
||||
d.mustLoad()
|
||||
return uintptr(d.dll.Handle)
|
||||
}
|
||||
|
||||
// NewProc returns a LazyProc for accessing the named procedure in the DLL d.
|
||||
func (d *LazyDLL) NewProc(name string) *LazyProc {
|
||||
return &LazyProc{l: d, Name: name}
|
||||
}
|
||||
|
||||
// NewLazyDLL creates new LazyDLL associated with DLL file.
|
||||
func NewLazyDLL(name string) *LazyDLL {
|
||||
return &LazyDLL{Name: name}
|
||||
}
|
||||
|
||||
// NewLazySystemDLL is like NewLazyDLL, but will only
|
||||
// search Windows System directory for the DLL if name is
|
||||
// a base name (like "advapi32.dll").
|
||||
func NewLazySystemDLL(name string) *LazyDLL {
|
||||
return &LazyDLL{Name: name, System: true}
|
||||
}
|
||||
|
||||
// A LazyProc implements access to a procedure inside a LazyDLL.
|
||||
// It delays the lookup until the Addr method is called.
|
||||
type LazyProc struct {
|
||||
Name string
|
||||
|
||||
mu sync.Mutex
|
||||
l *LazyDLL
|
||||
proc *Proc
|
||||
}
|
||||
|
||||
// Find searches DLL for procedure named p.Name. It returns
|
||||
// an error if search fails. Find will not search procedure,
|
||||
// if it is already found and loaded into memory.
|
||||
func (p *LazyProc) Find() error {
|
||||
// Non-racy version of:
|
||||
// if p.proc == nil {
|
||||
if atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc))) == nil {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.proc == nil {
|
||||
e := p.l.Load()
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
proc, e := p.l.dll.FindProc(p.Name)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
// Non-racy version of:
|
||||
// p.proc = proc
|
||||
atomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&p.proc)), unsafe.Pointer(proc))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mustFind is like Find but panics if search fails.
|
||||
func (p *LazyProc) mustFind() {
|
||||
e := p.Find()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns the address of the procedure represented by p.
|
||||
// The return value can be passed to Syscall to run the procedure.
|
||||
// It will panic if the procedure cannot be found.
|
||||
func (p *LazyProc) Addr() uintptr {
|
||||
p.mustFind()
|
||||
return p.proc.Addr()
|
||||
}
|
||||
|
||||
//go:uintptrescapes
|
||||
|
||||
// Call executes procedure p with arguments a. It will panic, if more than 15 arguments
|
||||
// are supplied. It will also panic if the procedure cannot be found.
|
||||
//
|
||||
// The returned error is always non-nil, constructed from the result of GetLastError.
|
||||
// Callers must inspect the primary return value to decide whether an error occurred
|
||||
// (according to the semantics of the specific function being called) before consulting
|
||||
// the error. The error will be guaranteed to contain windows.Errno.
|
||||
func (p *LazyProc) Call(a ...uintptr) (r1, r2 uintptr, lastErr error) {
|
||||
p.mustFind()
|
||||
return p.proc.Call(a...)
|
||||
}
|
||||
|
||||
var canDoSearchSystem32Once struct {
|
||||
sync.Once
|
||||
v bool
|
||||
}
|
||||
|
||||
func initCanDoSearchSystem32() {
|
||||
// https://msdn.microsoft.com/en-us/library/ms684179(v=vs.85).aspx says:
|
||||
// "Windows 7, Windows Server 2008 R2, Windows Vista, and Windows
|
||||
// Server 2008: The LOAD_LIBRARY_SEARCH_* flags are available on
|
||||
// systems that have KB2533623 installed. To determine whether the
|
||||
// flags are available, use GetProcAddress to get the address of the
|
||||
// AddDllDirectory, RemoveDllDirectory, or SetDefaultDllDirectories
|
||||
// function. If GetProcAddress succeeds, the LOAD_LIBRARY_SEARCH_*
|
||||
// flags can be used with LoadLibraryEx."
|
||||
canDoSearchSystem32Once.v = (modkernel32.NewProc("AddDllDirectory").Find() == nil)
|
||||
}
|
||||
|
||||
func canDoSearchSystem32() bool {
|
||||
canDoSearchSystem32Once.Do(initCanDoSearchSystem32)
|
||||
return canDoSearchSystem32Once.v
|
||||
}
|
||||
|
||||
func isBaseName(name string) bool {
|
||||
for _, c := range name {
|
||||
if c == ':' || c == '/' || c == '\\' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// loadLibraryEx wraps the Windows LoadLibraryEx function.
|
||||
//
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684179(v=vs.85).aspx
|
||||
//
|
||||
// If name is not an absolute path, LoadLibraryEx searches for the DLL
|
||||
// in a variety of automatic locations unless constrained by flags.
|
||||
// See: https://msdn.microsoft.com/en-us/library/ff919712%28VS.85%29.aspx
|
||||
func loadLibraryEx(name string, system bool) (*DLL, error) {
|
||||
loadDLL := name
|
||||
var flags uintptr
|
||||
if system {
|
||||
if canDoSearchSystem32() {
|
||||
flags = LOAD_LIBRARY_SEARCH_SYSTEM32
|
||||
} else if isBaseName(name) {
|
||||
// WindowsXP or unpatched Windows machine
|
||||
// trying to load "foo.dll" out of the system
|
||||
// folder, but LoadLibraryEx doesn't support
|
||||
// that yet on their system, so emulate it.
|
||||
systemdir, err := GetSystemDirectory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
loadDLL = systemdir + "\\" + name
|
||||
}
|
||||
}
|
||||
h, err := LoadLibraryEx(loadDLL, 0, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DLL{Name: name, Handle: h}, nil
|
||||
}
|
||||
|
||||
type errString string
|
||||
|
||||
func (s errString) Error() string { return string(s) }
|
8
vendor/golang.org/x/sys/windows/empty.s
generated
vendored
Normal file
8
vendor/golang.org/x/sys/windows/empty.s
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !go1.12
|
||||
|
||||
// This file is here to allow bodyless functions with go:linkname for Go 1.11
|
||||
// and earlier (see https://golang.org/issue/23311).
|
54
vendor/golang.org/x/sys/windows/env_windows.go
generated
vendored
Normal file
54
vendor/golang.org/x/sys/windows/env_windows.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Windows environment variables.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Getenv(key string) (value string, found bool) {
|
||||
return syscall.Getenv(key)
|
||||
}
|
||||
|
||||
func Setenv(key, value string) error {
|
||||
return syscall.Setenv(key, value)
|
||||
}
|
||||
|
||||
func Clearenv() {
|
||||
syscall.Clearenv()
|
||||
}
|
||||
|
||||
func Environ() []string {
|
||||
return syscall.Environ()
|
||||
}
|
||||
|
||||
// Returns a default environment associated with the token, rather than the current
|
||||
// process. If inheritExisting is true, then this environment also inherits the
|
||||
// environment of the current process.
|
||||
func (token Token) Environ(inheritExisting bool) (env []string, err error) {
|
||||
var block *uint16
|
||||
err = CreateEnvironmentBlock(&block, token, inheritExisting)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer DestroyEnvironmentBlock(block)
|
||||
blockp := unsafe.Pointer(block)
|
||||
for {
|
||||
entry := UTF16PtrToString((*uint16)(blockp))
|
||||
if len(entry) == 0 {
|
||||
break
|
||||
}
|
||||
env = append(env, entry)
|
||||
blockp = unsafe.Add(blockp, 2*(len(entry)+1))
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
func Unsetenv(key string) error {
|
||||
return syscall.Unsetenv(key)
|
||||
}
|
20
vendor/golang.org/x/sys/windows/eventlog.go
generated
vendored
Normal file
20
vendor/golang.org/x/sys/windows/eventlog.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
EVENTLOG_SUCCESS = 0
|
||||
EVENTLOG_ERROR_TYPE = 1
|
||||
EVENTLOG_WARNING_TYPE = 2
|
||||
EVENTLOG_INFORMATION_TYPE = 4
|
||||
EVENTLOG_AUDIT_SUCCESS = 8
|
||||
EVENTLOG_AUDIT_FAILURE = 16
|
||||
)
|
||||
|
||||
//sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW
|
||||
//sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource
|
||||
//sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW
|
248
vendor/golang.org/x/sys/windows/exec_windows.go
generated
vendored
Normal file
248
vendor/golang.org/x/sys/windows/exec_windows.go
generated
vendored
Normal file
@ -0,0 +1,248 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Fork, exec, wait, etc.
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
errorspkg "errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// EscapeArg rewrites command line argument s as prescribed
|
||||
// in http://msdn.microsoft.com/en-us/library/ms880421.
|
||||
// This function returns "" (2 double quotes) if s is empty.
|
||||
// Alternatively, these transformations are done:
|
||||
// - every back slash (\) is doubled, but only if immediately
|
||||
// followed by double quote (");
|
||||
// - every double quote (") is escaped by back slash (\);
|
||||
// - finally, s is wrapped with double quotes (arg -> "arg"),
|
||||
// but only if there is space or tab inside s.
|
||||
func EscapeArg(s string) string {
|
||||
if len(s) == 0 {
|
||||
return `""`
|
||||
}
|
||||
n := len(s)
|
||||
hasSpace := false
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case '"', '\\':
|
||||
n++
|
||||
case ' ', '\t':
|
||||
hasSpace = true
|
||||
}
|
||||
}
|
||||
if hasSpace {
|
||||
n += 2 // Reserve space for quotes.
|
||||
}
|
||||
if n == len(s) {
|
||||
return s
|
||||
}
|
||||
|
||||
qs := make([]byte, n)
|
||||
j := 0
|
||||
if hasSpace {
|
||||
qs[j] = '"'
|
||||
j++
|
||||
}
|
||||
slashes := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
default:
|
||||
slashes = 0
|
||||
qs[j] = s[i]
|
||||
case '\\':
|
||||
slashes++
|
||||
qs[j] = s[i]
|
||||
case '"':
|
||||
for ; slashes > 0; slashes-- {
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
}
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
qs[j] = s[i]
|
||||
}
|
||||
j++
|
||||
}
|
||||
if hasSpace {
|
||||
for ; slashes > 0; slashes-- {
|
||||
qs[j] = '\\'
|
||||
j++
|
||||
}
|
||||
qs[j] = '"'
|
||||
j++
|
||||
}
|
||||
return string(qs[:j])
|
||||
}
|
||||
|
||||
// ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line,
|
||||
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
|
||||
// or any program that uses CommandLineToArgv.
|
||||
func ComposeCommandLine(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw:
|
||||
// “This function accepts command lines that contain a program name; the
|
||||
// program name can be enclosed in quotation marks or not.”
|
||||
//
|
||||
// Unfortunately, it provides no means of escaping interior quotation marks
|
||||
// within that program name, and we have no way to report them here.
|
||||
prog := args[0]
|
||||
mustQuote := len(prog) == 0
|
||||
for i := 0; i < len(prog); i++ {
|
||||
c := prog[i]
|
||||
if c <= ' ' || (c == '"' && i == 0) {
|
||||
// Force quotes for not only the ASCII space and tab as described in the
|
||||
// MSDN article, but also ASCII control characters.
|
||||
// The documentation for CommandLineToArgvW doesn't say what happens when
|
||||
// the first argument is not a valid program name, but it empirically
|
||||
// seems to drop unquoted control characters.
|
||||
mustQuote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
var commandLine []byte
|
||||
if mustQuote {
|
||||
commandLine = make([]byte, 0, len(prog)+2)
|
||||
commandLine = append(commandLine, '"')
|
||||
for i := 0; i < len(prog); i++ {
|
||||
c := prog[i]
|
||||
if c == '"' {
|
||||
// This quote would interfere with our surrounding quotes.
|
||||
// We have no way to report an error, so just strip out
|
||||
// the offending character instead.
|
||||
continue
|
||||
}
|
||||
commandLine = append(commandLine, c)
|
||||
}
|
||||
commandLine = append(commandLine, '"')
|
||||
} else {
|
||||
if len(args) == 1 {
|
||||
// args[0] is a valid command line representing itself.
|
||||
// No need to allocate a new slice or string for it.
|
||||
return prog
|
||||
}
|
||||
commandLine = []byte(prog)
|
||||
}
|
||||
|
||||
for _, arg := range args[1:] {
|
||||
commandLine = append(commandLine, ' ')
|
||||
// TODO(bcmills): since we're already appending to a slice, it would be nice
|
||||
// to avoid the intermediate allocations of EscapeArg.
|
||||
// Perhaps we can factor out an appendEscapedArg function.
|
||||
commandLine = append(commandLine, EscapeArg(arg)...)
|
||||
}
|
||||
return string(commandLine)
|
||||
}
|
||||
|
||||
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
|
||||
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
|
||||
// command lines are passed around.
|
||||
// DecomposeCommandLine returns an error if commandLine contains NUL.
|
||||
func DecomposeCommandLine(commandLine string) ([]string, error) {
|
||||
if len(commandLine) == 0 {
|
||||
return []string{}, nil
|
||||
}
|
||||
utf16CommandLine, err := UTF16FromString(commandLine)
|
||||
if err != nil {
|
||||
return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine")
|
||||
}
|
||||
var argc int32
|
||||
argv, err := commandLineToArgv(&utf16CommandLine[0], &argc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer LocalFree(Handle(unsafe.Pointer(argv)))
|
||||
|
||||
var args []string
|
||||
for _, p := range unsafe.Slice(argv, argc) {
|
||||
args = append(args, UTF16PtrToString(p))
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// CommandLineToArgv parses a Unicode command line string and sets
|
||||
// argc to the number of parsed arguments.
|
||||
//
|
||||
// The returned memory should be freed using a single call to LocalFree.
|
||||
//
|
||||
// Note that although the return type of CommandLineToArgv indicates 8192
|
||||
// entries of up to 8192 characters each, the actual count of parsed arguments
|
||||
// may exceed 8192, and the documentation for CommandLineToArgvW does not mention
|
||||
// any bound on the lengths of the individual argument strings.
|
||||
// (See https://go.dev/issue/63236.)
|
||||
func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) {
|
||||
argp, err := commandLineToArgv(cmd, argc)
|
||||
argv = (*[8192]*[8192]uint16)(unsafe.Pointer(argp))
|
||||
return argv, err
|
||||
}
|
||||
|
||||
func CloseOnExec(fd Handle) {
|
||||
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
|
||||
}
|
||||
|
||||
// FullPath retrieves the full path of the specified file.
|
||||
func FullPath(name string) (path string, err error) {
|
||||
p, err := UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
n := uint32(100)
|
||||
for {
|
||||
buf := make([]uint16, n)
|
||||
n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n <= uint32(len(buf)) {
|
||||
return UTF16ToString(buf[:n]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes.
|
||||
func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) {
|
||||
var size uintptr
|
||||
err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size)
|
||||
if err != ERROR_INSUFFICIENT_BUFFER {
|
||||
if err == nil {
|
||||
return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
alloc, err := LocalAlloc(LMEM_FIXED, uint32(size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// size is guaranteed to be ≥1 by InitializeProcThreadAttributeList.
|
||||
al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))}
|
||||
err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return al, err
|
||||
}
|
||||
|
||||
// Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute.
|
||||
func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error {
|
||||
al.pointers = append(al.pointers, value)
|
||||
return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil)
|
||||
}
|
||||
|
||||
// Delete frees ProcThreadAttributeList's resources.
|
||||
func (al *ProcThreadAttributeListContainer) Delete() {
|
||||
deleteProcThreadAttributeList(al.data)
|
||||
LocalFree(Handle(unsafe.Pointer(al.data)))
|
||||
al.data = nil
|
||||
al.pointers = nil
|
||||
}
|
||||
|
||||
// List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx.
|
||||
func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList {
|
||||
return al.data
|
||||
}
|
48
vendor/golang.org/x/sys/windows/memory_windows.go
generated
vendored
Normal file
48
vendor/golang.org/x/sys/windows/memory_windows.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
MEM_COMMIT = 0x00001000
|
||||
MEM_RESERVE = 0x00002000
|
||||
MEM_DECOMMIT = 0x00004000
|
||||
MEM_RELEASE = 0x00008000
|
||||
MEM_RESET = 0x00080000
|
||||
MEM_TOP_DOWN = 0x00100000
|
||||
MEM_WRITE_WATCH = 0x00200000
|
||||
MEM_PHYSICAL = 0x00400000
|
||||
MEM_RESET_UNDO = 0x01000000
|
||||
MEM_LARGE_PAGES = 0x20000000
|
||||
|
||||
PAGE_NOACCESS = 0x00000001
|
||||
PAGE_READONLY = 0x00000002
|
||||
PAGE_READWRITE = 0x00000004
|
||||
PAGE_WRITECOPY = 0x00000008
|
||||
PAGE_EXECUTE = 0x00000010
|
||||
PAGE_EXECUTE_READ = 0x00000020
|
||||
PAGE_EXECUTE_READWRITE = 0x00000040
|
||||
PAGE_EXECUTE_WRITECOPY = 0x00000080
|
||||
PAGE_GUARD = 0x00000100
|
||||
PAGE_NOCACHE = 0x00000200
|
||||
PAGE_WRITECOMBINE = 0x00000400
|
||||
PAGE_TARGETS_INVALID = 0x40000000
|
||||
PAGE_TARGETS_NO_UPDATE = 0x40000000
|
||||
|
||||
QUOTA_LIMITS_HARDWS_MIN_DISABLE = 0x00000002
|
||||
QUOTA_LIMITS_HARDWS_MIN_ENABLE = 0x00000001
|
||||
QUOTA_LIMITS_HARDWS_MAX_DISABLE = 0x00000008
|
||||
QUOTA_LIMITS_HARDWS_MAX_ENABLE = 0x00000004
|
||||
)
|
||||
|
||||
type MemoryBasicInformation struct {
|
||||
BaseAddress uintptr
|
||||
AllocationBase uintptr
|
||||
AllocationProtect uint32
|
||||
PartitionId uint16
|
||||
RegionSize uintptr
|
||||
State uint32
|
||||
Protect uint32
|
||||
Type uint32
|
||||
}
|
70
vendor/golang.org/x/sys/windows/mkerrors.bash
generated
vendored
Normal file
70
vendor/golang.org/x/sys/windows/mkerrors.bash
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
|
||||
winerror="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/winerror.h | sort -Vr | head -n 1)"
|
||||
[[ -n $winerror ]] || { echo "Unable to find winerror.h" >&2; exit 1; }
|
||||
ntstatus="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/ntstatus.h | sort -Vr | head -n 1)"
|
||||
[[ -n $ntstatus ]] || { echo "Unable to find ntstatus.h" >&2; exit 1; }
|
||||
|
||||
declare -A errors
|
||||
|
||||
{
|
||||
echo "// Code generated by 'mkerrors.bash'; DO NOT EDIT."
|
||||
echo
|
||||
echo "package windows"
|
||||
echo "import \"syscall\""
|
||||
echo "const ("
|
||||
|
||||
while read -r line; do
|
||||
unset vtype
|
||||
if [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?([A-Z][A-Z0-9_]+k?)\)? ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?((0x)?[0-9A-Fa-f]+)L?\)? ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
vtype="${BASH_REMATCH[2]}"
|
||||
elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +\(\(([A-Z]+)\)((0x)?[0-9A-Fa-f]+)L?\) ]]; then
|
||||
key="${BASH_REMATCH[1]}"
|
||||
value="${BASH_REMATCH[3]}"
|
||||
vtype="${BASH_REMATCH[2]}"
|
||||
else
|
||||
continue
|
||||
fi
|
||||
[[ -n $key && -n $value ]] || continue
|
||||
[[ -z ${errors["$key"]} ]] || continue
|
||||
errors["$key"]="$value"
|
||||
if [[ -v vtype ]]; then
|
||||
if [[ $key == FACILITY_* || $key == NO_ERROR ]]; then
|
||||
vtype=""
|
||||
elif [[ $vtype == *HANDLE* || $vtype == *HRESULT* ]]; then
|
||||
vtype="Handle"
|
||||
else
|
||||
vtype="syscall.Errno"
|
||||
fi
|
||||
last_vtype="$vtype"
|
||||
else
|
||||
vtype=""
|
||||
if [[ $last_vtype == Handle && $value == NO_ERROR ]]; then
|
||||
value="S_OK"
|
||||
elif [[ $last_vtype == syscall.Errno && $value == NO_ERROR ]]; then
|
||||
value="ERROR_SUCCESS"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$key $vtype = $value"
|
||||
done < "$winerror"
|
||||
|
||||
while read -r line; do
|
||||
[[ $line =~ ^#define\ (STATUS_[^\s]+)\ +\(\(NTSTATUS\)((0x)?[0-9a-fA-F]+)L?\) ]] || continue
|
||||
echo "${BASH_REMATCH[1]} NTStatus = ${BASH_REMATCH[2]}"
|
||||
done < "$ntstatus"
|
||||
|
||||
echo ")"
|
||||
} | gofmt > "zerrors_windows.go"
|
27
vendor/golang.org/x/sys/windows/mkknownfolderids.bash
generated
vendored
Normal file
27
vendor/golang.org/x/sys/windows/mkknownfolderids.bash
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2019 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -e
|
||||
shopt -s nullglob
|
||||
|
||||
knownfolders="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/um/KnownFolders.h | sort -Vr | head -n 1)"
|
||||
[[ -n $knownfolders ]] || { echo "Unable to find KnownFolders.h" >&2; exit 1; }
|
||||
|
||||
{
|
||||
echo "// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT."
|
||||
echo
|
||||
echo "package windows"
|
||||
echo "type KNOWNFOLDERID GUID"
|
||||
echo "var ("
|
||||
while read -r line; do
|
||||
[[ $line =~ DEFINE_KNOWN_FOLDER\((FOLDERID_[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+)\) ]] || continue
|
||||
printf "%s = &KNOWNFOLDERID{0x%08x, 0x%04x, 0x%04x, [8]byte{0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x}}\n" \
|
||||
"${BASH_REMATCH[1]}" $(( "${BASH_REMATCH[2]}" )) $(( "${BASH_REMATCH[3]}" )) $(( "${BASH_REMATCH[4]}" )) \
|
||||
$(( "${BASH_REMATCH[5]}" )) $(( "${BASH_REMATCH[6]}" )) $(( "${BASH_REMATCH[7]}" )) $(( "${BASH_REMATCH[8]}" )) \
|
||||
$(( "${BASH_REMATCH[9]}" )) $(( "${BASH_REMATCH[10]}" )) $(( "${BASH_REMATCH[11]}" )) $(( "${BASH_REMATCH[12]}" ))
|
||||
done < "$knownfolders"
|
||||
echo ")"
|
||||
} | gofmt > "zknownfolderids_windows.go"
|
9
vendor/golang.org/x/sys/windows/mksyscall.go
generated
vendored
Normal file
9
vendor/golang.org/x/sys/windows/mksyscall.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build generate
|
||||
|
||||
package windows
|
||||
|
||||
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go setupapi_windows.go
|
30
vendor/golang.org/x/sys/windows/race.go
generated
vendored
Normal file
30
vendor/golang.org/x/sys/windows/race.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && race
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = true
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
runtime.RaceAcquire(addr)
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
runtime.RaceReleaseMerge(addr)
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceReadRange(addr, len)
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
runtime.RaceWriteRange(addr, len)
|
||||
}
|
25
vendor/golang.org/x/sys/windows/race0.go
generated
vendored
Normal file
25
vendor/golang.org/x/sys/windows/race0.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && !race
|
||||
|
||||
package windows
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const raceenabled = false
|
||||
|
||||
func raceAcquire(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReleaseMerge(addr unsafe.Pointer) {
|
||||
}
|
||||
|
||||
func raceReadRange(addr unsafe.Pointer, len int) {
|
||||
}
|
||||
|
||||
func raceWriteRange(addr unsafe.Pointer, len int) {
|
||||
}
|
1435
vendor/golang.org/x/sys/windows/security_windows.go
generated
vendored
Normal file
1435
vendor/golang.org/x/sys/windows/security_windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
257
vendor/golang.org/x/sys/windows/service.go
generated
vendored
Normal file
257
vendor/golang.org/x/sys/windows/service.go
generated
vendored
Normal file
@ -0,0 +1,257 @@
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
const (
|
||||
SC_MANAGER_CONNECT = 1
|
||||
SC_MANAGER_CREATE_SERVICE = 2
|
||||
SC_MANAGER_ENUMERATE_SERVICE = 4
|
||||
SC_MANAGER_LOCK = 8
|
||||
SC_MANAGER_QUERY_LOCK_STATUS = 16
|
||||
SC_MANAGER_MODIFY_BOOT_CONFIG = 32
|
||||
SC_MANAGER_ALL_ACCESS = 0xf003f
|
||||
)
|
||||
|
||||
const (
|
||||
SERVICE_KERNEL_DRIVER = 1
|
||||
SERVICE_FILE_SYSTEM_DRIVER = 2
|
||||
SERVICE_ADAPTER = 4
|
||||
SERVICE_RECOGNIZER_DRIVER = 8
|
||||
SERVICE_WIN32_OWN_PROCESS = 16
|
||||
SERVICE_WIN32_SHARE_PROCESS = 32
|
||||
SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS
|
||||
SERVICE_INTERACTIVE_PROCESS = 256
|
||||
SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER
|
||||
SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS
|
||||
|
||||
SERVICE_BOOT_START = 0
|
||||
SERVICE_SYSTEM_START = 1
|
||||
SERVICE_AUTO_START = 2
|
||||
SERVICE_DEMAND_START = 3
|
||||
SERVICE_DISABLED = 4
|
||||
|
||||
SERVICE_ERROR_IGNORE = 0
|
||||
SERVICE_ERROR_NORMAL = 1
|
||||
SERVICE_ERROR_SEVERE = 2
|
||||
SERVICE_ERROR_CRITICAL = 3
|
||||
|
||||
SC_STATUS_PROCESS_INFO = 0
|
||||
|
||||
SC_ACTION_NONE = 0
|
||||
SC_ACTION_RESTART = 1
|
||||
SC_ACTION_REBOOT = 2
|
||||
SC_ACTION_RUN_COMMAND = 3
|
||||
|
||||
SERVICE_STOPPED = 1
|
||||
SERVICE_START_PENDING = 2
|
||||
SERVICE_STOP_PENDING = 3
|
||||
SERVICE_RUNNING = 4
|
||||
SERVICE_CONTINUE_PENDING = 5
|
||||
SERVICE_PAUSE_PENDING = 6
|
||||
SERVICE_PAUSED = 7
|
||||
SERVICE_NO_CHANGE = 0xffffffff
|
||||
|
||||
SERVICE_ACCEPT_STOP = 1
|
||||
SERVICE_ACCEPT_PAUSE_CONTINUE = 2
|
||||
SERVICE_ACCEPT_SHUTDOWN = 4
|
||||
SERVICE_ACCEPT_PARAMCHANGE = 8
|
||||
SERVICE_ACCEPT_NETBINDCHANGE = 16
|
||||
SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32
|
||||
SERVICE_ACCEPT_POWEREVENT = 64
|
||||
SERVICE_ACCEPT_SESSIONCHANGE = 128
|
||||
SERVICE_ACCEPT_PRESHUTDOWN = 256
|
||||
|
||||
SERVICE_CONTROL_STOP = 1
|
||||
SERVICE_CONTROL_PAUSE = 2
|
||||
SERVICE_CONTROL_CONTINUE = 3
|
||||
SERVICE_CONTROL_INTERROGATE = 4
|
||||
SERVICE_CONTROL_SHUTDOWN = 5
|
||||
SERVICE_CONTROL_PARAMCHANGE = 6
|
||||
SERVICE_CONTROL_NETBINDADD = 7
|
||||
SERVICE_CONTROL_NETBINDREMOVE = 8
|
||||
SERVICE_CONTROL_NETBINDENABLE = 9
|
||||
SERVICE_CONTROL_NETBINDDISABLE = 10
|
||||
SERVICE_CONTROL_DEVICEEVENT = 11
|
||||
SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12
|
||||
SERVICE_CONTROL_POWEREVENT = 13
|
||||
SERVICE_CONTROL_SESSIONCHANGE = 14
|
||||
SERVICE_CONTROL_PRESHUTDOWN = 15
|
||||
|
||||
SERVICE_ACTIVE = 1
|
||||
SERVICE_INACTIVE = 2
|
||||
SERVICE_STATE_ALL = 3
|
||||
|
||||
SERVICE_QUERY_CONFIG = 1
|
||||
SERVICE_CHANGE_CONFIG = 2
|
||||
SERVICE_QUERY_STATUS = 4
|
||||
SERVICE_ENUMERATE_DEPENDENTS = 8
|
||||
SERVICE_START = 16
|
||||
SERVICE_STOP = 32
|
||||
SERVICE_PAUSE_CONTINUE = 64
|
||||
SERVICE_INTERROGATE = 128
|
||||
SERVICE_USER_DEFINED_CONTROL = 256
|
||||
SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL
|
||||
|
||||
SERVICE_RUNS_IN_SYSTEM_PROCESS = 1
|
||||
|
||||
SERVICE_CONFIG_DESCRIPTION = 1
|
||||
SERVICE_CONFIG_FAILURE_ACTIONS = 2
|
||||
SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3
|
||||
SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4
|
||||
SERVICE_CONFIG_SERVICE_SID_INFO = 5
|
||||
SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6
|
||||
SERVICE_CONFIG_PRESHUTDOWN_INFO = 7
|
||||
SERVICE_CONFIG_TRIGGER_INFO = 8
|
||||
SERVICE_CONFIG_PREFERRED_NODE = 9
|
||||
SERVICE_CONFIG_LAUNCH_PROTECTED = 12
|
||||
|
||||
SERVICE_SID_TYPE_NONE = 0
|
||||
SERVICE_SID_TYPE_UNRESTRICTED = 1
|
||||
SERVICE_SID_TYPE_RESTRICTED = 2 | SERVICE_SID_TYPE_UNRESTRICTED
|
||||
|
||||
SC_ENUM_PROCESS_INFO = 0
|
||||
|
||||
SERVICE_NOTIFY_STATUS_CHANGE = 2
|
||||
SERVICE_NOTIFY_STOPPED = 0x00000001
|
||||
SERVICE_NOTIFY_START_PENDING = 0x00000002
|
||||
SERVICE_NOTIFY_STOP_PENDING = 0x00000004
|
||||
SERVICE_NOTIFY_RUNNING = 0x00000008
|
||||
SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010
|
||||
SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020
|
||||
SERVICE_NOTIFY_PAUSED = 0x00000040
|
||||
SERVICE_NOTIFY_CREATED = 0x00000080
|
||||
SERVICE_NOTIFY_DELETED = 0x00000100
|
||||
SERVICE_NOTIFY_DELETE_PENDING = 0x00000200
|
||||
|
||||
SC_EVENT_DATABASE_CHANGE = 0
|
||||
SC_EVENT_PROPERTY_CHANGE = 1
|
||||
SC_EVENT_STATUS_CHANGE = 2
|
||||
|
||||
SERVICE_START_REASON_DEMAND = 0x00000001
|
||||
SERVICE_START_REASON_AUTO = 0x00000002
|
||||
SERVICE_START_REASON_TRIGGER = 0x00000004
|
||||
SERVICE_START_REASON_RESTART_ON_FAILURE = 0x00000008
|
||||
SERVICE_START_REASON_DELAYEDAUTO = 0x00000010
|
||||
|
||||
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
|
||||
)
|
||||
|
||||
type ENUM_SERVICE_STATUS struct {
|
||||
ServiceName *uint16
|
||||
DisplayName *uint16
|
||||
ServiceStatus SERVICE_STATUS
|
||||
}
|
||||
|
||||
type SERVICE_STATUS struct {
|
||||
ServiceType uint32
|
||||
CurrentState uint32
|
||||
ControlsAccepted uint32
|
||||
Win32ExitCode uint32
|
||||
ServiceSpecificExitCode uint32
|
||||
CheckPoint uint32
|
||||
WaitHint uint32
|
||||
}
|
||||
|
||||
type SERVICE_TABLE_ENTRY struct {
|
||||
ServiceName *uint16
|
||||
ServiceProc uintptr
|
||||
}
|
||||
|
||||
type QUERY_SERVICE_CONFIG struct {
|
||||
ServiceType uint32
|
||||
StartType uint32
|
||||
ErrorControl uint32
|
||||
BinaryPathName *uint16
|
||||
LoadOrderGroup *uint16
|
||||
TagId uint32
|
||||
Dependencies *uint16
|
||||
ServiceStartName *uint16
|
||||
DisplayName *uint16
|
||||
}
|
||||
|
||||
type SERVICE_DESCRIPTION struct {
|
||||
Description *uint16
|
||||
}
|
||||
|
||||
type SERVICE_DELAYED_AUTO_START_INFO struct {
|
||||
IsDelayedAutoStartUp uint32
|
||||
}
|
||||
|
||||
type SERVICE_STATUS_PROCESS struct {
|
||||
ServiceType uint32
|
||||
CurrentState uint32
|
||||
ControlsAccepted uint32
|
||||
Win32ExitCode uint32
|
||||
ServiceSpecificExitCode uint32
|
||||
CheckPoint uint32
|
||||
WaitHint uint32
|
||||
ProcessId uint32
|
||||
ServiceFlags uint32
|
||||
}
|
||||
|
||||
type ENUM_SERVICE_STATUS_PROCESS struct {
|
||||
ServiceName *uint16
|
||||
DisplayName *uint16
|
||||
ServiceStatusProcess SERVICE_STATUS_PROCESS
|
||||
}
|
||||
|
||||
type SERVICE_NOTIFY struct {
|
||||
Version uint32
|
||||
NotifyCallback uintptr
|
||||
Context uintptr
|
||||
NotificationStatus uint32
|
||||
ServiceStatus SERVICE_STATUS_PROCESS
|
||||
NotificationTriggered uint32
|
||||
ServiceNames *uint16
|
||||
}
|
||||
|
||||
type SERVICE_FAILURE_ACTIONS struct {
|
||||
ResetPeriod uint32
|
||||
RebootMsg *uint16
|
||||
Command *uint16
|
||||
ActionsCount uint32
|
||||
Actions *SC_ACTION
|
||||
}
|
||||
|
||||
type SERVICE_FAILURE_ACTIONS_FLAG struct {
|
||||
FailureActionsOnNonCrashFailures int32
|
||||
}
|
||||
|
||||
type SC_ACTION struct {
|
||||
Type uint32
|
||||
Delay uint32
|
||||
}
|
||||
|
||||
type QUERY_SERVICE_LOCK_STATUS struct {
|
||||
IsLocked uint32
|
||||
LockOwner *uint16
|
||||
LockDuration uint32
|
||||
}
|
||||
|
||||
//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
|
||||
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
|
||||
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
|
||||
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
|
||||
//sys DeleteService(service Handle) (err error) = advapi32.DeleteService
|
||||
//sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW
|
||||
//sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus
|
||||
//sys QueryServiceLockStatus(mgr Handle, lockStatus *QUERY_SERVICE_LOCK_STATUS, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceLockStatusW
|
||||
//sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService
|
||||
//sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW
|
||||
//sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus
|
||||
//sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW
|
||||
//sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW
|
||||
//sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W
|
||||
//sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W
|
||||
//sys EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW
|
||||
//sys QueryServiceStatusEx(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceStatusEx
|
||||
//sys NotifyServiceStatusChange(service Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) = advapi32.NotifyServiceStatusChangeW
|
||||
//sys SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) = sechost.SubscribeServiceChangeNotifications?
|
||||
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
|
||||
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
|
||||
//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
|
||||
//sys EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) = advapi32.EnumDependentServicesW
|
1425
vendor/golang.org/x/sys/windows/setupapi_windows.go
generated
vendored
Normal file
1425
vendor/golang.org/x/sys/windows/setupapi_windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
22
vendor/golang.org/x/sys/windows/str.go
generated
vendored
Normal file
22
vendor/golang.org/x/sys/windows/str.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
package windows
|
||||
|
||||
func itoa(val int) string { // do it here rather than with fmt to avoid dependency
|
||||
if val < 0 {
|
||||
return "-" + itoa(-val)
|
||||
}
|
||||
var buf [32]byte // big enough for int64
|
||||
i := len(buf) - 1
|
||||
for val >= 10 {
|
||||
buf[i] = byte(val%10 + '0')
|
||||
i--
|
||||
val /= 10
|
||||
}
|
||||
buf[i] = byte(val + '0')
|
||||
return string(buf[i:])
|
||||
}
|
104
vendor/golang.org/x/sys/windows/syscall.go
generated
vendored
Normal file
104
vendor/golang.org/x/sys/windows/syscall.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
|
||||
// Package windows contains an interface to the low-level operating system
|
||||
// primitives. OS details vary depending on the underlying system, and
|
||||
// by default, godoc will display the OS-specific documentation for the current
|
||||
// system. If you want godoc to display syscall documentation for another
|
||||
// system, set $GOOS and $GOARCH to the desired system. For example, if
|
||||
// you want to view documentation for freebsd/arm on linux/amd64, set $GOOS
|
||||
// to freebsd and $GOARCH to arm.
|
||||
//
|
||||
// The primary use of this package is inside other packages that provide a more
|
||||
// portable interface to the system, such as "os", "time" and "net". Use
|
||||
// those packages rather than this one if you can.
|
||||
//
|
||||
// For details of the functions and data types in this package consult
|
||||
// the manuals for the appropriate operating system.
|
||||
//
|
||||
// These calls return err == nil to indicate success; otherwise
|
||||
// err represents an operating system error describing the failure and
|
||||
// holds a value of type syscall.Errno.
|
||||
package windows // import "golang.org/x/sys/windows"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ByteSliceFromString returns a NUL-terminated slice of bytes
|
||||
// containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, syscall.EINVAL).
|
||||
func ByteSliceFromString(s string) ([]byte, error) {
|
||||
if strings.IndexByte(s, 0) != -1 {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
a := make([]byte, len(s)+1)
|
||||
copy(a, s)
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// BytePtrFromString returns a pointer to a NUL-terminated array of
|
||||
// bytes containing the text of s. If s contains a NUL byte at any
|
||||
// location, it returns (nil, syscall.EINVAL).
|
||||
func BytePtrFromString(s string) (*byte, error) {
|
||||
a, err := ByteSliceFromString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &a[0], nil
|
||||
}
|
||||
|
||||
// ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any
|
||||
// bytes after the NUL removed.
|
||||
func ByteSliceToString(s []byte) string {
|
||||
if i := bytes.IndexByte(s, 0); i != -1 {
|
||||
s = s[:i]
|
||||
}
|
||||
return string(s)
|
||||
}
|
||||
|
||||
// BytePtrToString takes a pointer to a sequence of text and returns the corresponding string.
|
||||
// If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated
|
||||
// at a zero byte; if the zero byte is not present, the program may crash.
|
||||
func BytePtrToString(p *byte) string {
|
||||
if p == nil {
|
||||
return ""
|
||||
}
|
||||
if *p == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Find NUL terminator.
|
||||
n := 0
|
||||
for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ {
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + 1)
|
||||
}
|
||||
|
||||
return string(unsafe.Slice(p, n))
|
||||
}
|
||||
|
||||
// Single-word zero for use when we need a valid pointer to 0 bytes.
|
||||
// See mksyscall.pl.
|
||||
var _zero uintptr
|
||||
|
||||
func (ts *Timespec) Unix() (sec int64, nsec int64) {
|
||||
return int64(ts.Sec), int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Unix() (sec int64, nsec int64) {
|
||||
return int64(tv.Sec), int64(tv.Usec) * 1000
|
||||
}
|
||||
|
||||
func (ts *Timespec) Nano() int64 {
|
||||
return int64(ts.Sec)*1e9 + int64(ts.Nsec)
|
||||
}
|
||||
|
||||
func (tv *Timeval) Nano() int64 {
|
||||
return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000
|
||||
}
|
1836
vendor/golang.org/x/sys/windows/syscall_windows.go
generated
vendored
Normal file
1836
vendor/golang.org/x/sys/windows/syscall_windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3382
vendor/golang.org/x/sys/windows/types_windows.go
generated
vendored
Normal file
3382
vendor/golang.org/x/sys/windows/types_windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
35
vendor/golang.org/x/sys/windows/types_windows_386.go
generated
vendored
Normal file
35
vendor/golang.org/x/sys/windows/types_windows_386.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Port uint16
|
||||
Proto *byte
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
_ uint32 // pad to 8 byte boundary
|
||||
}
|
34
vendor/golang.org/x/sys/windows/types_windows_amd64.go
generated
vendored
Normal file
34
vendor/golang.org/x/sys/windows/types_windows_amd64.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Proto *byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
}
|
35
vendor/golang.org/x/sys/windows/types_windows_arm.go
generated
vendored
Normal file
35
vendor/golang.org/x/sys/windows/types_windows_arm.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Port uint16
|
||||
Proto *byte
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
_ uint32 // pad to 8 byte boundary
|
||||
}
|
34
vendor/golang.org/x/sys/windows/types_windows_arm64.go
generated
vendored
Normal file
34
vendor/golang.org/x/sys/windows/types_windows_arm64.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package windows
|
||||
|
||||
type WSAData struct {
|
||||
Version uint16
|
||||
HighVersion uint16
|
||||
MaxSockets uint16
|
||||
MaxUdpDg uint16
|
||||
VendorInfo *byte
|
||||
Description [WSADESCRIPTION_LEN + 1]byte
|
||||
SystemStatus [WSASYS_STATUS_LEN + 1]byte
|
||||
}
|
||||
|
||||
type Servent struct {
|
||||
Name *byte
|
||||
Aliases **byte
|
||||
Proto *byte
|
||||
Port uint16
|
||||
}
|
||||
|
||||
type JOBOBJECT_BASIC_LIMIT_INFORMATION struct {
|
||||
PerProcessUserTimeLimit int64
|
||||
PerJobUserTimeLimit int64
|
||||
LimitFlags uint32
|
||||
MinimumWorkingSetSize uintptr
|
||||
MaximumWorkingSetSize uintptr
|
||||
ActiveProcessLimit uint32
|
||||
Affinity uintptr
|
||||
PriorityClass uint32
|
||||
SchedulingClass uint32
|
||||
}
|
9468
vendor/golang.org/x/sys/windows/zerrors_windows.go
generated
vendored
Normal file
9468
vendor/golang.org/x/sys/windows/zerrors_windows.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
149
vendor/golang.org/x/sys/windows/zknownfolderids_windows.go
generated
vendored
Normal file
149
vendor/golang.org/x/sys/windows/zknownfolderids_windows.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT.
|
||||
|
||||
package windows
|
||||
|
||||
type KNOWNFOLDERID GUID
|
||||
|
||||
var (
|
||||
FOLDERID_NetworkFolder = &KNOWNFOLDERID{0xd20beec4, 0x5ca8, 0x4905, [8]byte{0xae, 0x3b, 0xbf, 0x25, 0x1e, 0xa0, 0x9b, 0x53}}
|
||||
FOLDERID_ComputerFolder = &KNOWNFOLDERID{0x0ac0837c, 0xbbf8, 0x452a, [8]byte{0x85, 0x0d, 0x79, 0xd0, 0x8e, 0x66, 0x7c, 0xa7}}
|
||||
FOLDERID_InternetFolder = &KNOWNFOLDERID{0x4d9f7874, 0x4e0c, 0x4904, [8]byte{0x96, 0x7b, 0x40, 0xb0, 0xd2, 0x0c, 0x3e, 0x4b}}
|
||||
FOLDERID_ControlPanelFolder = &KNOWNFOLDERID{0x82a74aeb, 0xaeb4, 0x465c, [8]byte{0xa0, 0x14, 0xd0, 0x97, 0xee, 0x34, 0x6d, 0x63}}
|
||||
FOLDERID_PrintersFolder = &KNOWNFOLDERID{0x76fc4e2d, 0xd6ad, 0x4519, [8]byte{0xa6, 0x63, 0x37, 0xbd, 0x56, 0x06, 0x81, 0x85}}
|
||||
FOLDERID_SyncManagerFolder = &KNOWNFOLDERID{0x43668bf8, 0xc14e, 0x49b2, [8]byte{0x97, 0xc9, 0x74, 0x77, 0x84, 0xd7, 0x84, 0xb7}}
|
||||
FOLDERID_SyncSetupFolder = &KNOWNFOLDERID{0x0f214138, 0xb1d3, 0x4a90, [8]byte{0xbb, 0xa9, 0x27, 0xcb, 0xc0, 0xc5, 0x38, 0x9a}}
|
||||
FOLDERID_ConflictFolder = &KNOWNFOLDERID{0x4bfefb45, 0x347d, 0x4006, [8]byte{0xa5, 0xbe, 0xac, 0x0c, 0xb0, 0x56, 0x71, 0x92}}
|
||||
FOLDERID_SyncResultsFolder = &KNOWNFOLDERID{0x289a9a43, 0xbe44, 0x4057, [8]byte{0xa4, 0x1b, 0x58, 0x7a, 0x76, 0xd7, 0xe7, 0xf9}}
|
||||
FOLDERID_RecycleBinFolder = &KNOWNFOLDERID{0xb7534046, 0x3ecb, 0x4c18, [8]byte{0xbe, 0x4e, 0x64, 0xcd, 0x4c, 0xb7, 0xd6, 0xac}}
|
||||
FOLDERID_ConnectionsFolder = &KNOWNFOLDERID{0x6f0cd92b, 0x2e97, 0x45d1, [8]byte{0x88, 0xff, 0xb0, 0xd1, 0x86, 0xb8, 0xde, 0xdd}}
|
||||
FOLDERID_Fonts = &KNOWNFOLDERID{0xfd228cb7, 0xae11, 0x4ae3, [8]byte{0x86, 0x4c, 0x16, 0xf3, 0x91, 0x0a, 0xb8, 0xfe}}
|
||||
FOLDERID_Desktop = &KNOWNFOLDERID{0xb4bfcc3a, 0xdb2c, 0x424c, [8]byte{0xb0, 0x29, 0x7f, 0xe9, 0x9a, 0x87, 0xc6, 0x41}}
|
||||
FOLDERID_Startup = &KNOWNFOLDERID{0xb97d20bb, 0xf46a, 0x4c97, [8]byte{0xba, 0x10, 0x5e, 0x36, 0x08, 0x43, 0x08, 0x54}}
|
||||
FOLDERID_Programs = &KNOWNFOLDERID{0xa77f5d77, 0x2e2b, 0x44c3, [8]byte{0xa6, 0xa2, 0xab, 0xa6, 0x01, 0x05, 0x4a, 0x51}}
|
||||
FOLDERID_StartMenu = &KNOWNFOLDERID{0x625b53c3, 0xab48, 0x4ec1, [8]byte{0xba, 0x1f, 0xa1, 0xef, 0x41, 0x46, 0xfc, 0x19}}
|
||||
FOLDERID_Recent = &KNOWNFOLDERID{0xae50c081, 0xebd2, 0x438a, [8]byte{0x86, 0x55, 0x8a, 0x09, 0x2e, 0x34, 0x98, 0x7a}}
|
||||
FOLDERID_SendTo = &KNOWNFOLDERID{0x8983036c, 0x27c0, 0x404b, [8]byte{0x8f, 0x08, 0x10, 0x2d, 0x10, 0xdc, 0xfd, 0x74}}
|
||||
FOLDERID_Documents = &KNOWNFOLDERID{0xfdd39ad0, 0x238f, 0x46af, [8]byte{0xad, 0xb4, 0x6c, 0x85, 0x48, 0x03, 0x69, 0xc7}}
|
||||
FOLDERID_Favorites = &KNOWNFOLDERID{0x1777f761, 0x68ad, 0x4d8a, [8]byte{0x87, 0xbd, 0x30, 0xb7, 0x59, 0xfa, 0x33, 0xdd}}
|
||||
FOLDERID_NetHood = &KNOWNFOLDERID{0xc5abbf53, 0xe17f, 0x4121, [8]byte{0x89, 0x00, 0x86, 0x62, 0x6f, 0xc2, 0xc9, 0x73}}
|
||||
FOLDERID_PrintHood = &KNOWNFOLDERID{0x9274bd8d, 0xcfd1, 0x41c3, [8]byte{0xb3, 0x5e, 0xb1, 0x3f, 0x55, 0xa7, 0x58, 0xf4}}
|
||||
FOLDERID_Templates = &KNOWNFOLDERID{0xa63293e8, 0x664e, 0x48db, [8]byte{0xa0, 0x79, 0xdf, 0x75, 0x9e, 0x05, 0x09, 0xf7}}
|
||||
FOLDERID_CommonStartup = &KNOWNFOLDERID{0x82a5ea35, 0xd9cd, 0x47c5, [8]byte{0x96, 0x29, 0xe1, 0x5d, 0x2f, 0x71, 0x4e, 0x6e}}
|
||||
FOLDERID_CommonPrograms = &KNOWNFOLDERID{0x0139d44e, 0x6afe, 0x49f2, [8]byte{0x86, 0x90, 0x3d, 0xaf, 0xca, 0xe6, 0xff, 0xb8}}
|
||||
FOLDERID_CommonStartMenu = &KNOWNFOLDERID{0xa4115719, 0xd62e, 0x491d, [8]byte{0xaa, 0x7c, 0xe7, 0x4b, 0x8b, 0xe3, 0xb0, 0x67}}
|
||||
FOLDERID_PublicDesktop = &KNOWNFOLDERID{0xc4aa340d, 0xf20f, 0x4863, [8]byte{0xaf, 0xef, 0xf8, 0x7e, 0xf2, 0xe6, 0xba, 0x25}}
|
||||
FOLDERID_ProgramData = &KNOWNFOLDERID{0x62ab5d82, 0xfdc1, 0x4dc3, [8]byte{0xa9, 0xdd, 0x07, 0x0d, 0x1d, 0x49, 0x5d, 0x97}}
|
||||
FOLDERID_CommonTemplates = &KNOWNFOLDERID{0xb94237e7, 0x57ac, 0x4347, [8]byte{0x91, 0x51, 0xb0, 0x8c, 0x6c, 0x32, 0xd1, 0xf7}}
|
||||
FOLDERID_PublicDocuments = &KNOWNFOLDERID{0xed4824af, 0xdce4, 0x45a8, [8]byte{0x81, 0xe2, 0xfc, 0x79, 0x65, 0x08, 0x36, 0x34}}
|
||||
FOLDERID_RoamingAppData = &KNOWNFOLDERID{0x3eb685db, 0x65f9, 0x4cf6, [8]byte{0xa0, 0x3a, 0xe3, 0xef, 0x65, 0x72, 0x9f, 0x3d}}
|
||||
FOLDERID_LocalAppData = &KNOWNFOLDERID{0xf1b32785, 0x6fba, 0x4fcf, [8]byte{0x9d, 0x55, 0x7b, 0x8e, 0x7f, 0x15, 0x70, 0x91}}
|
||||
FOLDERID_LocalAppDataLow = &KNOWNFOLDERID{0xa520a1a4, 0x1780, 0x4ff6, [8]byte{0xbd, 0x18, 0x16, 0x73, 0x43, 0xc5, 0xaf, 0x16}}
|
||||
FOLDERID_InternetCache = &KNOWNFOLDERID{0x352481e8, 0x33be, 0x4251, [8]byte{0xba, 0x85, 0x60, 0x07, 0xca, 0xed, 0xcf, 0x9d}}
|
||||
FOLDERID_Cookies = &KNOWNFOLDERID{0x2b0f765d, 0xc0e9, 0x4171, [8]byte{0x90, 0x8e, 0x08, 0xa6, 0x11, 0xb8, 0x4f, 0xf6}}
|
||||
FOLDERID_History = &KNOWNFOLDERID{0xd9dc8a3b, 0xb784, 0x432e, [8]byte{0xa7, 0x81, 0x5a, 0x11, 0x30, 0xa7, 0x59, 0x63}}
|
||||
FOLDERID_System = &KNOWNFOLDERID{0x1ac14e77, 0x02e7, 0x4e5d, [8]byte{0xb7, 0x44, 0x2e, 0xb1, 0xae, 0x51, 0x98, 0xb7}}
|
||||
FOLDERID_SystemX86 = &KNOWNFOLDERID{0xd65231b0, 0xb2f1, 0x4857, [8]byte{0xa4, 0xce, 0xa8, 0xe7, 0xc6, 0xea, 0x7d, 0x27}}
|
||||
FOLDERID_Windows = &KNOWNFOLDERID{0xf38bf404, 0x1d43, 0x42f2, [8]byte{0x93, 0x05, 0x67, 0xde, 0x0b, 0x28, 0xfc, 0x23}}
|
||||
FOLDERID_Profile = &KNOWNFOLDERID{0x5e6c858f, 0x0e22, 0x4760, [8]byte{0x9a, 0xfe, 0xea, 0x33, 0x17, 0xb6, 0x71, 0x73}}
|
||||
FOLDERID_Pictures = &KNOWNFOLDERID{0x33e28130, 0x4e1e, 0x4676, [8]byte{0x83, 0x5a, 0x98, 0x39, 0x5c, 0x3b, 0xc3, 0xbb}}
|
||||
FOLDERID_ProgramFilesX86 = &KNOWNFOLDERID{0x7c5a40ef, 0xa0fb, 0x4bfc, [8]byte{0x87, 0x4a, 0xc0, 0xf2, 0xe0, 0xb9, 0xfa, 0x8e}}
|
||||
FOLDERID_ProgramFilesCommonX86 = &KNOWNFOLDERID{0xde974d24, 0xd9c6, 0x4d3e, [8]byte{0xbf, 0x91, 0xf4, 0x45, 0x51, 0x20, 0xb9, 0x17}}
|
||||
FOLDERID_ProgramFilesX64 = &KNOWNFOLDERID{0x6d809377, 0x6af0, 0x444b, [8]byte{0x89, 0x57, 0xa3, 0x77, 0x3f, 0x02, 0x20, 0x0e}}
|
||||
FOLDERID_ProgramFilesCommonX64 = &KNOWNFOLDERID{0x6365d5a7, 0x0f0d, 0x45e5, [8]byte{0x87, 0xf6, 0x0d, 0xa5, 0x6b, 0x6a, 0x4f, 0x7d}}
|
||||
FOLDERID_ProgramFiles = &KNOWNFOLDERID{0x905e63b6, 0xc1bf, 0x494e, [8]byte{0xb2, 0x9c, 0x65, 0xb7, 0x32, 0xd3, 0xd2, 0x1a}}
|
||||
FOLDERID_ProgramFilesCommon = &KNOWNFOLDERID{0xf7f1ed05, 0x9f6d, 0x47a2, [8]byte{0xaa, 0xae, 0x29, 0xd3, 0x17, 0xc6, 0xf0, 0x66}}
|
||||
FOLDERID_UserProgramFiles = &KNOWNFOLDERID{0x5cd7aee2, 0x2219, 0x4a67, [8]byte{0xb8, 0x5d, 0x6c, 0x9c, 0xe1, 0x56, 0x60, 0xcb}}
|
||||
FOLDERID_UserProgramFilesCommon = &KNOWNFOLDERID{0xbcbd3057, 0xca5c, 0x4622, [8]byte{0xb4, 0x2d, 0xbc, 0x56, 0xdb, 0x0a, 0xe5, 0x16}}
|
||||
FOLDERID_AdminTools = &KNOWNFOLDERID{0x724ef170, 0xa42d, 0x4fef, [8]byte{0x9f, 0x26, 0xb6, 0x0e, 0x84, 0x6f, 0xba, 0x4f}}
|
||||
FOLDERID_CommonAdminTools = &KNOWNFOLDERID{0xd0384e7d, 0xbac3, 0x4797, [8]byte{0x8f, 0x14, 0xcb, 0xa2, 0x29, 0xb3, 0x92, 0xb5}}
|
||||
FOLDERID_Music = &KNOWNFOLDERID{0x4bd8d571, 0x6d19, 0x48d3, [8]byte{0xbe, 0x97, 0x42, 0x22, 0x20, 0x08, 0x0e, 0x43}}
|
||||
FOLDERID_Videos = &KNOWNFOLDERID{0x18989b1d, 0x99b5, 0x455b, [8]byte{0x84, 0x1c, 0xab, 0x7c, 0x74, 0xe4, 0xdd, 0xfc}}
|
||||
FOLDERID_Ringtones = &KNOWNFOLDERID{0xc870044b, 0xf49e, 0x4126, [8]byte{0xa9, 0xc3, 0xb5, 0x2a, 0x1f, 0xf4, 0x11, 0xe8}}
|
||||
FOLDERID_PublicPictures = &KNOWNFOLDERID{0xb6ebfb86, 0x6907, 0x413c, [8]byte{0x9a, 0xf7, 0x4f, 0xc2, 0xab, 0xf0, 0x7c, 0xc5}}
|
||||
FOLDERID_PublicMusic = &KNOWNFOLDERID{0x3214fab5, 0x9757, 0x4298, [8]byte{0xbb, 0x61, 0x92, 0xa9, 0xde, 0xaa, 0x44, 0xff}}
|
||||
FOLDERID_PublicVideos = &KNOWNFOLDERID{0x2400183a, 0x6185, 0x49fb, [8]byte{0xa2, 0xd8, 0x4a, 0x39, 0x2a, 0x60, 0x2b, 0xa3}}
|
||||
FOLDERID_PublicRingtones = &KNOWNFOLDERID{0xe555ab60, 0x153b, 0x4d17, [8]byte{0x9f, 0x04, 0xa5, 0xfe, 0x99, 0xfc, 0x15, 0xec}}
|
||||
FOLDERID_ResourceDir = &KNOWNFOLDERID{0x8ad10c31, 0x2adb, 0x4296, [8]byte{0xa8, 0xf7, 0xe4, 0x70, 0x12, 0x32, 0xc9, 0x72}}
|
||||
FOLDERID_LocalizedResourcesDir = &KNOWNFOLDERID{0x2a00375e, 0x224c, 0x49de, [8]byte{0xb8, 0xd1, 0x44, 0x0d, 0xf7, 0xef, 0x3d, 0xdc}}
|
||||
FOLDERID_CommonOEMLinks = &KNOWNFOLDERID{0xc1bae2d0, 0x10df, 0x4334, [8]byte{0xbe, 0xdd, 0x7a, 0xa2, 0x0b, 0x22, 0x7a, 0x9d}}
|
||||
FOLDERID_CDBurning = &KNOWNFOLDERID{0x9e52ab10, 0xf80d, 0x49df, [8]byte{0xac, 0xb8, 0x43, 0x30, 0xf5, 0x68, 0x78, 0x55}}
|
||||
FOLDERID_UserProfiles = &KNOWNFOLDERID{0x0762d272, 0xc50a, 0x4bb0, [8]byte{0xa3, 0x82, 0x69, 0x7d, 0xcd, 0x72, 0x9b, 0x80}}
|
||||
FOLDERID_Playlists = &KNOWNFOLDERID{0xde92c1c7, 0x837f, 0x4f69, [8]byte{0xa3, 0xbb, 0x86, 0xe6, 0x31, 0x20, 0x4a, 0x23}}
|
||||
FOLDERID_SamplePlaylists = &KNOWNFOLDERID{0x15ca69b3, 0x30ee, 0x49c1, [8]byte{0xac, 0xe1, 0x6b, 0x5e, 0xc3, 0x72, 0xaf, 0xb5}}
|
||||
FOLDERID_SampleMusic = &KNOWNFOLDERID{0xb250c668, 0xf57d, 0x4ee1, [8]byte{0xa6, 0x3c, 0x29, 0x0e, 0xe7, 0xd1, 0xaa, 0x1f}}
|
||||
FOLDERID_SamplePictures = &KNOWNFOLDERID{0xc4900540, 0x2379, 0x4c75, [8]byte{0x84, 0x4b, 0x64, 0xe6, 0xfa, 0xf8, 0x71, 0x6b}}
|
||||
FOLDERID_SampleVideos = &KNOWNFOLDERID{0x859ead94, 0x2e85, 0x48ad, [8]byte{0xa7, 0x1a, 0x09, 0x69, 0xcb, 0x56, 0xa6, 0xcd}}
|
||||
FOLDERID_PhotoAlbums = &KNOWNFOLDERID{0x69d2cf90, 0xfc33, 0x4fb7, [8]byte{0x9a, 0x0c, 0xeb, 0xb0, 0xf0, 0xfc, 0xb4, 0x3c}}
|
||||
FOLDERID_Public = &KNOWNFOLDERID{0xdfdf76a2, 0xc82a, 0x4d63, [8]byte{0x90, 0x6a, 0x56, 0x44, 0xac, 0x45, 0x73, 0x85}}
|
||||
FOLDERID_ChangeRemovePrograms = &KNOWNFOLDERID{0xdf7266ac, 0x9274, 0x4867, [8]byte{0x8d, 0x55, 0x3b, 0xd6, 0x61, 0xde, 0x87, 0x2d}}
|
||||
FOLDERID_AppUpdates = &KNOWNFOLDERID{0xa305ce99, 0xf527, 0x492b, [8]byte{0x8b, 0x1a, 0x7e, 0x76, 0xfa, 0x98, 0xd6, 0xe4}}
|
||||
FOLDERID_AddNewPrograms = &KNOWNFOLDERID{0xde61d971, 0x5ebc, 0x4f02, [8]byte{0xa3, 0xa9, 0x6c, 0x82, 0x89, 0x5e, 0x5c, 0x04}}
|
||||
FOLDERID_Downloads = &KNOWNFOLDERID{0x374de290, 0x123f, 0x4565, [8]byte{0x91, 0x64, 0x39, 0xc4, 0x92, 0x5e, 0x46, 0x7b}}
|
||||
FOLDERID_PublicDownloads = &KNOWNFOLDERID{0x3d644c9b, 0x1fb8, 0x4f30, [8]byte{0x9b, 0x45, 0xf6, 0x70, 0x23, 0x5f, 0x79, 0xc0}}
|
||||
FOLDERID_SavedSearches = &KNOWNFOLDERID{0x7d1d3a04, 0xdebb, 0x4115, [8]byte{0x95, 0xcf, 0x2f, 0x29, 0xda, 0x29, 0x20, 0xda}}
|
||||
FOLDERID_QuickLaunch = &KNOWNFOLDERID{0x52a4f021, 0x7b75, 0x48a9, [8]byte{0x9f, 0x6b, 0x4b, 0x87, 0xa2, 0x10, 0xbc, 0x8f}}
|
||||
FOLDERID_Contacts = &KNOWNFOLDERID{0x56784854, 0xc6cb, 0x462b, [8]byte{0x81, 0x69, 0x88, 0xe3, 0x50, 0xac, 0xb8, 0x82}}
|
||||
FOLDERID_SidebarParts = &KNOWNFOLDERID{0xa75d362e, 0x50fc, 0x4fb7, [8]byte{0xac, 0x2c, 0xa8, 0xbe, 0xaa, 0x31, 0x44, 0x93}}
|
||||
FOLDERID_SidebarDefaultParts = &KNOWNFOLDERID{0x7b396e54, 0x9ec5, 0x4300, [8]byte{0xbe, 0x0a, 0x24, 0x82, 0xeb, 0xae, 0x1a, 0x26}}
|
||||
FOLDERID_PublicGameTasks = &KNOWNFOLDERID{0xdebf2536, 0xe1a8, 0x4c59, [8]byte{0xb6, 0xa2, 0x41, 0x45, 0x86, 0x47, 0x6a, 0xea}}
|
||||
FOLDERID_GameTasks = &KNOWNFOLDERID{0x054fae61, 0x4dd8, 0x4787, [8]byte{0x80, 0xb6, 0x09, 0x02, 0x20, 0xc4, 0xb7, 0x00}}
|
||||
FOLDERID_SavedGames = &KNOWNFOLDERID{0x4c5c32ff, 0xbb9d, 0x43b0, [8]byte{0xb5, 0xb4, 0x2d, 0x72, 0xe5, 0x4e, 0xaa, 0xa4}}
|
||||
FOLDERID_Games = &KNOWNFOLDERID{0xcac52c1a, 0xb53d, 0x4edc, [8]byte{0x92, 0xd7, 0x6b, 0x2e, 0x8a, 0xc1, 0x94, 0x34}}
|
||||
FOLDERID_SEARCH_MAPI = &KNOWNFOLDERID{0x98ec0e18, 0x2098, 0x4d44, [8]byte{0x86, 0x44, 0x66, 0x97, 0x93, 0x15, 0xa2, 0x81}}
|
||||
FOLDERID_SEARCH_CSC = &KNOWNFOLDERID{0xee32e446, 0x31ca, 0x4aba, [8]byte{0x81, 0x4f, 0xa5, 0xeb, 0xd2, 0xfd, 0x6d, 0x5e}}
|
||||
FOLDERID_Links = &KNOWNFOLDERID{0xbfb9d5e0, 0xc6a9, 0x404c, [8]byte{0xb2, 0xb2, 0xae, 0x6d, 0xb6, 0xaf, 0x49, 0x68}}
|
||||
FOLDERID_UsersFiles = &KNOWNFOLDERID{0xf3ce0f7c, 0x4901, 0x4acc, [8]byte{0x86, 0x48, 0xd5, 0xd4, 0x4b, 0x04, 0xef, 0x8f}}
|
||||
FOLDERID_UsersLibraries = &KNOWNFOLDERID{0xa302545d, 0xdeff, 0x464b, [8]byte{0xab, 0xe8, 0x61, 0xc8, 0x64, 0x8d, 0x93, 0x9b}}
|
||||
FOLDERID_SearchHome = &KNOWNFOLDERID{0x190337d1, 0xb8ca, 0x4121, [8]byte{0xa6, 0x39, 0x6d, 0x47, 0x2d, 0x16, 0x97, 0x2a}}
|
||||
FOLDERID_OriginalImages = &KNOWNFOLDERID{0x2c36c0aa, 0x5812, 0x4b87, [8]byte{0xbf, 0xd0, 0x4c, 0xd0, 0xdf, 0xb1, 0x9b, 0x39}}
|
||||
FOLDERID_DocumentsLibrary = &KNOWNFOLDERID{0x7b0db17d, 0x9cd2, 0x4a93, [8]byte{0x97, 0x33, 0x46, 0xcc, 0x89, 0x02, 0x2e, 0x7c}}
|
||||
FOLDERID_MusicLibrary = &KNOWNFOLDERID{0x2112ab0a, 0xc86a, 0x4ffe, [8]byte{0xa3, 0x68, 0x0d, 0xe9, 0x6e, 0x47, 0x01, 0x2e}}
|
||||
FOLDERID_PicturesLibrary = &KNOWNFOLDERID{0xa990ae9f, 0xa03b, 0x4e80, [8]byte{0x94, 0xbc, 0x99, 0x12, 0xd7, 0x50, 0x41, 0x04}}
|
||||
FOLDERID_VideosLibrary = &KNOWNFOLDERID{0x491e922f, 0x5643, 0x4af4, [8]byte{0xa7, 0xeb, 0x4e, 0x7a, 0x13, 0x8d, 0x81, 0x74}}
|
||||
FOLDERID_RecordedTVLibrary = &KNOWNFOLDERID{0x1a6fdba2, 0xf42d, 0x4358, [8]byte{0xa7, 0x98, 0xb7, 0x4d, 0x74, 0x59, 0x26, 0xc5}}
|
||||
FOLDERID_HomeGroup = &KNOWNFOLDERID{0x52528a6b, 0xb9e3, 0x4add, [8]byte{0xb6, 0x0d, 0x58, 0x8c, 0x2d, 0xba, 0x84, 0x2d}}
|
||||
FOLDERID_HomeGroupCurrentUser = &KNOWNFOLDERID{0x9b74b6a3, 0x0dfd, 0x4f11, [8]byte{0x9e, 0x78, 0x5f, 0x78, 0x00, 0xf2, 0xe7, 0x72}}
|
||||
FOLDERID_DeviceMetadataStore = &KNOWNFOLDERID{0x5ce4a5e9, 0xe4eb, 0x479d, [8]byte{0xb8, 0x9f, 0x13, 0x0c, 0x02, 0x88, 0x61, 0x55}}
|
||||
FOLDERID_Libraries = &KNOWNFOLDERID{0x1b3ea5dc, 0xb587, 0x4786, [8]byte{0xb4, 0xef, 0xbd, 0x1d, 0xc3, 0x32, 0xae, 0xae}}
|
||||
FOLDERID_PublicLibraries = &KNOWNFOLDERID{0x48daf80b, 0xe6cf, 0x4f4e, [8]byte{0xb8, 0x00, 0x0e, 0x69, 0xd8, 0x4e, 0xe3, 0x84}}
|
||||
FOLDERID_UserPinned = &KNOWNFOLDERID{0x9e3995ab, 0x1f9c, 0x4f13, [8]byte{0xb8, 0x27, 0x48, 0xb2, 0x4b, 0x6c, 0x71, 0x74}}
|
||||
FOLDERID_ImplicitAppShortcuts = &KNOWNFOLDERID{0xbcb5256f, 0x79f6, 0x4cee, [8]byte{0xb7, 0x25, 0xdc, 0x34, 0xe4, 0x02, 0xfd, 0x46}}
|
||||
FOLDERID_AccountPictures = &KNOWNFOLDERID{0x008ca0b1, 0x55b4, 0x4c56, [8]byte{0xb8, 0xa8, 0x4d, 0xe4, 0xb2, 0x99, 0xd3, 0xbe}}
|
||||
FOLDERID_PublicUserTiles = &KNOWNFOLDERID{0x0482af6c, 0x08f1, 0x4c34, [8]byte{0x8c, 0x90, 0xe1, 0x7e, 0xc9, 0x8b, 0x1e, 0x17}}
|
||||
FOLDERID_AppsFolder = &KNOWNFOLDERID{0x1e87508d, 0x89c2, 0x42f0, [8]byte{0x8a, 0x7e, 0x64, 0x5a, 0x0f, 0x50, 0xca, 0x58}}
|
||||
FOLDERID_StartMenuAllPrograms = &KNOWNFOLDERID{0xf26305ef, 0x6948, 0x40b9, [8]byte{0xb2, 0x55, 0x81, 0x45, 0x3d, 0x09, 0xc7, 0x85}}
|
||||
FOLDERID_CommonStartMenuPlaces = &KNOWNFOLDERID{0xa440879f, 0x87a0, 0x4f7d, [8]byte{0xb7, 0x00, 0x02, 0x07, 0xb9, 0x66, 0x19, 0x4a}}
|
||||
FOLDERID_ApplicationShortcuts = &KNOWNFOLDERID{0xa3918781, 0xe5f2, 0x4890, [8]byte{0xb3, 0xd9, 0xa7, 0xe5, 0x43, 0x32, 0x32, 0x8c}}
|
||||
FOLDERID_RoamingTiles = &KNOWNFOLDERID{0x00bcfc5a, 0xed94, 0x4e48, [8]byte{0x96, 0xa1, 0x3f, 0x62, 0x17, 0xf2, 0x19, 0x90}}
|
||||
FOLDERID_RoamedTileImages = &KNOWNFOLDERID{0xaaa8d5a5, 0xf1d6, 0x4259, [8]byte{0xba, 0xa8, 0x78, 0xe7, 0xef, 0x60, 0x83, 0x5e}}
|
||||
FOLDERID_Screenshots = &KNOWNFOLDERID{0xb7bede81, 0xdf94, 0x4682, [8]byte{0xa7, 0xd8, 0x57, 0xa5, 0x26, 0x20, 0xb8, 0x6f}}
|
||||
FOLDERID_CameraRoll = &KNOWNFOLDERID{0xab5fb87b, 0x7ce2, 0x4f83, [8]byte{0x91, 0x5d, 0x55, 0x08, 0x46, 0xc9, 0x53, 0x7b}}
|
||||
FOLDERID_SkyDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||
FOLDERID_OneDrive = &KNOWNFOLDERID{0xa52bba46, 0xe9e1, 0x435f, [8]byte{0xb3, 0xd9, 0x28, 0xda, 0xa6, 0x48, 0xc0, 0xf6}}
|
||||
FOLDERID_SkyDriveDocuments = &KNOWNFOLDERID{0x24d89e24, 0x2f19, 0x4534, [8]byte{0x9d, 0xde, 0x6a, 0x66, 0x71, 0xfb, 0xb8, 0xfe}}
|
||||
FOLDERID_SkyDrivePictures = &KNOWNFOLDERID{0x339719b5, 0x8c47, 0x4894, [8]byte{0x94, 0xc2, 0xd8, 0xf7, 0x7a, 0xdd, 0x44, 0xa6}}
|
||||
FOLDERID_SkyDriveMusic = &KNOWNFOLDERID{0xc3f2459e, 0x80d6, 0x45dc, [8]byte{0xbf, 0xef, 0x1f, 0x76, 0x9f, 0x2b, 0xe7, 0x30}}
|
||||
FOLDERID_SkyDriveCameraRoll = &KNOWNFOLDERID{0x767e6811, 0x49cb, 0x4273, [8]byte{0x87, 0xc2, 0x20, 0xf3, 0x55, 0xe1, 0x08, 0x5b}}
|
||||
FOLDERID_SearchHistory = &KNOWNFOLDERID{0x0d4c3db6, 0x03a3, 0x462f, [8]byte{0xa0, 0xe6, 0x08, 0x92, 0x4c, 0x41, 0xb5, 0xd4}}
|
||||
FOLDERID_SearchTemplates = &KNOWNFOLDERID{0x7e636bfe, 0xdfa9, 0x4d5e, [8]byte{0xb4, 0x56, 0xd7, 0xb3, 0x98, 0x51, 0xd8, 0xa9}}
|
||||
FOLDERID_CameraRollLibrary = &KNOWNFOLDERID{0x2b20df75, 0x1eda, 0x4039, [8]byte{0x80, 0x97, 0x38, 0x79, 0x82, 0x27, 0xd5, 0xb7}}
|
||||
FOLDERID_SavedPictures = &KNOWNFOLDERID{0x3b193882, 0xd3ad, 0x4eab, [8]byte{0x96, 0x5a, 0x69, 0x82, 0x9d, 0x1f, 0xb5, 0x9f}}
|
||||
FOLDERID_SavedPicturesLibrary = &KNOWNFOLDERID{0xe25b5812, 0xbe88, 0x4bd9, [8]byte{0x94, 0xb0, 0x29, 0x23, 0x34, 0x77, 0xb6, 0xc3}}
|
||||
FOLDERID_RetailDemo = &KNOWNFOLDERID{0x12d4c69e, 0x24ad, 0x4923, [8]byte{0xbe, 0x19, 0x31, 0x32, 0x1c, 0x43, 0xa7, 0x67}}
|
||||
FOLDERID_Device = &KNOWNFOLDERID{0x1c2ac1dc, 0x4358, 0x4b6c, [8]byte{0x97, 0x33, 0xaf, 0x21, 0x15, 0x65, 0x76, 0xf0}}
|
||||
FOLDERID_DevelopmentFiles = &KNOWNFOLDERID{0xdbe8e08e, 0x3053, 0x4bbc, [8]byte{0xb1, 0x83, 0x2a, 0x7b, 0x2b, 0x19, 0x1e, 0x59}}
|
||||
FOLDERID_Objects3D = &KNOWNFOLDERID{0x31c0dd25, 0x9439, 0x4f12, [8]byte{0xbf, 0x41, 0x7f, 0xf4, 0xed, 0xa3, 0x87, 0x22}}
|
||||
FOLDERID_AppCaptures = &KNOWNFOLDERID{0xedc0fe71, 0x98d8, 0x4f4a, [8]byte{0xb9, 0x20, 0xc8, 0xdc, 0x13, 0x3c, 0xb1, 0x65}}
|
||||
FOLDERID_LocalDocuments = &KNOWNFOLDERID{0xf42ee2d3, 0x909f, 0x4907, [8]byte{0x88, 0x71, 0x4c, 0x22, 0xfc, 0x0b, 0xf7, 0x56}}
|
||||
FOLDERID_LocalPictures = &KNOWNFOLDERID{0x0ddd015d, 0xb06c, 0x45d5, [8]byte{0x8c, 0x4c, 0xf5, 0x97, 0x13, 0x85, 0x46, 0x39}}
|
||||
FOLDERID_LocalVideos = &KNOWNFOLDERID{0x35286a68, 0x3c57, 0x41a1, [8]byte{0xbb, 0xb1, 0x0e, 0xae, 0x73, 0xd7, 0x6c, 0x95}}
|
||||
FOLDERID_LocalMusic = &KNOWNFOLDERID{0xa0c69a99, 0x21c8, 0x4671, [8]byte{0x87, 0x03, 0x79, 0x34, 0x16, 0x2f, 0xcf, 0x1d}}
|
||||
FOLDERID_LocalDownloads = &KNOWNFOLDERID{0x7d83ee9b, 0x2244, 0x4e70, [8]byte{0xb1, 0xf5, 0x53, 0x93, 0x04, 0x2a, 0xf1, 0xe4}}
|
||||
FOLDERID_RecordedCalls = &KNOWNFOLDERID{0x2f8b40c2, 0x83ed, 0x48ee, [8]byte{0xb3, 0x83, 0xa1, 0xf1, 0x57, 0xec, 0x6f, 0x9a}}
|
||||
FOLDERID_AllAppMods = &KNOWNFOLDERID{0x7ad67899, 0x66af, 0x43ba, [8]byte{0x91, 0x56, 0x6a, 0xad, 0x42, 0xe6, 0xc5, 0x96}}
|
||||
FOLDERID_CurrentAppMods = &KNOWNFOLDERID{0x3db40b20, 0x2a30, 0x4dbe, [8]byte{0x91, 0x7e, 0x77, 0x1d, 0xd2, 0x1d, 0xd0, 0x99}}
|
||||
FOLDERID_AppDataDesktop = &KNOWNFOLDERID{0xb2c5e279, 0x7add, 0x439f, [8]byte{0xb2, 0x8c, 0xc4, 0x1f, 0xe1, 0xbb, 0xf6, 0x72}}
|
||||
FOLDERID_AppDataDocuments = &KNOWNFOLDERID{0x7be16610, 0x1f7f, 0x44ac, [8]byte{0xbf, 0xf0, 0x83, 0xe1, 0x5f, 0x2f, 0xfc, 0xa1}}
|
||||
FOLDERID_AppDataFavorites = &KNOWNFOLDERID{0x7cfbefbc, 0xde1f, 0x45aa, [8]byte{0xb8, 0x43, 0xa5, 0x42, 0xac, 0x53, 0x6c, 0xc9}}
|
||||
FOLDERID_AppDataProgramData = &KNOWNFOLDERID{0x559d40a3, 0xa036, 0x40fa, [8]byte{0xaf, 0x61, 0x84, 0xcb, 0x43, 0x0a, 0x4d, 0x34}}
|
||||
)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user